carray 3.0.2 → 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 +304 -0
- data/README.md +3 -4
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +225 -0
- data/ext/ca_axis_group.c +177 -99
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +66 -71
- 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 +663 -214
- data/ext/ca_kernel_iterator.h +231 -167
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +132 -22
- 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 +1 -0
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +96 -25
- data/ext/ca_obj_face.c +406 -64
- data/ext/ca_obj_face.h +92 -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 +21 -21
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +189 -107
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +543 -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 +47 -49
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -118
- data/ext/ca_obj_stride.c +195 -34
- data/ext/ca_obj_string.c +17 -18
- 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 +149 -27
- data/ext/ca_op_ipower.c +24 -11
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +102 -79
- data/ext/ca_sweep_engine.h +7 -20
- 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 +70 -27
- data/ext/carray_access.c +455 -211
- data/ext/carray_address_basis.c +177 -36
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +46 -14
- data/ext/carray_call_cfunc.c +0 -28
- data/ext/carray_cast.c +1444 -209
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +32 -11
- data/ext/carray_core.c +514 -122
- data/ext/carray_count.c +140 -87
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +569 -277
- 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 +385 -3
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +81 -65
- data/ext/carray_mask.c +682 -316
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +221 -76
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1089 -359
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +273 -239
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +170 -70
- data/ext/carray_sort.c +215 -218
- 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 +0 -2
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1076 -846
- data/ext/ruby_carray.c +7 -1
- 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 +19 -0
- data/lib/carray/autoload_method_extension.rb +6 -0
- data/lib/carray/axis_group.rb +174 -26
- data/lib/carray/basics.rb +91 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +118 -48
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +94 -26
- data/lib/carray/categorical_iterator.rb +333 -826
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +46 -0
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +589 -82
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +124 -25
- data/lib/carray/frame/group.rb +189 -63
- data/lib/carray/frame/io.rb +412 -72
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +37 -16
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +715 -48
- 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 +29 -11
- data/lib/carray/iterator.rb +14 -2
- data/lib/carray/lazy.rb +54 -30
- data/lib/carray/mask_gap_fill.rb +66 -52
- 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/factorize.rb +2 -2
- 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 +117 -4
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +177 -36
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/repeat.rb +1 -20
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +11 -2
- data/lib/carray/record_order.rb +208 -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 +51 -27
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +4 -3
- 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 +517 -103
- data/lib/carray/window_iterator.rb +127 -72
- data/lib/carray.rb +2 -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_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 +1 -2
- data/yard-stubs/carray_access.rb +5 -4
- data/yard-stubs/carray_broadcast.rb +1 -1
- 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 -3
- data/yard-stubs/carray_count.rb +9 -0
- data/yard-stubs/carray_lazy.rb +5 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +25 -13
- data/yard-stubs/carray_median_percentile.rb +10 -6
- data/yard-stubs/carray_order.rb +34 -13
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +2 -1
- data/yard-stubs/carray_sort.rb +43 -3
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +11 -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 -26005
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14862
- data/ext/carray_kernels_reduce_extreme.c +0 -17494
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32218
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
|
@@ -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"
|
|
@@ -23,8 +23,8 @@ class CArray
|
|
|
23
23
|
# Distinctness is the family's hash-key judgement (see {#unique}):
|
|
24
24
|
# `==` for numeric with all NaN collapsed to one value and
|
|
25
25
|
# -0.0 / +0.0 the same value; `eql?` / `hash` for `CA_OBJECT` and
|
|
26
|
-
# `CA_FIXLEN`. Complex
|
|
27
|
-
#
|
|
26
|
+
# `CA_FIXLEN`. Complex values compare by value, with NaN and -0.0
|
|
27
|
+
# treated the same way in each component.
|
|
28
28
|
#
|
|
29
29
|
# There is no `sort:` here, unlike {#unique} and {#value_counts}:
|
|
30
30
|
# the codes index the levels, so reordering the vocabulary would
|
|
@@ -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,8 +15,11 @@ 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}):
|
|
@@ -27,6 +30,15 @@ class CArray
|
|
|
27
30
|
# (so a 5-byte array written from `"be"` is `is_in(["be"])`). A
|
|
28
31
|
# `CA_FIXLEN` set given as a CArray must already be of that width.
|
|
29
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.
|
|
41
|
+
#
|
|
30
42
|
# Masked cells of `values` do not enter the set. Masked cells of
|
|
31
43
|
# `self` stay masked in the result (membership is unknown), so
|
|
32
44
|
# `is_in` propagates `self`'s mask like an element-wise comparison.
|
|
@@ -64,6 +76,7 @@ class CArray
|
|
|
64
76
|
def intersection (other, sort: false)
|
|
65
77
|
a, b = promote_value_set(other)
|
|
66
78
|
r = a.__send__(:__intersection__, b)
|
|
79
|
+
r = r.to_unit(unit) if !a.equal?(self) && respond_to?(:in_common_unit_with)
|
|
67
80
|
sort ? r.sort : r
|
|
68
81
|
end
|
|
69
82
|
|
|
@@ -78,13 +91,17 @@ class CArray
|
|
|
78
91
|
def difference (other, sort: false)
|
|
79
92
|
a, b = promote_value_set(other)
|
|
80
93
|
r = a.__send__(:__difference__, b)
|
|
94
|
+
r = r.to_unit(unit) if !a.equal?(self) && respond_to?(:in_common_unit_with)
|
|
81
95
|
sort ? r.sort : r
|
|
82
96
|
end
|
|
83
97
|
|
|
84
98
|
# @overload union(other, sort: false)
|
|
85
99
|
# Returns a 1-D CArray of the distinct values appearing in either
|
|
86
100
|
# `self` or `other`, in self-then-other first-appearance order.
|
|
87
|
-
# 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).
|
|
88
105
|
#
|
|
89
106
|
# @param other [CArray, Array, Range] promoted with `self`.
|
|
90
107
|
# @param sort [Boolean] when true, return the values sorted ascending
|
|
@@ -113,13 +130,60 @@ class CArray
|
|
|
113
130
|
# operand (Numo, a MemoryView producer, ...) comes in through wrap_readonly,
|
|
114
131
|
# the canonical type-coercion entry, so its format's data type drives the promote.
|
|
115
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
|
|
116
180
|
case values
|
|
117
181
|
when Array then return promote_elements(values)
|
|
118
182
|
when Range then return promote_elements(values.to_a)
|
|
119
183
|
end
|
|
120
184
|
set = values.is_a?(CArray) ? values : CArray.wrap_readonly(values)
|
|
121
185
|
return [self, set] if set.data_type == data_type # common fast path
|
|
122
|
-
t = CArray.result_type(self, set)
|
|
186
|
+
t = value_match_type(CArray.result_type(self, set), set.data_type)
|
|
123
187
|
[coerce_self(t), set.to_type(t)]
|
|
124
188
|
end
|
|
125
189
|
|
|
@@ -141,10 +205,59 @@ class CArray
|
|
|
141
205
|
[self, elems.to_ca.to_type(data_type)]
|
|
142
206
|
else
|
|
143
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 }
|
|
144
209
|
[coerce_self(t), elems.to_ca.to_type(t)]
|
|
145
210
|
end
|
|
146
211
|
end
|
|
147
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
|
+
|
|
148
261
|
def coerce_self (t)
|
|
149
262
|
CArray.result_type(self) == t ? self : to_type(t)
|
|
150
263
|
end
|
data/lib/carray/methods/join.rb
CHANGED
|
@@ -66,7 +66,7 @@ class CArray
|
|
|
66
66
|
t = transpose(*order)
|
|
67
67
|
end
|
|
68
68
|
inner = t.shape[-1]
|
|
69
|
-
outer_n = t.
|
|
69
|
+
outer_n = t.shape[0...-1].inject(1, :*) # 1 when ndim == 1
|
|
70
70
|
flat = t.reshape(outer_n, inner)
|
|
71
71
|
|
|
72
72
|
strings = Array.new(outer_n) { |i| flat[i, nil].to_a.join(sep_str) }
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "carray/methods/is_in" # fixlen_widened_with, values_in_face, values_for_lookup
|
|
2
|
+
|
|
1
3
|
class CArray
|
|
2
4
|
|
|
3
5
|
# @!group Sorting and searching
|
|
@@ -27,8 +29,13 @@ class CArray
|
|
|
27
29
|
# `self` and `ref` are compared at their common type, negotiated by
|
|
28
30
|
# `CArray.result_type` — a fractional query against an integer `ref`
|
|
29
31
|
# is compared at the promoted type rather than truncated, so `1.5` no
|
|
30
|
-
# longer matches `1
|
|
31
|
-
#
|
|
32
|
+
# longer matches `1`, and integers of mixed sign are matched by value
|
|
33
|
+
# (`-1` never matches `255`). Cross-family input (numeric against
|
|
34
|
+
# fixlen) raises.
|
|
35
|
+
#
|
|
36
|
+
# A time key (`CATime`) reads values that are not time arrays -- Time,
|
|
37
|
+
# Date or String objects, in an Array, an `:object` array or a string
|
|
38
|
+
# array -- as instants, on either side of the lookup, as {#is_in} does.
|
|
32
39
|
#
|
|
33
40
|
# Typical use is time-axis lookup: compute the address once against a
|
|
34
41
|
# reference axis, then reuse it to gather from many `ref`-shaped
|
|
@@ -41,15 +48,40 @@ class CArray
|
|
|
41
48
|
# numeric lane.
|
|
42
49
|
# @return [CArray] `:int64` flat addresses into `ref`, same shape as
|
|
43
50
|
# `self`; unmatched cells are masked.
|
|
44
|
-
# @raise [
|
|
51
|
+
# @raise [CArray::DataTypeError] when `self` and `ref` have no common data type.
|
|
45
52
|
def locate_addr (ref)
|
|
53
|
+
# A time key (any Face that reconciles operands) against values that do
|
|
54
|
+
# not -- Time, Date or String objects, a string column -- reads them as
|
|
55
|
+
# values of its kind, whichever side they are on.
|
|
56
|
+
if ref.is_a?(CArray) && ref.class == self.class &&
|
|
57
|
+
respond_to?(:in_common_unit_with) && ref.unit != unit
|
|
58
|
+
a, b = in_common_unit_with(ref)
|
|
59
|
+
return a.locate_addr(b)
|
|
60
|
+
end
|
|
61
|
+
if respond_to?(:to_comparable) && !ref.respond_to?(:to_comparable)
|
|
62
|
+
ref = values_in_face(self, ref)
|
|
63
|
+
return CArray.int64(*shape).fill(UNDEF) unless ref
|
|
64
|
+
elsif ref.respond_to?(:to_comparable) && !respond_to?(:to_comparable)
|
|
65
|
+
q = values_in_face(ref, self)
|
|
66
|
+
return CArray.int64(*shape).fill(UNDEF) unless q
|
|
67
|
+
return q.locate_addr(ref)
|
|
68
|
+
end
|
|
69
|
+
unless ref.is_a?(CArray) || ref.respond_to?(:to_ca)
|
|
70
|
+
raise TypeError, "locate_addr: the reference is an array of values (got #{ref.class})"
|
|
71
|
+
end
|
|
46
72
|
ref = ref.to_ca unless ref.is_a?(CArray)
|
|
73
|
+
ref = values_for_lookup(ref)
|
|
74
|
+
if data_type == CA_FIXLEN && ref.data_type == CA_FIXLEN && bytes != ref.bytes
|
|
75
|
+
a, b = fixlen_widened_with(ref)
|
|
76
|
+
return a.locate_addr(b) unless a.equal?(self) && b.equal?(ref)
|
|
77
|
+
end
|
|
47
78
|
# Put self and ref in a common lane via the single-source promotion rule
|
|
48
79
|
# (CArray.result_type), so a fractional query against an int ref is compared
|
|
49
80
|
# at the promoted type instead of truncating (1.5 no longer matches 1).
|
|
50
81
|
# to_type is elementwise and order-preserving, so the addresses stay valid
|
|
51
|
-
# indices into ref. result_type raises for cross-family input
|
|
52
|
-
|
|
82
|
+
# indices into ref. result_type raises for cross-family input; an integer
|
|
83
|
+
# pair of mixed sign is matched by value (see value_match_type).
|
|
84
|
+
t = value_match_type(CArray.result_type(self, ref), ref.data_type)
|
|
53
85
|
q = (data_type == t) ? self : to_type(t)
|
|
54
86
|
r = (ref.data_type == t) ? ref : ref.to_type(t)
|
|
55
87
|
q.send(:__locate_addr__, r)
|
|
@@ -57,16 +89,18 @@ class CArray
|
|
|
57
89
|
|
|
58
90
|
# @overload locate_nearest_addr(ref, direction: :round, tolerance: nil)
|
|
59
91
|
# Returns, for each element of `self`, the flat address into `ref` of
|
|
60
|
-
# the nearest reference value. Continuous sibling of {#locate_addr}
|
|
61
|
-
#
|
|
62
|
-
#
|
|
92
|
+
# the nearest reference value. Continuous sibling of {#locate_addr}:
|
|
93
|
+
# each cell is placed among the sorted reference values by comparing
|
|
94
|
+
# it with them, so an integer key is exact at any size and an infinite
|
|
95
|
+
# one keeps its place. `:round` takes the nearer neighbour, the upper
|
|
96
|
+
# one on a tie.
|
|
63
97
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# before
|
|
69
|
-
#
|
|
98
|
+
# Outside `ref`'s span the direction still decides: `:floor` matches
|
|
99
|
+
# a cell after the last value to that last value, `:ceil` matches a
|
|
100
|
+
# cell before the first value to that first value, and `:round`
|
|
101
|
+
# matches either to the nearer end. The other side has no match
|
|
102
|
+
# (`:floor` before the first value, `:ceil` after the last) and is
|
|
103
|
+
# masked. Use `tolerance:` to refuse matches that are too far away.
|
|
70
104
|
#
|
|
71
105
|
# `tolerance:` (default `nil`) sets a maximum accepted absolute
|
|
72
106
|
# distance between `self[i]` and its matched `ref` value. When
|
|
@@ -76,50 +110,157 @@ class CArray
|
|
|
76
110
|
#
|
|
77
111
|
# `ref` need not be given in ascending order: it is sorted internally
|
|
78
112
|
# and the returned addresses are mapped back to positions in `ref` as
|
|
79
|
-
# passed.
|
|
113
|
+
# passed. A masked or NaN `ref` value matches nothing. Two time keys
|
|
114
|
+
# of different units are compared in the finer of the two.
|
|
115
|
+
# With one value present in `ref`, that value is the match for
|
|
116
|
+
# every cell the direction allows (`:floor` at or after it, `:ceil`
|
|
117
|
+
# at or before it, `:round` all). With none, every cell is masked;
|
|
118
|
+
# an empty `self` gives an empty result. A masked or NaN cell of
|
|
119
|
+
# `self` is always masked.
|
|
80
120
|
#
|
|
81
121
|
# @param ref [CArray] 1-D reference grid to match against.
|
|
82
122
|
# @param direction [Symbol] `:round`, `:floor`, or `:ceil` — rounding
|
|
83
123
|
# applied to the fractional position.
|
|
84
|
-
# @param tolerance [Numeric, nil] maximum
|
|
85
|
-
# distance; cells beyond this are masked.
|
|
124
|
+
# @param tolerance [Numeric, String, CATimedelta::Element, nil] maximum
|
|
125
|
+
# accepted `|self - ref|` distance; cells beyond this are masked.
|
|
126
|
+
# For a time key it is a duration: a String such as `"10 minutes"` or
|
|
127
|
+
# a CATimedelta value (a bare number is refused, since its length
|
|
128
|
+
# would depend on the key's unit). `nil` disables the check.
|
|
86
129
|
# @return [CArray] `:int64` flat addresses into `ref`, same shape as
|
|
87
|
-
# `self`;
|
|
130
|
+
# `self`; cells with no match in the direction and beyond-tolerance
|
|
131
|
+
# cells are masked.
|
|
88
132
|
# @raise [ArgumentError] when `direction` is not one of the accepted
|
|
89
|
-
# symbols.
|
|
133
|
+
# symbols, or `tolerance` is not a duration for a time key.
|
|
90
134
|
def locate_nearest_addr (ref, direction: :round, tolerance: nil)
|
|
91
135
|
unless [:round, :floor, :ceil].include?(direction)
|
|
92
136
|
raise ArgumentError,
|
|
93
137
|
"locate_nearest_addr: direction must be :round / :floor / " \
|
|
94
138
|
":ceil (got #{direction.inspect})"
|
|
95
139
|
end
|
|
140
|
+
return CArray.int64(*shape) if elements == 0
|
|
141
|
+
# A masked reference value matches nothing, as in locate_addr: sorting
|
|
142
|
+
# puts the masked ones last, and only the others are searched. A NaN
|
|
143
|
+
# one has no place among the others either; it sorts after them.
|
|
96
144
|
ri = ref.sort_addr
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fill = CArray.float64(*shape)
|
|
106
|
-
fill[] = sec.nil? ? UNDEF : sec
|
|
107
|
-
sec = fill
|
|
145
|
+
ri = ri[0...ref.count_not_masked] if ref.has_mask?
|
|
146
|
+
if ref.float? && ri.elements > 0
|
|
147
|
+
ri = ri[0...(ri.elements - ref[ri].is_nan.count(true))]
|
|
148
|
+
end
|
|
149
|
+
if ri.elements == 0
|
|
150
|
+
out = CArray.int64(*shape)
|
|
151
|
+
out[] = UNDEF
|
|
152
|
+
return out
|
|
108
153
|
end
|
|
109
|
-
|
|
154
|
+
rs = ref[ri]
|
|
155
|
+
n = rs.elements
|
|
156
|
+
rk, qk = nearest_keys(rs, self)
|
|
157
|
+
# The position of each cell among the sorted references is counted, not
|
|
158
|
+
# interpolated: :floor takes the last reference at or below it, :ceil the
|
|
159
|
+
# first at or above it.
|
|
160
|
+
# Counting keeps an integer key exact past 2**53 and an infinite one in
|
|
161
|
+
# its place, where an interpolated position would not.
|
|
110
162
|
si = case direction
|
|
111
|
-
when :
|
|
112
|
-
when :
|
|
113
|
-
when :
|
|
114
|
-
end
|
|
163
|
+
when :floor then nearest_count_below(rk, qk, true) - 1
|
|
164
|
+
when :ceil then nearest_count_below(rk, qk, false)
|
|
165
|
+
when :round then nearest_round(rk, qk, nearest_count_below(rk, qk, false))
|
|
166
|
+
end
|
|
167
|
+
si[si.lt(0) | si.ge(n)] = UNDEF
|
|
168
|
+
si[qk.is_nan.strip_mask(false)] = UNDEF if qk.float?
|
|
169
|
+
si = si.reshape(*shape)
|
|
170
|
+
si[is_masked] = UNDEF if has_mask?
|
|
115
171
|
idx = ri.project(si)
|
|
116
172
|
if tolerance
|
|
117
173
|
dist = (ref.project(idx) - self).abs
|
|
118
|
-
idx[dist > tolerance] = UNDEF
|
|
174
|
+
idx[dist > nearest_tolerance(tolerance, dist)] = UNDEF
|
|
119
175
|
end
|
|
120
176
|
idx
|
|
121
177
|
end
|
|
122
178
|
|
|
179
|
+
# The keys locate_nearest_addr orders by: the storage of the sorted
|
|
180
|
+
# reference and of the query, in one numeric type. A Face key is first
|
|
181
|
+
# brought into the reference's space (two time keys of different units
|
|
182
|
+
# into the finer of the two, which holds both exactly); plain keys meet at
|
|
183
|
+
# the type CArray.result_type gives, an integer pair of mixed sign at one
|
|
184
|
+
# that holds both.
|
|
185
|
+
private def nearest_keys(rs, q)
|
|
186
|
+
if rs.face? || q.face?
|
|
187
|
+
if rs.respond_to?(:to_unit) && q.class == rs.class && q.unit != rs.unit
|
|
188
|
+
u = CATimeUnitAlgebra.finer(rs.unit, q.unit)
|
|
189
|
+
rs = rs.to_unit(u)
|
|
190
|
+
q = q.to_unit(u)
|
|
191
|
+
end
|
|
192
|
+
q = rs.__send__(:__reconcile_operand__, q, "locate_nearest_addr")
|
|
193
|
+
rs = rs.__send__(:__face_storage__)
|
|
194
|
+
end
|
|
195
|
+
t = value_match_type(CArray.result_type(q, rs), rs.data_type)
|
|
196
|
+
rk = (rs.data_type == t) ? rs : rs.to_type(t)
|
|
197
|
+
qk = (q.data_type == t) ? q : q.to_type(t)
|
|
198
|
+
[rk, qk.flatten]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# For each query key, how many of the sorted reference keys are below it
|
|
202
|
+
# (inclusive: at or below). A numeric key is binary-searched in C; an
|
|
203
|
+
# object key is sorted together with the references, a tie putting the
|
|
204
|
+
# reference first when it counts, and counted against the references met
|
|
205
|
+
# before it.
|
|
206
|
+
private def nearest_count_below(rk, qk, inclusive)
|
|
207
|
+
return rk.send(:__count_below__, qk, inclusive) unless rk.data_type == CA_OBJECT
|
|
208
|
+
n, m = rk.elements, qk.elements
|
|
209
|
+
keys = CArray.concatenate([rk, qk])
|
|
210
|
+
tag = CArray.int8(n + m)
|
|
211
|
+
tag[0...n] = inclusive ? 0 : 1
|
|
212
|
+
tag[n...n+m] = inclusive ? 1 : 0
|
|
213
|
+
perm = CArray.sort_addr(keys, tag)
|
|
214
|
+
seen = perm.lt(n).cumsum.int64
|
|
215
|
+
query = perm.ge(n)
|
|
216
|
+
count = CArray.int64(m)
|
|
217
|
+
count[perm[query] - n] = seen[query]
|
|
218
|
+
count
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# :round -- an exact match takes its first reference; otherwise the
|
|
222
|
+
# nearer of the two neighbours, the upper one on a tie. +lt+ counts the
|
|
223
|
+
# references below each key, so the upper neighbour is at lt and an exact
|
|
224
|
+
# match is the reference there.
|
|
225
|
+
private def nearest_round(rk, qk, lt)
|
|
226
|
+
lo = rk.project(lt - 1)
|
|
227
|
+
hi = rk.project(lt)
|
|
228
|
+
upper = (hi - qk).le(qk - lo).strip_mask(false)
|
|
229
|
+
upper[lo.is_masked] = true if lo.has_mask?
|
|
230
|
+
upper[hi.is_masked] = false if hi.has_mask?
|
|
231
|
+
upper[hi.eq(qk).strip_mask(false)] = true
|
|
232
|
+
si = lt - 1
|
|
233
|
+
si[upper] = lt[upper]
|
|
234
|
+
si
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# The tolerance in the units of the distance. A time key measures its
|
|
238
|
+
# distance as a CATimedelta, so its tolerance is a duration: a String in
|
|
239
|
+
# the spelling CArray.time takes for a unit ("10 minutes"), or a
|
|
240
|
+
# CATimedelta value. A bare number would mean a different length in each
|
|
241
|
+
# unit, so it is refused there; any other key takes a number.
|
|
242
|
+
private def nearest_tolerance(tolerance, dist)
|
|
243
|
+
unless dist.is_a?(CATimedelta)
|
|
244
|
+
if tolerance.is_a?(String)
|
|
245
|
+
raise ArgumentError,
|
|
246
|
+
"tolerance: #{tolerance.inspect} is a duration, but the key is " \
|
|
247
|
+
"#{data_type_name}; give a number"
|
|
248
|
+
end
|
|
249
|
+
return tolerance
|
|
250
|
+
end
|
|
251
|
+
case tolerance
|
|
252
|
+
when String
|
|
253
|
+
res = CATime::Resolution.parse(tolerance)
|
|
254
|
+
CATimedelta::Element.new(res.count, res.base)
|
|
255
|
+
when CATimedelta::Element
|
|
256
|
+
tolerance
|
|
257
|
+
else
|
|
258
|
+
raise ArgumentError,
|
|
259
|
+
"tolerance: a time key needs a duration such as \"10 minutes\" " \
|
|
260
|
+
"or a CATimedelta value (got #{tolerance.inspect})"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
123
264
|
# @!endgroup
|
|
124
265
|
|
|
125
266
|
end
|