carray 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/meld_reduce.rb
CHANGED
|
@@ -36,7 +36,7 @@ class CAMeld
|
|
|
36
36
|
|
|
37
37
|
def sum(*args, **kw)
|
|
38
38
|
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
39
|
-
axis = kw
|
|
39
|
+
axis = meld_fast_path_axis(kw)
|
|
40
40
|
if axis.nil?
|
|
41
41
|
parents.map(&:sum).inject(:+)
|
|
42
42
|
elsif meld_axis_normalized?(axis)
|
|
@@ -52,7 +52,10 @@ class CAMeld
|
|
|
52
52
|
# @return [CArray, Numeric]
|
|
53
53
|
def mean(*args, **kw)
|
|
54
54
|
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
55
|
-
|
|
55
|
+
# The combine divides in Float; an object array's mean stays exact
|
|
56
|
+
# (Rational, Integer division) on the core path.
|
|
57
|
+
return super if object?
|
|
58
|
+
axis = meld_fast_path_axis(kw)
|
|
56
59
|
# `mean` has no identity: an empty set of contributors has no defined
|
|
57
60
|
# mean (0/0 = NaN). Punt to super, which returns UNDEF: a reduction
|
|
58
61
|
# with no contributors yields the identity where one exists and UNDEF
|
|
@@ -78,12 +81,12 @@ class CAMeld
|
|
|
78
81
|
# @return [CArray, Numeric]
|
|
79
82
|
def min(*args, **kw)
|
|
80
83
|
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
81
|
-
axis = kw
|
|
84
|
+
axis = meld_fast_path_axis(kw)
|
|
82
85
|
# `min` has no identity — empty parent list / all-empty parents punt to
|
|
83
86
|
# super for UNDEF.
|
|
84
87
|
if axis.nil?
|
|
85
88
|
return super if parents.all? { |p| p.elements == 0 }
|
|
86
|
-
|
|
89
|
+
meld_combine_flat(:min)
|
|
87
90
|
elsif meld_axis_normalized?(axis)
|
|
88
91
|
return super if parents.all? { |p| p.dim[meld_axis] == 0 }
|
|
89
92
|
nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
|
|
@@ -99,10 +102,10 @@ class CAMeld
|
|
|
99
102
|
# @return [CArray, Numeric]
|
|
100
103
|
def max(*args, **kw)
|
|
101
104
|
return super unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
102
|
-
axis = kw
|
|
105
|
+
axis = meld_fast_path_axis(kw)
|
|
103
106
|
if axis.nil?
|
|
104
107
|
return super if parents.all? { |p| p.elements == 0 }
|
|
105
|
-
|
|
108
|
+
meld_combine_flat(:max)
|
|
106
109
|
elsif meld_axis_normalized?(axis)
|
|
107
110
|
return super if parents.all? { |p| p.dim[meld_axis] == 0 }
|
|
108
111
|
nonempty = parents.reject { |p| p.dim[meld_axis] == 0 }
|
|
@@ -168,6 +171,9 @@ class CAMeld
|
|
|
168
171
|
# more elaborate (min_count / fill_value / keep_axis / multi-axis /
|
|
169
172
|
# mask propagation) punts to super.
|
|
170
173
|
def meld_reduce_fast_path_ok?(kw)
|
|
174
|
+
return false unless CArray.__fast_paths_enabled__
|
|
175
|
+
# The mask of a meld is a meld built in C, with no parents list.
|
|
176
|
+
return false unless parents
|
|
171
177
|
return false unless (kw.keys - [:axis]).empty?
|
|
172
178
|
axis = kw[:axis]
|
|
173
179
|
unless axis.nil?
|
|
@@ -177,9 +183,30 @@ class CAMeld
|
|
|
177
183
|
end
|
|
178
184
|
return false if has_mask?
|
|
179
185
|
parents.each { |p| return false if p.has_mask? }
|
|
186
|
+
# A meld of Face arrays keeps its parents in their Face; the per-parent
|
|
187
|
+
# calls would reach the Face's own reductions, which answer in the Face's
|
|
188
|
+
# terms (or refuse), not in storage.
|
|
189
|
+
parents.each { |p| return false if p.face? }
|
|
180
190
|
true
|
|
181
191
|
end
|
|
182
192
|
|
|
193
|
+
# The axis a fast path reduces along. A 1-D meld has only the meld axis,
|
|
194
|
+
# and reducing it is reducing the whole array: the answer is a scalar, as
|
|
195
|
+
# for the flat call.
|
|
196
|
+
def meld_fast_path_axis(kw)
|
|
197
|
+
ndim == 1 ? nil : kw[:axis]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# The per-parent answers of a whole-array min / max, combined by core's
|
|
201
|
+
# own min / max so NaN loses the way it loses inside one parent (Ruby's
|
|
202
|
+
# Array#min would raise on it).
|
|
203
|
+
def meld_combine_flat(op)
|
|
204
|
+
vals = parents.reject { |p| p.elements == 0 }.map(&op)
|
|
205
|
+
part = CArray.new(data_type, [vals.size])
|
|
206
|
+
vals.each_with_index { |v, i| part[i] = v }
|
|
207
|
+
part.public_send(op)
|
|
208
|
+
end
|
|
209
|
+
|
|
183
210
|
# Non-meld-axis decompose: each parent reduces the same axis independently,
|
|
184
211
|
# then the K per-parent results are concatenated back along the meld axis.
|
|
185
212
|
# Materialised to entity (`.copy`) to match CArray#reduce entity semantics.
|
|
@@ -198,7 +225,13 @@ class CAMeld
|
|
|
198
225
|
|
|
199
226
|
def variance_family(args, kw, sample:, sqrt:)
|
|
200
227
|
return yield unless args.empty? && meld_reduce_fast_path_ok?(kw)
|
|
201
|
-
|
|
228
|
+
# The merge works in Float; an object array's variance stays exact
|
|
229
|
+
# (Rational, BigDecimal) on the core path.
|
|
230
|
+
return yield if object?
|
|
231
|
+
# The merge squares the difference of means, which for complex values is
|
|
232
|
+
# not |δ|²; the core path computes the variance of a complex array.
|
|
233
|
+
return yield if complex?
|
|
234
|
+
axis = meld_fast_path_axis(kw)
|
|
202
235
|
# Empty-parent / short-parent handling — Welford needs n >= 2 per parent
|
|
203
236
|
# to recover m2 from p.variance for sample, n >= 1 for variancep (n=1
|
|
204
237
|
# gives m2=0, fine). Any parent below its threshold punts to super,
|
|
@@ -85,8 +85,10 @@ class CArray
|
|
|
85
85
|
# b_on_grid = b_data.project(ib)
|
|
86
86
|
#
|
|
87
87
|
# `direction:` (`:round` / `:floor` / `:ceil`) and `tolerance:` are
|
|
88
|
-
# forwarded to {#locate_nearest_addr}:
|
|
89
|
-
#
|
|
88
|
+
# forwarded to {#locate_nearest_addr}: grid points with no match in the
|
|
89
|
+
# direction (before an array's first value under `:floor`, after its last
|
|
90
|
+
# under `:ceil`), and points whose match is farther than `tolerance`, come
|
|
91
|
+
# back masked.
|
|
90
92
|
#
|
|
91
93
|
# @param arrays [Array<CArray>] one or more arrays (Array / Range coerced
|
|
92
94
|
# via `to_ca`).
|
|
@@ -94,8 +96,10 @@ class CArray
|
|
|
94
96
|
# `nil` uses the first array verbatim.
|
|
95
97
|
# @param direction [:round, :floor, :ceil] rounding rule for the nearest
|
|
96
98
|
# match.
|
|
97
|
-
# @param tolerance [Numeric, nil] maximum
|
|
98
|
-
# points are masked.
|
|
99
|
+
# @param tolerance [Numeric, String, CATimedelta::Element, nil] maximum
|
|
100
|
+
# accepted distance; farther grid points are masked. For time keys it
|
|
101
|
+
# is a duration (`"10 minutes"` or a CATimedelta value), as in
|
|
102
|
+
# {#locate_nearest_addr}. `nil` disables the check.
|
|
99
103
|
# @return [Array<CArray>] `[common, idx_0, idx_1, ...]`.
|
|
100
104
|
# @raise [ArgumentError] when no array is given (or `direction` is invalid,
|
|
101
105
|
# raised by {#locate_nearest_addr}).
|
|
@@ -38,7 +38,7 @@ class CArray
|
|
|
38
38
|
|
|
39
39
|
if elements.zero?
|
|
40
40
|
if weights
|
|
41
|
-
out = CArray.new(weights.data_type, [length])
|
|
41
|
+
out = CArray.new(weights.data_type, [length], bytes: weights.bytes)
|
|
42
42
|
else
|
|
43
43
|
out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
|
|
44
44
|
out = CArray.new(out_type, [length])
|
|
@@ -54,7 +54,7 @@ class CArray
|
|
|
54
54
|
# Every cell is masked: no labels to count, same result as an empty
|
|
55
55
|
# input (all-zero output of the requested minimum length).
|
|
56
56
|
if weights
|
|
57
|
-
out = CArray.new(weights.data_type, [length])
|
|
57
|
+
out = CArray.new(weights.data_type, [length], bytes: weights.bytes)
|
|
58
58
|
else
|
|
59
59
|
out_type = (length > 0xFFFFFFFF) ? CA_UINT64 : CA_UINT32
|
|
60
60
|
out = CArray.new(out_type, [length])
|
|
@@ -58,9 +58,12 @@ class CArray
|
|
|
58
58
|
# multiple of 8) are zero-filled. The byte order matches the packed-bit
|
|
59
59
|
# convention used by Apache Arrow validity bitmaps and PEP 3118 `?` /
|
|
60
60
|
# `_Bool` at the bit level.
|
|
61
|
+
# A masked cell has no bit to pack, so a receiver with masked cells is
|
|
62
|
+
# refused; pack the mask separately with {#validity_bits}.
|
|
61
63
|
# @return [CArray] uint8 CArray of shape `[ceil(n / 8)]`.
|
|
62
|
-
# @raise [ArgumentError] when the receiver is not 1-D
|
|
63
|
-
#
|
|
64
|
+
# @raise [ArgumentError] when the receiver is not 1-D, its `data_type`
|
|
65
|
+
# is not one of `CA_BOOLEAN` / `CA_UINT8` / `CA_INT8`, or it has
|
|
66
|
+
# masked cells.
|
|
64
67
|
def pack_bits
|
|
65
68
|
unless data_type == CA_BOOLEAN || data_type == CA_UINT8 || data_type == CA_INT8
|
|
66
69
|
raise ArgumentError,
|
|
@@ -71,7 +74,15 @@ class CArray
|
|
|
71
74
|
n_bytes = (n + 7) / 8
|
|
72
75
|
packed = CArray.uint8(n_bytes) { 0 }
|
|
73
76
|
return packed if n == 0
|
|
74
|
-
|
|
77
|
+
source = self
|
|
78
|
+
if has_mask?
|
|
79
|
+
if count_masked > 0
|
|
80
|
+
raise ArgumentError,
|
|
81
|
+
"pack_bits: masked cells have no bit to pack; pack the mask with validity_bits"
|
|
82
|
+
end
|
|
83
|
+
source = value
|
|
84
|
+
end
|
|
85
|
+
packed.bitarray.reshape(-1)[0..n-1] = source
|
|
75
86
|
packed
|
|
76
87
|
end
|
|
77
88
|
|
|
@@ -75,7 +75,8 @@ class CArray
|
|
|
75
75
|
# Use `CArray.meld` for the uniform-shape view-default counterpart.
|
|
76
76
|
#
|
|
77
77
|
# @param list [Array<CArray>] pieces to concatenate.
|
|
78
|
-
# @param axis [Integer] axis to concatenate along
|
|
78
|
+
# @param axis [Integer, nil] axis to concatenate along; `nil` is the
|
|
79
|
+
# same as `0`.
|
|
79
80
|
# @param data_type [Symbol, Integer, nil] result `data_type`;
|
|
80
81
|
# inferred via `result_type` when `nil`.
|
|
81
82
|
# @return [CArray] fresh CArray with per-piece `axis` sizes summed.
|
|
@@ -83,7 +84,8 @@ class CArray
|
|
|
83
84
|
# inconsistent.
|
|
84
85
|
def self.concatenate (list, axis: 0, data_type: nil)
|
|
85
86
|
raise ArgumentError, "concatenate: list must not be empty" if list.empty?
|
|
86
|
-
|
|
87
|
+
axis = 0 if axis.nil?
|
|
88
|
+
__ragged_paste(list, [list.size], axis, data_type, "concatenate")
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
# @overload concatenate(*others, axis: 0, data_type: nil)
|
|
@@ -128,7 +130,7 @@ class CArray
|
|
|
128
130
|
raise ArgumentError,
|
|
129
131
|
"mosaic: tdim product (#{expected}) must equal list size (#{list.size})"
|
|
130
132
|
end
|
|
131
|
-
__ragged_paste(list, tdim, axis, data_type)
|
|
133
|
+
__ragged_paste(list, tdim, axis, data_type, "mosaic")
|
|
132
134
|
end
|
|
133
135
|
|
|
134
136
|
# Shared eager paste-loop helper (= ex-`combine` paste implementation,
|
|
@@ -136,7 +138,7 @@ class CArray
|
|
|
136
138
|
# shape obtained by tiling `list` over `tdim` starting at `axis`, with
|
|
137
139
|
# per-piece dim sizes summed along each tile axis (= block-matrix
|
|
138
140
|
# consistency required across rows / columns).
|
|
139
|
-
def self.__ragged_paste (list, tdim, axis, data_type)
|
|
141
|
+
def self.__ragged_paste (list, tdim, axis, data_type, name)
|
|
140
142
|
list = CArray.promote_list(list, data_type: data_type)
|
|
141
143
|
# promote_list has already enforced homogeneity (= common data_type, and
|
|
142
144
|
# for Face elements: same Face class + portable + state-compatible). So,
|
|
@@ -144,12 +146,16 @@ class CArray
|
|
|
144
146
|
# result with face_lift, rather than special-casing data_class / DATA_SIZE
|
|
145
147
|
# here (= which only ever handled CARecord and silently corrupted plain
|
|
146
148
|
# fixlen / numeric Faces by leaving bytes nil).
|
|
147
|
-
|
|
149
|
+
# The Face is re-wrapped from the widest piece: a fixlen Face carries
|
|
150
|
+
# its cell width, and the result is as wide as its widest piece.
|
|
151
|
+
face_parent = list[0].face? ? list.max_by(&:bytes) : nil
|
|
148
152
|
if face_parent
|
|
149
|
-
list = list.map { |x|
|
|
153
|
+
list = list.map { |x| x.__send__(:__face_storage__) }
|
|
150
154
|
end
|
|
151
155
|
data_type = list[0].data_type
|
|
152
|
-
|
|
156
|
+
# Fixlen pieces of different widths join at the widest, so no cell is
|
|
157
|
+
# cut; each narrower piece is padded with NUL as it is pasted.
|
|
158
|
+
bytes = (data_type == :fixlen) ? list.map(&:bytes).max : nil
|
|
153
159
|
# promote_list guarantees a non-empty list of CArrays, so the reference
|
|
154
160
|
# shape is simply the first element (a CScalar carries shape [1], so a
|
|
155
161
|
# leading scalar is fine; the scalar-expansion pass below broadcasts it).
|
|
@@ -157,7 +163,7 @@ class CArray
|
|
|
157
163
|
dim = ref.shape
|
|
158
164
|
ndim = ref.ndim
|
|
159
165
|
tndim = tdim.size
|
|
160
|
-
axis = CArray.normalize_axis(axis, ndim - tndim + 1,
|
|
166
|
+
axis = CArray.normalize_axis(axis, ndim - tndim + 1, name)
|
|
161
167
|
|
|
162
168
|
list = list.map do |x|
|
|
163
169
|
if x.scalar?
|
|
@@ -190,7 +196,7 @@ class CArray
|
|
|
190
196
|
block.each_with_index do |item, *tidx|
|
|
191
197
|
unless item.ndim == ndim
|
|
192
198
|
raise ArgumentError,
|
|
193
|
-
"
|
|
199
|
+
"#{name}: piece at tile #{tidx.inspect} has ndim " \
|
|
194
200
|
"#{item.ndim} (expected #{ndim})"
|
|
195
201
|
end
|
|
196
202
|
ndim.times do |d|
|
|
@@ -199,7 +205,7 @@ class CArray
|
|
|
199
205
|
expected = on_tile ? tile_sizes[i][tidx[i]] : dim[d]
|
|
200
206
|
unless item.shape[d] == expected
|
|
201
207
|
raise ArgumentError,
|
|
202
|
-
"
|
|
208
|
+
"#{name}: piece at tile #{tidx.inspect} has " \
|
|
203
209
|
"#{on_tile ? 'tile' : 'non-tile'} axis #{d} size " \
|
|
204
210
|
"#{item.shape[d]} (expected #{expected}); pieces must agree " \
|
|
205
211
|
"on non-tile axes and be block-matrix consistent along tile axes"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# The value-hash discovery family (`unique`, `nunique`,
|
|
4
|
+
# `mask_duplicates`, ...) interns one **cell** at a time. `along:`
|
|
5
|
+
# widens the unit to a whole sub-array: the sub-arrays enumerated
|
|
6
|
+
# along one axis, each compared as a single value.
|
|
7
|
+
#
|
|
8
|
+
# Nothing new has to hash. `fz_hash`'s third key lane already interns
|
|
9
|
+
# a fixed-width block of bytes (FNV-1a, with a memcmp re-check), which
|
|
10
|
+
# is what a sub-array is once it sits contiguously; this method is the
|
|
11
|
+
# bridge to it, and the three things the bridge has to get right:
|
|
12
|
+
#
|
|
13
|
+
# - the sub-arrays have to be contiguous before their bytes mean
|
|
14
|
+
# anything, which a transpose or a strided view does not give;
|
|
15
|
+
# - byte equality is not the family's contract for floats, where
|
|
16
|
+
# every NaN is one value and -0.0 is +0.0;
|
|
17
|
+
# - masks fold for free -- a CARefer that covers several parent
|
|
18
|
+
# cells with one view cell OR-reduces their mask bits, so a
|
|
19
|
+
# sub-array holding a masked cell is a masked sub-array and the
|
|
20
|
+
# kernels skip it.
|
|
21
|
+
#
|
|
22
|
+
# Returns a 1-D fixlen array, one cell per sub-array, for the caller
|
|
23
|
+
# to run an ordinary cell-level discovery method over.
|
|
24
|
+
private def fibers_as_cells (along, caller_name)
|
|
25
|
+
if ndim < 2
|
|
26
|
+
raise ArgumentError,
|
|
27
|
+
"#{caller_name}: along: compares sub-arrays, and a #{ndim}-D array " \
|
|
28
|
+
"has none -- drop along: for the whole-array form"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if data_type == CA_OBJECT
|
|
32
|
+
raise ArgumentError,
|
|
33
|
+
"#{caller_name}: along: is not available for an object array -- " \
|
|
34
|
+
"its cells hold Ruby references, which would compare by identity " \
|
|
35
|
+
"rather than by value"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
axis = normalize_axis(along, caller_name)
|
|
39
|
+
|
|
40
|
+
# Bring the named axis to the front so each sub-array is one
|
|
41
|
+
# unbroken run of bytes, and take a copy: `refer` reinterprets a
|
|
42
|
+
# byte buffer, and a view whose element stride is not its element
|
|
43
|
+
# width is not one. The copy is also what makes the normalisation
|
|
44
|
+
# below safe to write.
|
|
45
|
+
order = [axis] + (0...ndim).to_a.reject { |i| i == axis }
|
|
46
|
+
block = (axis.zero? ? self : transpose(*order)).copy
|
|
47
|
+
|
|
48
|
+
# Two values the family calls equal are not equal byte for byte.
|
|
49
|
+
# Normalise them so the bytes say what the values mean.
|
|
50
|
+
if block.float?
|
|
51
|
+
block[:is_nan] = Float::NAN # every NaN is one value
|
|
52
|
+
block[:eq, 0.0] = 0.0 # -0.0 is +0.0
|
|
53
|
+
elsif block.complex?
|
|
54
|
+
[block.real, block.imag].each do |part|
|
|
55
|
+
part[:is_nan] = Float::NAN
|
|
56
|
+
part[:eq, 0.0] = 0.0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
count = shape[axis]
|
|
61
|
+
block.refer(:fixlen, [count], bytes: elements / count * bytes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# `axis:` and `along:` ask different questions of the same array, so
|
|
65
|
+
# answering both at once has no meaning.
|
|
66
|
+
private def reject_axis_with_along (axis, along, caller_name)
|
|
67
|
+
return unless axis and along
|
|
68
|
+
raise ArgumentError,
|
|
69
|
+
"#{caller_name}: axis: and along: cannot be given together -- " \
|
|
70
|
+
"axis: is about the values inside each sub-array, along: is about " \
|
|
71
|
+
"the sub-arrays themselves"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class CArray
|
|
2
|
+
|
|
3
|
+
# @overload factorize
|
|
4
|
+
# Returns `[codes, levels]` in one pass: `levels` is a 1-D CArray of
|
|
5
|
+
# the distinct values of `self` in first-appearance order — what
|
|
6
|
+
# {#unique} answers — and `codes` is an integer CArray of `self`'s
|
|
7
|
+
# shape where `levels[codes[i]]` is `self[i]`.
|
|
8
|
+
#
|
|
9
|
+
# This is the member of the value-hash discovery family ({#unique},
|
|
10
|
+
# {#value_counts}, {#mask_duplicates}, {#nunique}) that hands back
|
|
11
|
+
# the codes as storage. {#unique} answers with the vocabulary alone
|
|
12
|
+
# and {#categorize} wraps both in a {CACategorical} Face; a caller
|
|
13
|
+
# who wants the codes themselves — a position to scatter into, a key
|
|
14
|
+
# to group by, a dense renumbering of sparse keys — would otherwise
|
|
15
|
+
# pay a second pass or take the Face and its Ruby label list.
|
|
16
|
+
#
|
|
17
|
+
# `codes` takes the narrowest unsigned data type the vocabulary
|
|
18
|
+
# fits, reserving that type's top value as the exclusion sentinel.
|
|
19
|
+
# A cell that joins no category — a masked cell — is both masked and
|
|
20
|
+
# holds the sentinel, exactly as {CACategorical}'s storage is, so a
|
|
21
|
+
# consumer may read either.
|
|
22
|
+
#
|
|
23
|
+
# Distinctness is the family's hash-key judgement (see {#unique}):
|
|
24
|
+
# `==` for numeric with all NaN collapsed to one value and
|
|
25
|
+
# -0.0 / +0.0 the same value; `eql?` / `hash` for `CA_OBJECT` and
|
|
26
|
+
# `CA_FIXLEN`. Complex values compare by value, with NaN and -0.0
|
|
27
|
+
# treated the same way in each component.
|
|
28
|
+
#
|
|
29
|
+
# There is no `sort:` here, unlike {#unique} and {#value_counts}:
|
|
30
|
+
# the codes index the levels, so reordering the vocabulary would
|
|
31
|
+
# desync them. Take {#unique}`(sort: true)` where the codes are not
|
|
32
|
+
# wanted, or sort afterwards and carry the codes through the same
|
|
33
|
+
# permutation.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array(CArray, CArray)] `[codes, levels]`.
|
|
36
|
+
def factorize
|
|
37
|
+
# One linear pass through the shared value hash (C
|
|
38
|
+
# __factorize_appearance__). It writes the sentinel into the
|
|
39
|
+
# excluded cells but leaves the codes unmasked; masking them is what
|
|
40
|
+
# CACategorical.from_codes does on its way to the Face, and this
|
|
41
|
+
# surface owes the same, since it hands the storage out bare.
|
|
42
|
+
codes, levels = __factorize_appearance__
|
|
43
|
+
excluded = codes.eq(CACategorical::SENTINEL[codes.data_type])
|
|
44
|
+
if excluded.count(true) > 0
|
|
45
|
+
codes.mask = codes.has_mask? ? (codes.mask | excluded) : excluded
|
|
46
|
+
end
|
|
47
|
+
[codes, levels]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
@@ -64,7 +64,8 @@ class CArray
|
|
|
64
64
|
flat_addr, outer, rest = gather_nd_flat_addr(indices, "gather_nd")
|
|
65
65
|
out_shape = outer + rest
|
|
66
66
|
# flatten + 1-D fancy indexing -> CAMapping view -> materialise via .copy.
|
|
67
|
-
|
|
67
|
+
# A masked coordinate reads as UNDEF: project carries the address mask.
|
|
68
|
+
result = flat_addr.has_mask? ? flatten.project(flat_addr) : self.flatten[flat_addr].copy
|
|
68
69
|
out_shape.empty? ? result : result.reshape(*out_shape)
|
|
69
70
|
end
|
|
70
71
|
|
|
@@ -91,7 +92,12 @@ class CArray
|
|
|
91
92
|
# @raise [IndexError] when a coordinate is out of range on any axis.
|
|
92
93
|
def put_nd (indices, values)
|
|
93
94
|
flat_addr, _outer, _rest = gather_nd_flat_addr(indices, "put_nd")
|
|
94
|
-
|
|
95
|
+
if flat_addr.has_mask?
|
|
96
|
+
# A masked coordinate writes nothing: scatter_replace! skips it.
|
|
97
|
+
flatten.scatter_replace!(flat_addr, values)
|
|
98
|
+
else
|
|
99
|
+
self.flatten[flat_addr] = values
|
|
100
|
+
end
|
|
95
101
|
self
|
|
96
102
|
end
|
|
97
103
|
|
|
@@ -150,6 +156,11 @@ class CArray
|
|
|
150
156
|
flat_idx = (flat_idx_raw + dims) % dims
|
|
151
157
|
|
|
152
158
|
base = (flat_idx * strides).sum(axis: 1).int64.reshape(m) # (M,)
|
|
159
|
+
# sum skips a masked coordinate, which would leave its row pointing at
|
|
160
|
+
# the wrong cell: a row with any masked coordinate has no address.
|
|
161
|
+
if flat_idx_raw.has_mask?
|
|
162
|
+
base[flat_idx_raw.is_masked.any(axis: 1)] = UNDEF
|
|
163
|
+
end
|
|
153
164
|
|
|
154
165
|
flat_addr =
|
|
155
166
|
if rest.empty?
|
|
@@ -11,8 +11,8 @@ class CArray
|
|
|
11
11
|
# appends at the end) and a negative value counts from the end;
|
|
12
12
|
# `bsize[i]` must be non-negative. Inserted cells are filled by
|
|
13
13
|
# the block's return value, or left at the type's default when
|
|
14
|
-
# no block is given. Preserves fixlen bytes and Face identity
|
|
15
|
-
#
|
|
14
|
+
# no block is given. Preserves fixlen bytes and Face identity; the
|
|
15
|
+
# block returns a value of the array (a Time for a time array).
|
|
16
16
|
# The `offset` array is not mutated.
|
|
17
17
|
#
|
|
18
18
|
# @param offset [Array<Integer>] insertion offset per axis.
|
|
@@ -40,21 +40,16 @@ class CArray
|
|
|
40
40
|
# offset == dim (append) leaves nothing on the upper side to shift.
|
|
41
41
|
grids[i][offset[i]..-1].seq!(offset[i]+bsize[i]) if offset[i] < shape[i]
|
|
42
42
|
end
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
src = src.parent while src.face?
|
|
48
|
-
dt = src.data_type
|
|
49
|
-
bytes = (dt == :fixlen) ? src.bytes : nil
|
|
50
|
-
out = CArray.new(dt, newdim, bytes: bytes)
|
|
43
|
+
# The original cells are copied as storage, so a read-only Face inserts
|
|
44
|
+
# too; the block's value is written as a value of the array, which a
|
|
45
|
+
# read-only Face refuses.
|
|
46
|
+
out = __blank_like__(newdim)
|
|
51
47
|
if block_given?
|
|
52
|
-
sel =
|
|
53
|
-
sel[*grids] =
|
|
48
|
+
sel = CArray.boolean(*newdim) { true }
|
|
49
|
+
sel[*grids] = false
|
|
54
50
|
out[sel] = block.call
|
|
55
51
|
end
|
|
56
|
-
out[*grids] =
|
|
57
|
-
out = out.face_lift(face_parent) if face_parent
|
|
52
|
+
out.__send__(:__face_storage__)[*grids] = __face_storage__
|
|
58
53
|
return out
|
|
59
54
|
end
|
|
60
55
|
|
data/lib/carray/methods/is_in.rb
CHANGED
|
@@ -15,14 +15,29 @@ class CArray
|
|
|
15
15
|
# {CArray.result_type}), so membership is value-correct across
|
|
16
16
|
# data types (e.g. an int cell equals a float set element of the same
|
|
17
17
|
# value, and a fractional set element never truncates onto an int
|
|
18
|
-
# cell).
|
|
19
|
-
#
|
|
18
|
+
# cell). Integers of mixed sign are matched by value, as {#eq}
|
|
19
|
+
# compares them: `-1` in an int8 array never matches `255` in a uint8
|
|
20
|
+
# one, and the set operations answer such a pair in a type that holds
|
|
21
|
+
# both (int16 for uint8 with int8, object for uint64 with int64).
|
|
22
|
+
# Genuinely incompatible data types (e.g. numeric vs fixlen) raise.
|
|
20
23
|
#
|
|
21
24
|
# Membership is value-based and shares the distinctness of the
|
|
22
25
|
# value-hash discovery family ({#unique} / {#value_counts}):
|
|
23
26
|
# numeric follows `==` with all NaN collapsed to one value and
|
|
24
27
|
# -0.0 == +0.0; `CA_OBJECT` follows Ruby `hash` / `eql?` with Float
|
|
25
|
-
# NaN collapsed; `CA_FIXLEN` follows byte equality
|
|
28
|
+
# NaN collapsed; `CA_FIXLEN` follows byte equality over the whole
|
|
29
|
+
# cell, with a short String in the set padded out to the cell width
|
|
30
|
+
# (so a 5-byte array written from `"be"` is `is_in(["be"])`). A
|
|
31
|
+
# `CA_FIXLEN` set given as a CArray must already be of that width.
|
|
32
|
+
#
|
|
33
|
+
# An object array keeps Ruby's own split between its two kinds of
|
|
34
|
+
# equality. A lookup through a hash -- `is_in`, the set operations,
|
|
35
|
+
# {#locate_addr}, {#unique} and the rest of the discovery family --
|
|
36
|
+
# uses `eql?`, as `Array#uniq`, `Array#&` and `Set` do, so `1` and
|
|
37
|
+
# `1.0` are different values there. A cell-by-cell comparison --
|
|
38
|
+
# {#eq}, {#count}, {#search}, {#bsearch} -- uses `==`, as
|
|
39
|
+
# `Array#include?` does, so they are equal there. To match by value
|
|
40
|
+
# across Integer and Float, convert the array to a numeric type first.
|
|
26
41
|
#
|
|
27
42
|
# Masked cells of `values` do not enter the set. Masked cells of
|
|
28
43
|
# `self` stay masked in the result (membership is unknown), so
|
|
@@ -61,6 +76,7 @@ class CArray
|
|
|
61
76
|
def intersection (other, sort: false)
|
|
62
77
|
a, b = promote_value_set(other)
|
|
63
78
|
r = a.__send__(:__intersection__, b)
|
|
79
|
+
r = r.to_unit(unit) if !a.equal?(self) && respond_to?(:in_common_unit_with)
|
|
64
80
|
sort ? r.sort : r
|
|
65
81
|
end
|
|
66
82
|
|
|
@@ -75,13 +91,17 @@ class CArray
|
|
|
75
91
|
def difference (other, sort: false)
|
|
76
92
|
a, b = promote_value_set(other)
|
|
77
93
|
r = a.__send__(:__difference__, b)
|
|
94
|
+
r = r.to_unit(unit) if !a.equal?(self) && respond_to?(:in_common_unit_with)
|
|
78
95
|
sort ? r.sort : r
|
|
79
96
|
end
|
|
80
97
|
|
|
81
98
|
# @overload union(other, sort: false)
|
|
82
99
|
# Returns a 1-D CArray of the distinct values appearing in either
|
|
83
100
|
# `self` or `other`, in self-then-other first-appearance order.
|
|
84
|
-
# See {#intersection} for the shared semantics and options.
|
|
101
|
+
# See {#intersection} for the shared semantics and options. Two time
|
|
102
|
+
# arrays of different units give their union in the finer unit, which
|
|
103
|
+
# holds the values of both ({#intersection} and {#difference} answer in
|
|
104
|
+
# `self`'s unit, since their values are `self`'s).
|
|
85
105
|
#
|
|
86
106
|
# @param other [CArray, Array, Range] promoted with `self`.
|
|
87
107
|
# @param sort [Boolean] when true, return the values sorted ascending
|
|
@@ -110,13 +130,60 @@ class CArray
|
|
|
110
130
|
# operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
|
|
111
131
|
# the canonical type-coercion entry, so its format's data type drives the promote.
|
|
112
132
|
def promote_value_set (values)
|
|
133
|
+
# Two time arrays of different units meet in the finer one.
|
|
134
|
+
if values.is_a?(CArray) && values.class == self.class &&
|
|
135
|
+
respond_to?(:in_common_unit_with)
|
|
136
|
+
return in_common_unit_with(values)
|
|
137
|
+
end
|
|
138
|
+
values = values_for_lookup(values) if values.is_a?(CArray)
|
|
139
|
+
# Two fixed-width string arrays of different widths meet at the wider.
|
|
140
|
+
if values.is_a?(CArray) && data_type == CA_FIXLEN && values.data_type == CA_FIXLEN
|
|
141
|
+
return fixlen_widened_with(values)
|
|
142
|
+
end
|
|
143
|
+
# A Face that reconciles operands (CATime) reads a bare Array as values
|
|
144
|
+
# of its own kind -- instants written as Time / String / elements.
|
|
145
|
+
if values.is_a?(Array) && face? && respond_to?(:to_comparable)
|
|
146
|
+
return [self, to_comparable(values)]
|
|
147
|
+
end
|
|
148
|
+
# A single value (a Time, an element, a bare number) is a set of one,
|
|
149
|
+
# read the same way; a bare number is refused like any other operand
|
|
150
|
+
# that is not of its kind.
|
|
151
|
+
if !values.is_a?(CArray) && !values.is_a?(Range) && face? &&
|
|
152
|
+
respond_to?(:to_comparable)
|
|
153
|
+
return [self, to_comparable([values])]
|
|
154
|
+
end
|
|
155
|
+
# ...and so does an array of values that are not of its kind (an object
|
|
156
|
+
# column of Time values, a string column).
|
|
157
|
+
if values.is_a?(CArray) && respond_to?(:to_comparable) &&
|
|
158
|
+
!values.respond_to?(:to_comparable)
|
|
159
|
+
return [self, values_in_face(self, values) || to_comparable([])]
|
|
160
|
+
end
|
|
161
|
+
# The same from the other side: values of a time array looked for in an
|
|
162
|
+
# array that is not one.
|
|
163
|
+
if values.is_a?(CArray) && values.respond_to?(:to_comparable) &&
|
|
164
|
+
!respond_to?(:to_comparable)
|
|
165
|
+
return [values_in_face(values, self) || values.to_comparable([]), values]
|
|
166
|
+
end
|
|
167
|
+
# A Face whose storage is not of its surface type. When its cells are
|
|
168
|
+
# its values, the set meets it as it would meet an array of the storage
|
|
169
|
+
# type, and the Face stays while that type does not change. Any other
|
|
170
|
+
# Face leaves the set to the lookup, which brings it into the Face's
|
|
171
|
+
# space or refuses it.
|
|
172
|
+
if face? && (storage = __face_storage__).data_type != data_type
|
|
173
|
+
unless __comparable_storage__
|
|
174
|
+
set = values.is_a?(Range) ? values.to_a : values
|
|
175
|
+
return [self, set.is_a?(CArray) ? set : CArray.wrap_readonly(set)]
|
|
176
|
+
end
|
|
177
|
+
s, set = storage.send(:promote_value_set, values)
|
|
178
|
+
return [s.equal?(storage) ? self : s, set]
|
|
179
|
+
end
|
|
113
180
|
case values
|
|
114
181
|
when Array then return promote_elements(values)
|
|
115
182
|
when Range then return promote_elements(values.to_a)
|
|
116
183
|
end
|
|
117
184
|
set = values.is_a?(CArray) ? values : CArray.wrap_readonly(values)
|
|
118
185
|
return [self, set] if set.data_type == data_type # common fast path
|
|
119
|
-
t = CArray.result_type(self, set)
|
|
186
|
+
t = value_match_type(CArray.result_type(self, set), set.data_type)
|
|
120
187
|
[coerce_self(t), set.to_type(t)]
|
|
121
188
|
end
|
|
122
189
|
|
|
@@ -126,14 +193,71 @@ class CArray
|
|
|
126
193
|
# object / fixlen self the elements are values, not data type specifiers (a
|
|
127
194
|
# String is a value, not a type name), so build the set in self's data type.
|
|
128
195
|
def promote_elements (elems)
|
|
129
|
-
if data_type ==
|
|
196
|
+
if data_type == CA_FIXLEN
|
|
197
|
+
# A fixlen value is a cell-width blob, so the set has to be built at
|
|
198
|
+
# this array's width -- to_type(:fixlen) with no bytes: builds it at
|
|
199
|
+
# width 0, where every element is "" and the C guard then refuses the
|
|
200
|
+
# set for not matching self. Same rule the scalar operand of a
|
|
201
|
+
# comparison follows: a String standing in for a cell is padded to the
|
|
202
|
+
# cell's width.
|
|
203
|
+
[self, elems.to_ca.to_type(data_type, bytes: bytes)]
|
|
204
|
+
elsif data_type == CA_OBJECT
|
|
130
205
|
[self, elems.to_ca.to_type(data_type)]
|
|
131
206
|
else
|
|
132
207
|
t = CArray.result_type(self, *elems)
|
|
208
|
+
t = value_match_type(t, CA_INT64) if elems.any? { |v| v.is_a?(Integer) && v < 0 }
|
|
133
209
|
[coerce_self(t), elems.to_ca.to_type(t)]
|
|
134
210
|
end
|
|
135
211
|
end
|
|
136
212
|
|
|
213
|
+
# +other+ as a lookup against self reads it. A string array whose cells
|
|
214
|
+
# hold ranges into a pool of its own (CAConstString) is read by its
|
|
215
|
+
# strings, and a categorical by its labels, since their storage is not
|
|
216
|
+
# their values; anything else, or the same kind of array as self, is
|
|
217
|
+
# taken as it is. A record is left to be refused.
|
|
218
|
+
def values_for_lookup (other)
|
|
219
|
+
return other if other.send(:__orderable_storage__)
|
|
220
|
+
return other if face? && other.class == self.class
|
|
221
|
+
if other.respond_to?(:to_string)
|
|
222
|
+
other.to_string
|
|
223
|
+
elsif other.is_a?(CACategorical)
|
|
224
|
+
other.to_type(:object)
|
|
225
|
+
else
|
|
226
|
+
other
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# [self, other] at one cell width when both are fixed-width string arrays
|
|
231
|
+
# (CAFixlenString) of different widths: the narrower is padded with NUL to
|
|
232
|
+
# the wider, as concatenate joins them, so a string matches whatever width
|
|
233
|
+
# it is held at. Raw fixlen cells of different widths are values of
|
|
234
|
+
# different lengths, not padded strings, and are left as they are (the
|
|
235
|
+
# lookup refuses them, naming the widths).
|
|
236
|
+
def fixlen_widened_with (other)
|
|
237
|
+
return [self, other] if bytes == other.bytes
|
|
238
|
+
return [self, other] unless is_a?(CAFixlenString) && other.is_a?(CAFixlenString)
|
|
239
|
+
w = [bytes, other.bytes].max
|
|
240
|
+
[widen_fixlen_string(self, w), widen_fixlen_string(other, w)]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def widen_fixlen_string (x, w)
|
|
244
|
+
x.bytes == w ? x : x.to_fixlen_string(bytes: w)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# The values of +other+, which has no to_comparable, read as values of +face+'s
|
|
248
|
+
# kind (instants written as Time / Date / String for a time key), in an
|
|
249
|
+
# array of +other+'s shape; a masked cell stays masked. nil when every
|
|
250
|
+
# cell is masked: there is nothing to read.
|
|
251
|
+
private def values_in_face(face, other)
|
|
252
|
+
return face.to_comparable(other) if other.is_a?(Array)
|
|
253
|
+
vals = other.flatten.to_a
|
|
254
|
+
filler = vals.find { |v| !UNDEF.equal?(v) }
|
|
255
|
+
return nil if filler.equal?(nil) && vals.all? { |v| UNDEF.equal?(v) }
|
|
256
|
+
out = face.to_comparable(vals.map { |v| UNDEF.equal?(v) ? filler : v })
|
|
257
|
+
out[other.is_masked.flatten] = UNDEF if other.has_mask?
|
|
258
|
+
out.reshape(*other.shape)
|
|
259
|
+
end
|
|
260
|
+
|
|
137
261
|
def coerce_self (t)
|
|
138
262
|
CArray.result_type(self) == t ? self : to_type(t)
|
|
139
263
|
end
|