Skip to content

Commit 355ab71

Browse files
committed
GH-50385: [Ruby] Add bitmap builder for red-arrow-format
1 parent a1ec26f commit 355ab71

6 files changed

Lines changed: 253 additions & 12 deletions

File tree

ruby/red-arrow-format/lib/arrow-format/array.rb

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require "bigdecimal"
1818

1919
require_relative "bitmap"
20+
require_relative "bitmap-builder"
2021

2122
module ArrowFormat
2223
class Array
@@ -186,7 +187,16 @@ def element_size
186187
end
187188

188189
class BooleanArray < PrimitiveArray
189-
def initialize(size, validity_buffer, values_buffer)
190+
def initialize(*args)
191+
if args.size == 1
192+
args = build_data(args[0])
193+
end
194+
n_args = args.size
195+
if args.size != 3
196+
message = "wrong number of arguments (given #{n_args}, expected 1 or 3)"
197+
raise ArgumentError, message
198+
end
199+
size, validity_buffer, values_buffer = args
190200
super(BooleanType.singleton, size, validity_buffer, values_buffer)
191201
end
192202

@@ -210,6 +220,30 @@ def clear_cache
210220
super
211221
@values_bitmap = nil
212222
end
223+
224+
def build_data(data)
225+
n = data.size
226+
validity_buffer_builder = SparseBitmapBuilder.new(true)
227+
have_null = false
228+
values_buffer_builder = DenseBitmapBuilder.new
229+
data.each_with_index do |value, i|
230+
if value.nil?
231+
have_null = true
232+
validity_buffer_builder.set(i)
233+
values_buffer_builder.append(false)
234+
elsif value
235+
values_buffer_builder.append(true)
236+
else
237+
values_buffer_builder.append(false)
238+
end
239+
end
240+
if have_null
241+
validity_buffer = validity_buffer_builder.finish(n)
242+
else
243+
validity_buffer = nil
244+
end
245+
return n, validity_buffer, values_buffer_builder.finish
246+
end
213247
end
214248

215249
class IntArray < PrimitiveArray
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# or more contributor license agreements. See the NOTICE file
2+
# distributed with this work for additional information
3+
# regarding copyright ownership. The ASF licenses this file
4+
# to you under the Apache License, Version 2.0 (the
5+
# "License"); you may not use this file except in compliance
6+
# with the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing,
11+
# software distributed under the License is distributed on an
12+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
# KIND, either express or implied. See the License for the
14+
# specific language governing permissions and limitations
15+
# under the License.
16+
17+
require_relative "buffer-alignable"
18+
19+
module ArrowFormat
20+
class DenseBitmapBuilder
21+
include BufferAlignable
22+
23+
def initialize
24+
@buffer = +"".b
25+
@n_bits = 0
26+
@byte = 0
27+
end
28+
29+
def append(value)
30+
@byte |= 1 << @n_bits if value
31+
@n_bits += 1
32+
flush if @n_bits == 8
33+
self
34+
end
35+
36+
def finish
37+
flush if @n_bits > 0
38+
padding_size = buffer_padding_size(@buffer)
39+
@buffer.append_as_bytes(padding(padding_size)) if padding_size > 0
40+
@buffer.freeze
41+
IO::Buffer.for(@buffer)
42+
end
43+
44+
private
45+
def flush
46+
@buffer.append_as_bytes([@byte].pack("C"))
47+
@n_bits = 0
48+
@byte = 0
49+
end
50+
end
51+
52+
class SparseBitmapBuilder
53+
include BufferAlignable
54+
55+
def initialize(default)
56+
@default = default
57+
@indexes = []
58+
end
59+
60+
def set(index)
61+
@indexes << index
62+
end
63+
64+
def finish(size)
65+
builder = DenseBitmapBuilder.new
66+
not_set_value = @default
67+
set_value = !@default
68+
previous_index = 0
69+
@indexes.sort.each do |index|
70+
previous_index.upto(index - 1) do
71+
builder.append(not_set_value)
72+
end
73+
builder.append(set_value)
74+
previous_index = index + 1
75+
end
76+
if previous_index.zero?
77+
n_remains = size
78+
else
79+
n_remains = size - previous_index
80+
end
81+
n_remains.times do
82+
builder.append(not_set_value)
83+
end
84+
builder.finish
85+
end
86+
end
87+
end

ruby/red-arrow-format/lib/arrow-format/integration/json-reader.rb

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,11 @@ def read_schema
205205
end
206206

207207
def read_bitmap(bitmap)
208-
buffer = +"".b
209-
bitmap.each_slice(8) do |bits|
210-
byte = 0
211-
while bits.size < 8
212-
bits << 0
213-
end
214-
bits.reverse_each do |bit|
215-
byte = (byte << 1) + bit
216-
end
217-
buffer << [byte].pack("C")
208+
builder = DenseBitmapBuilder.new
209+
bitmap.each do |bit|
210+
builder.append(bit == 1 ? true : false)
218211
end
219-
IO::Buffer.for(buffer)
212+
builder.finish
220213
end
221214

222215
def read_types(types)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
class TestBooleanArray < Test::Unit::TestCase
19+
def test_mixed
20+
assert_equal([true, nil, false],
21+
ArrowFormat::BooleanArray.new([true, nil, false]).to_a)
22+
end
23+
24+
def test_no_null
25+
assert_equal([true, false],
26+
ArrowFormat::BooleanArray.new([true, false]).to_a)
27+
end
28+
29+
def test_more_8bits
30+
values = [true] * 8 + [nil, false]
31+
assert_equal(values,
32+
ArrowFormat::BooleanArray.new(values).to_a)
33+
end
34+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
class TestDenseBitmapBuilder < Test::Unit::TestCase
19+
def setup
20+
@builder = ArrowFormat::DenseBitmapBuilder.new
21+
end
22+
23+
def test_empty
24+
assert_equal(IO::Buffer.for(""), @builder.finish)
25+
end
26+
27+
def test_1byte
28+
8.times do |i|
29+
@builder.append(i.odd?)
30+
end
31+
buffer = [0b10101010].pack("C") + "\x00" * 63
32+
assert_equal(IO::Buffer.for(buffer),
33+
@builder.finish)
34+
end
35+
36+
def test_9bits
37+
8.times do |i|
38+
@builder.append(i.odd?)
39+
end
40+
@builder.append(true)
41+
buffer = [0b10101010].pack("C") + [0b00000001].pack("C") + "\x00" * 62
42+
assert_equal(IO::Buffer.for(buffer),
43+
@builder.finish)
44+
end
45+
end
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
class TestSparseBitmapBuilder < Test::Unit::TestCase
19+
def setup
20+
@builder = ArrowFormat::SparseBitmapBuilder.new(true)
21+
end
22+
23+
def test_empty
24+
buffer = [0b00000001].pack("C") + "\x00" * 63
25+
assert_equal(IO::Buffer.for(buffer),
26+
@builder.finish(1))
27+
end
28+
29+
def test_set_0bit
30+
@builder.set(0)
31+
buffer = [0b11111110].pack("C") + "\x00" * 63
32+
assert_equal(IO::Buffer.for(buffer),
33+
@builder.finish(8))
34+
end
35+
36+
def test_set_multiple_bytes
37+
@builder.set(15)
38+
@builder.set(31)
39+
buffer = [0b11111111].pack("C")
40+
buffer += [0b01111111].pack("C")
41+
buffer += [0b11111111].pack("C")
42+
buffer += [0b01111111].pack("C")
43+
buffer += [0b11111111].pack("C")
44+
buffer += "\x00" * 59
45+
assert_equal(IO::Buffer.for(buffer),
46+
@builder.finish(40))
47+
end
48+
end

0 commit comments

Comments
 (0)