carray 3.0.4 → 3.0.6
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 +438 -0
- data/README.md +1 -1
- data/carray.gemspec +3 -2
- data/ext/ca_array_pool.c +25 -1
- data/ext/ca_axis_dispatch.c +19 -20
- data/ext/ca_axis_group.c +1 -1
- data/ext/ca_categorical_iterator.c +34 -27
- data/ext/ca_compare.c +4 -4
- data/ext/ca_composite_dispatch.c +9 -9
- data/ext/ca_kernel_iterator.c +122 -237
- data/ext/ca_kernel_iterator.h +522 -337
- data/ext/ca_obj_array.c +69 -19
- data/ext/ca_obj_bincmp.c +10 -1
- data/ext/ca_obj_binop.c +11 -1
- data/ext/ca_obj_bitarray.c +5 -1
- data/ext/ca_obj_bitfield.c +60 -43
- data/ext/ca_obj_byte_swap.c +4 -1
- data/ext/ca_obj_const_string.c +15 -3
- data/ext/ca_obj_face.c +44 -1
- data/ext/ca_obj_fake.c +3 -1
- data/ext/ca_obj_field.c +13 -3
- data/ext/ca_obj_grid.c +219 -17
- data/ext/ca_obj_meld.c +69 -113
- data/ext/ca_obj_moncmp.c +9 -1
- data/ext/ca_obj_monop.c +86 -12
- data/ext/ca_obj_object.c +118 -32
- data/ext/ca_obj_record.c +3 -0
- data/ext/ca_obj_reduce.c +19 -1
- data/ext/ca_obj_refer.c +80 -26
- data/ext/ca_obj_remap.c +69 -10
- data/ext/ca_obj_roll.c +27 -6
- data/ext/ca_obj_select.c +38 -6
- data/ext/ca_obj_select_axis.c +1 -0
- data/ext/ca_obj_shift.c +32 -7
- data/ext/ca_obj_stack.c +311 -111
- data/ext/ca_obj_stride.c +159 -64
- data/ext/ca_obj_string.c +27 -2
- data/ext/ca_obj_tile.c +28 -7
- data/ext/ca_obj_transpose.c +8 -4
- data/ext/ca_obj_triop.c +12 -1
- data/ext/ca_obj_window.c +80 -50
- data/ext/ca_op_ipower.c +12 -12
- data/ext/ca_sort_kernels.h +6 -3
- data/ext/caframe_csv_reader.c +1 -1
- data/ext/carray.h +35 -1
- data/ext/carray_access.c +43 -67
- data/ext/carray_address_basis.c +134 -0
- data/ext/carray_attribute.c +32 -0
- data/ext/carray_broadcast.c +3 -4
- data/ext/carray_build_flags.h +1 -1
- data/ext/carray_cast.c +251 -93
- data/ext/carray_cast_func.rb +56 -26
- data/ext/carray_conversion.c +11 -7
- data/ext/carray_core.c +814 -72
- data/ext/carray_count.c +6 -0
- data/ext/carray_element.c +22 -13
- data/ext/carray_factorize.c +20 -20
- data/ext/carray_generate.c +8 -2
- data/ext/carray_histogram.c +26 -4
- data/ext/carray_index_classifier.c +4 -4
- data/ext/carray_internal.h +159 -12
- data/ext/carray_kernels_bincmp.c +2174 -170
- data/ext/carray_kernels_binop.c +25653 -5487
- data/ext/carray_kernels_init.c +8 -8
- data/ext/carray_kernels_moncmp.c +96 -96
- data/ext/carray_kernels_monop.c +12253 -5235
- data/ext/carray_kernels_reduce_aggregate.c +6150 -2423
- data/ext/carray_kernels_reduce_boolean.c +1533 -15
- data/ext/carray_kernels_reduce_cumulative.c +2442 -882
- data/ext/carray_kernels_reduce_extreme.c +5658 -1661
- data/ext/carray_kernels_reduce_variance.c +2534 -402
- data/ext/carray_kernels_scan.c +368 -368
- data/ext/carray_kernels_search.c +4551 -604
- data/ext/carray_kernels_sort.c +659 -751
- data/ext/carray_kernels_triop.c +114 -114
- data/ext/carray_key_windows.c +4 -0
- data/ext/carray_lazy.c +47 -0
- data/ext/carray_loop.c +1 -1
- data/ext/carray_mask.c +175 -161
- data/ext/carray_median_percentile.c +4 -4
- data/ext/carray_memory_view.c +15 -9
- data/ext/carray_operator.c +152 -49
- data/ext/carray_order.c +230 -10
- data/ext/carray_random.c +22 -19
- data/ext/carray_scatter.c +24 -7
- data/ext/carray_slab.c +128 -29
- data/ext/carray_sort.c +224 -16
- data/ext/carray_sort_kernel.c +5 -7
- data/ext/carray_struct.c +35 -43
- data/ext/carray_test.c +74 -19
- data/ext/carray_time_text.c +6 -4
- data/ext/carray_utils.c +63 -21
- data/ext/extconf.rb +11 -0
- data/ext/mkkernel.rb +546 -199
- data/ext/ruby_carray.c +4 -1
- data/ext/version.h +4 -4
- data/lib/carray/attribute.rb +42 -5
- data/lib/carray/autoload_carray.rb +3 -3
- data/lib/carray/autoload_method_extension.rb +5 -3
- data/lib/carray/axis_group.rb +13 -7
- data/lib/carray/basics.rb +28 -8
- data/lib/carray/block_iterator.rb +17 -3
- data/lib/carray/categorical.rb +35 -23
- data/lib/carray/categorical_iterator.rb +8 -8
- data/lib/carray/complex.rb +2 -2
- data/lib/carray/conditional.rb +3 -2
- data/lib/carray/const_string.rb +10 -0
- data/lib/carray/data_type_extension.rb +8 -1
- data/lib/carray/fixlen_string.rb +41 -2
- data/lib/carray/frame/column_verbs.rb +48 -16
- data/lib/carray/frame/concat.rb +17 -3
- data/lib/carray/frame/convert.rb +19 -0
- data/lib/carray/frame/csv_parser.rb +32 -32
- data/lib/carray/frame/describe.rb +4 -2
- data/lib/carray/frame/frame.rb +405 -85
- data/lib/carray/frame/group.rb +777 -64
- data/lib/carray/frame/io.rb +212 -72
- data/lib/carray/frame/join.rb +146 -37
- data/lib/carray/frame/records.rb +3 -3
- data/lib/carray/frame/reshape.rb +89 -11
- data/lib/carray/frame/sort.rb +57 -9
- data/lib/carray/frame/verbs.rb +384 -49
- data/lib/carray/frame/windows.rb +44 -6
- data/lib/carray/fuse_source.rb +31 -2
- data/lib/carray/inspect.rb +7 -2
- data/lib/carray/iterator.rb +32 -0
- data/lib/carray/lazy.rb +52 -8
- data/lib/carray/mask_gap_fill.rb +37 -17
- data/lib/carray/meld_reduce.rb +1 -1
- data/lib/carray/methods/choose.rb +3 -2
- data/lib/carray/methods/is_in.rb +8 -1
- data/lib/carray/methods/meshgrid.rb +3 -1
- data/lib/carray/runtime.rb +47 -8
- data/lib/carray/segment_iterator.rb +35 -16
- data/lib/carray/serialize.rb +53 -19
- data/lib/carray/stack.rb +33 -18
- data/lib/carray/string.rb +8 -0
- data/lib/carray/string_operation_extension.rb +13 -5
- data/lib/carray/struct_builder.rb +5 -3
- data/lib/carray/time.rb +112 -68
- data/lib/carray/window_iterator.rb +16 -4
- data/yard-stubs/ca_obj_array.rb +26 -1
- data/yard-stubs/ca_obj_transpose.rb +5 -4
- data/yard-stubs/carray_attribute.rb +3 -1
- data/yard-stubs/carray_cast.rb +10 -5
- data/yard-stubs/carray_conversion.rb +13 -3
- data/yard-stubs/carray_element.rb +7 -6
- data/yard-stubs/carray_mask.rb +28 -4
- data/yard-stubs/carray_math.rb +45 -20
- data/yard-stubs/carray_memory_view.rb +7 -0
- data/yard-stubs/carray_order.rb +100 -4
- data/yard-stubs/carray_scatter.rb +7 -5
- data/yard-stubs/carray_test.rb +7 -4
- metadata +18 -4
data/ext/ruby_carray.c
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
---------------------------------------------------------------------------- */
|
|
15
15
|
|
|
16
16
|
#include "carray.h"
|
|
17
|
+
#include "carray_internal.h"
|
|
17
18
|
#include "carray_build_flags.h"
|
|
18
19
|
#include "version.h"
|
|
19
20
|
|
|
@@ -313,7 +314,7 @@ Init_carray_ext (void)
|
|
|
313
314
|
Init_carray_sort(); /* sort / sort_copy / sort_addr / axis2addr */
|
|
314
315
|
Init_carray_count();
|
|
315
316
|
Init_carray_kernels(); /* mkkernel-generated: reductions + maps */
|
|
316
|
-
Init_carray_round(); /* round(ndigits)
|
|
317
|
+
Init_carray_round(); /* round(ndigits) / round!(ndigits) */
|
|
317
318
|
|
|
318
319
|
Init_carray_utils();
|
|
319
320
|
|
|
@@ -394,5 +395,7 @@ Init_carray_ext (void)
|
|
|
394
395
|
Init_carray_slab();
|
|
395
396
|
|
|
396
397
|
Init_carray_address_basis();
|
|
398
|
+
|
|
399
|
+
ca_obj_num_builtin = ca_obj_num;
|
|
397
400
|
}
|
|
398
401
|
|
data/ext/version.h
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#define CA_VERSION "3.0.
|
|
2
|
-
#define CA_VERSION_CODE
|
|
1
|
+
#define CA_VERSION "3.0.6"
|
|
2
|
+
#define CA_VERSION_CODE 30006
|
|
3
3
|
#define CA_VERSION_MAJOR 3
|
|
4
4
|
#define CA_VERSION_MINOR 0
|
|
5
|
-
#define CA_VERSION_TEENY
|
|
6
|
-
#define CA_VERSION_DATE "2026/10/
|
|
5
|
+
#define CA_VERSION_TEENY 6
|
|
6
|
+
#define CA_VERSION_DATE "2026/10/28"
|
data/lib/carray/attribute.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# ca.set_attr(:unit, "m/s") # => setter; writes to self's @attr
|
|
7
7
|
# ca.set_attrs(other.attrs) # => sets every key of a Hash at once
|
|
8
8
|
# ca.attrs # => frozen shallow clone of the full Hash
|
|
9
|
+
# ca.attrs_symkey # => the same, with Symbol keys (nested too)
|
|
9
10
|
# ca.has_attr? # => any attribute present?
|
|
10
11
|
# ca.has_attr?(:unit) # => specific key present?
|
|
11
12
|
#
|
|
@@ -33,14 +34,17 @@
|
|
|
33
34
|
# parent's cells, so the parent's attributes do not describe them.
|
|
34
35
|
# `a.lazy` itself (CALazyMarker) is a view of `a` and walks through,
|
|
35
36
|
# and so does a lazy type conversion (the cast lane `promote_list`
|
|
36
|
-
# and `wrap_readonly` build), for the reason `to_type` carries them
|
|
37
|
+
# and `wrap_readonly` build), for the reason `to_type` carries them,
|
|
38
|
+
# and `swap_bytes`: like `refer`, `bitarray` and `bitfield`, a view
|
|
39
|
+
# that reads the same cells another way carries them.
|
|
37
40
|
# A view of a Face shows the attributes of the Face it was taken
|
|
38
41
|
# from (the walk goes to that Face, which the view's own parent
|
|
39
42
|
# chain skips).
|
|
40
43
|
# A view of several arrays (CAStack / CAMeld, so `stack`, `meld` and
|
|
41
44
|
# `montage`) shows, below its own @attr, the keys every one of its
|
|
42
45
|
# arrays shows with the same value; a key they disagree on, or one
|
|
43
|
-
# some of them lack, is not shown.
|
|
46
|
+
# some of them lack, is not shown. An array with no cells is left out
|
|
47
|
+
# of that count, unless all of them are empty.
|
|
44
48
|
#
|
|
45
49
|
# Stored values are frozen copies, nested Arrays and Hashes included, so
|
|
46
50
|
# an attribute changes only through set_attr / set_attrs: a value read
|
|
@@ -58,7 +62,9 @@
|
|
|
58
62
|
# `categorize`, a CAFrame `cast` / `parse_to_time` / `to_time`) carries
|
|
59
63
|
# them. Arithmetic and reductions do not -- the result is a different
|
|
60
64
|
# quantity -- including those that pick a value (`first`, `last`, `min`,
|
|
61
|
-
# `max`, `unique`)
|
|
65
|
+
# `max`, `unique`) and the element-wise ones that keep the unit (`abs`,
|
|
66
|
+
# `round`, `clip`): an attribute does not say which operations leave it
|
|
67
|
+
# true, so none carries it. Set it on the result again. A CAFrame index built from a key column's values
|
|
62
68
|
# (group and pivot) shows the key column's attributes.
|
|
63
69
|
|
|
64
70
|
class CArray
|
|
@@ -157,6 +163,15 @@ class CArray
|
|
|
157
163
|
(merged || {}).freeze
|
|
158
164
|
end
|
|
159
165
|
|
|
166
|
+
# @overload attrs_symkey
|
|
167
|
+
# Returns {#attrs} with its keys as Symbols, frozen. The keys of
|
|
168
|
+
# Hashes nested in the values, inside Arrays too, become Symbols
|
|
169
|
+
# as well.
|
|
170
|
+
# @return [Hash{Symbol => Object}]
|
|
171
|
+
def attrs_symkey
|
|
172
|
+
return CArray.__send__(:attr_symbolize_keys, self.attrs)
|
|
173
|
+
end
|
|
174
|
+
|
|
160
175
|
# @overload has_attr?
|
|
161
176
|
# Returns whether `self` (or any parent view) has any attribute
|
|
162
177
|
# set.
|
|
@@ -235,7 +250,12 @@ class CArray
|
|
|
235
250
|
end
|
|
236
251
|
|
|
237
252
|
# The attributes every array in `list` shows with the same value.
|
|
253
|
+
# An array with no cells holds no values for the attributes to describe,
|
|
254
|
+
# so it has no say (unless every one is empty), as CAFrame leaves a frame
|
|
255
|
+
# with no rows out of a meld.
|
|
238
256
|
def self.attrs_shared_by (list)
|
|
257
|
+
with_cells = list.reject { |a| a.elements == 0 }
|
|
258
|
+
list = with_cells unless with_cells.empty?
|
|
239
259
|
first, *rest = list.map(&:attrs)
|
|
240
260
|
return {} if first.nil?
|
|
241
261
|
first.select { |k, v| rest.all? { |h| h.key?(k) && h[k] == v } }
|
|
@@ -243,7 +263,10 @@ class CArray
|
|
|
243
263
|
private_class_method :attrs_shared_by
|
|
244
264
|
|
|
245
265
|
def attr_computing_view? (ca)
|
|
246
|
-
|
|
266
|
+
if ca.is_a?(CAMonOp)
|
|
267
|
+
op = ca.__op_id__
|
|
268
|
+
return op < CAMonOp::CAST_BASE && op != CAMonOp::OP_BYTE_SWAP
|
|
269
|
+
end
|
|
247
270
|
ca.is_a?(CABinOp) || ca.is_a?(CATriOp) ||
|
|
248
271
|
ca.is_a?(CAMonCmp) || ca.is_a?(CABinCmp)
|
|
249
272
|
end
|
|
@@ -304,6 +327,20 @@ class CArray
|
|
|
304
327
|
v
|
|
305
328
|
end
|
|
306
329
|
end
|
|
307
|
-
|
|
330
|
+
# A stored value with the keys of every Hash in it as Symbols, frozen
|
|
331
|
+
# as the stored value is.
|
|
332
|
+
def self.attr_symbolize_keys (v)
|
|
333
|
+
case v
|
|
334
|
+
when Array then v.map { |e| attr_symbolize_keys(e) }.freeze
|
|
335
|
+
when Hash
|
|
336
|
+
v.each_with_object({}) do |(k, vv), h|
|
|
337
|
+
h[k.to_sym] = attr_symbolize_keys(vv)
|
|
338
|
+
end.freeze
|
|
339
|
+
else
|
|
340
|
+
v
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
private_class_method :attr_normalize_key, :attr_validate_value, :attr_coerce_value,
|
|
344
|
+
:attr_symbolize_keys
|
|
308
345
|
|
|
309
346
|
end
|
|
@@ -32,12 +32,12 @@ class CArray
|
|
|
32
32
|
autoload_method "marshal_load", serialize_rb
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# A view, a wrap and a scalar are marshalled through _dump / self._load,
|
|
35
|
+
# A view, a mask, a wrap and a scalar are marshalled through _dump / self._load,
|
|
36
36
|
# which return a new array: with marshal_dump Marshal records the class and
|
|
37
37
|
# loads into an empty instance of it, which the plain array the payload
|
|
38
38
|
# holds cannot become. A record array keeps marshal_dump, under which it
|
|
39
39
|
# already round-trips with its class.
|
|
40
|
-
[CAView, CAWrap, CScalar].each do |klass|
|
|
40
|
+
[CAView, CAWrap, CScalar, CArrayMask, CAObjectMask].each do |klass|
|
|
41
41
|
klass.class_eval do
|
|
42
42
|
undef_method :marshal_dump
|
|
43
43
|
autoload_method "_dump", "carray/serialize"
|
|
@@ -137,7 +137,7 @@ autoload :CAUnion, "carray/struct"
|
|
|
137
137
|
# ---- CAFrame (DataFrame over CArray columns) -------------------------------
|
|
138
138
|
#
|
|
139
139
|
# The frame is only reached through the CAFrame constant (CAFrame.new /
|
|
140
|
-
# .
|
|
140
|
+
# .read_csv / subclassing) or GroupedFrame (obtained from CAFrame#group_by,
|
|
141
141
|
# by which point CAFrame is already loaded). Both constants live in the same
|
|
142
142
|
# require bundle (carray/frame.rb), so autoloading either loads the whole
|
|
143
143
|
# frame. Nothing in the eager core references them, so it loads on first use.
|
|
@@ -31,9 +31,11 @@ module AutoloadMethodExtension
|
|
|
31
31
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
|
-
def autoload_define (target, name,
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
def autoload_define (target, name, library_given, spec_given)
|
|
35
|
+
# The stub reads these two, so they are never assigned again: Ruby 4.0
|
|
36
|
+
# refuses to make a Proc shareable when a variable it reads may be.
|
|
37
|
+
library = Ractor.make_shareable(library_given.to_s.dup)
|
|
38
|
+
original_spec = Ractor.make_shareable(spec_given.to_s.dup)
|
|
37
39
|
# Shareable, so the stub answers in any Ractor. Only the main Ractor
|
|
38
40
|
# loads the library: Ractors that require at the same time can deadlock
|
|
39
41
|
# (Ruby 3.4), so another Ractor is told what to load there instead.
|
data/lib/carray/axis_group.rb
CHANGED
|
@@ -409,7 +409,7 @@ class CAGroupIterator
|
|
|
409
409
|
# except for a band folded into the count, which has no route but the
|
|
410
410
|
# engine.
|
|
411
411
|
def core_counts? (kw)
|
|
412
|
-
return false if CArray.__fast_paths_enabled__
|
|
412
|
+
return false if CArray.__fast_paths_enabled__(:axis_group_count_kernel)
|
|
413
413
|
has_group, fused = AxisGroup.parse_axis(kw[:axis])
|
|
414
414
|
has_group && fused.empty?
|
|
415
415
|
end
|
|
@@ -494,8 +494,8 @@ class CAGroupIterator
|
|
|
494
494
|
# Weighted mean = Sum(v*w) / Sum(w), both over the combined present-set. The
|
|
495
495
|
# `value*0 + weights` denominator carries the same value|weight mask, so its
|
|
496
496
|
# group-sum is Sum(w) over exactly the cells value*weight used. An empty group
|
|
497
|
-
# (no present pair)
|
|
498
|
-
#
|
|
497
|
+
# (no present pair) and a group whose weights sum to zero are UNDEF, as
|
|
498
|
+
# CArray#wmean leaves both.
|
|
499
499
|
def wmean (weights, **kw)
|
|
500
500
|
has_group, = AxisGroup.parse_axis(kw[:axis])
|
|
501
501
|
return value.wmean(weights, **kw) unless has_group
|
|
@@ -508,7 +508,7 @@ class CAGroupIterator
|
|
|
508
508
|
# No present pair is UNDEF (an empty group), set on the denominator so
|
|
509
509
|
# the division never meets an empty group's 0 / 0.
|
|
510
510
|
den = den.copy
|
|
511
|
-
den[cnt.eq(0)] = UNDEF
|
|
511
|
+
den[cnt.eq(0).or(den.eq(0))] = UNDEF
|
|
512
512
|
# min_count: counts the present pairs, as the core wmean does.
|
|
513
513
|
pairs = regroup(prod, spec)
|
|
514
514
|
pairs.finish_reduction(:wmean, kw, num / den)
|
|
@@ -863,7 +863,7 @@ class CAGroupIterator
|
|
|
863
863
|
# pass. With the fast paths off it goes through the band loop instead,
|
|
864
864
|
# which then has one position.
|
|
865
865
|
def whole_block? (bslots)
|
|
866
|
-
bslots.empty? && CArray.__fast_paths_enabled__
|
|
866
|
+
bslots.empty? && CArray.__fast_paths_enabled__(:axis_group_whole_block)
|
|
867
867
|
end
|
|
868
868
|
|
|
869
869
|
# Iterate the band positions of a band-preserving grouping. For each band
|
|
@@ -923,9 +923,15 @@ class CAGroupIterator
|
|
|
923
923
|
# paths off (a development build run with CARRAY_GENERAL_PATH set) each
|
|
924
924
|
# group is reduced by the core instead -- except a band folded into the
|
|
925
925
|
# statistic (+fused+), which has no route but the kernel.
|
|
926
|
+
#
|
|
927
|
+
# With no band axis the core groups the whole value in one pass, which is
|
|
928
|
+
# faster than the kernel for every group size (1M cells on an M2 Max: 7.5
|
|
929
|
+
# to 12.5 ms against 15 ms), so the kernel is taken only with a band.
|
|
926
930
|
def kernel_reads_value? (fused = [])
|
|
927
931
|
return false if value.face? || ! (value.integer? || value.float?)
|
|
928
|
-
|
|
932
|
+
return true unless fused.empty?
|
|
933
|
+
return false if spec.slot_meta.none? { |m| m[:kind] == :band }
|
|
934
|
+
CArray.__fast_paths_enabled__(:axis_group_reduce_kernel)
|
|
929
935
|
end
|
|
930
936
|
|
|
931
937
|
# Whether the scan kernel gives the core's answer: it reads an integer,
|
|
@@ -933,7 +939,7 @@ class CAGroupIterator
|
|
|
933
939
|
# float64 (an object in object), cummax / cummin in the payload's data type
|
|
934
940
|
# and cumcount in int64 -- not the core's answer for a boolean's running sum.
|
|
935
941
|
def kernel_scans? (op)
|
|
936
|
-
return false unless CArray.__fast_paths_enabled__
|
|
942
|
+
return false unless CArray.__fast_paths_enabled__(:axis_group_scan_kernel)
|
|
937
943
|
return false if value.face? || value.complex?
|
|
938
944
|
return true if value.object? || op == :cumcount
|
|
939
945
|
core = CAIterator.core_probe(value).public_send(op).data_type
|
data/lib/carray/basics.rb
CHANGED
|
@@ -117,13 +117,15 @@ class CArray
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
# @overload drop_axis
|
|
120
|
-
# Returns a view of `self` with every size-1 axis dropped.
|
|
120
|
+
# Returns a view of `self` with every size-1 axis dropped. When every
|
|
121
|
+
# axis has size 1, one axis of size 1 is kept.
|
|
121
122
|
# @return [CArray] view with reduced `ndim`.
|
|
122
123
|
def drop_axis
|
|
123
124
|
if ndim == 1
|
|
124
|
-
return self[]
|
|
125
|
+
return scalar? ? self : self[]
|
|
125
126
|
else
|
|
126
127
|
newdim = shape.reject{|x| x == 1 }
|
|
128
|
+
newdim = [1] if newdim.empty? # every axis has size 1: keep one
|
|
127
129
|
return ( ndim != newdim.size ) ? reshape(*newdim) : self[]
|
|
128
130
|
end
|
|
129
131
|
end
|
|
@@ -189,7 +191,13 @@ class CArray
|
|
|
189
191
|
# @param src [CArray] source array.
|
|
190
192
|
# @return [self]
|
|
191
193
|
# @raise [ArgumentError] when `offset.length != self.ndim`.
|
|
194
|
+
# @raise [FrozenError] when `self` is frozen.
|
|
192
195
|
def paste (offset, src)
|
|
196
|
+
# The write goes through a window view, which is not frozen itself:
|
|
197
|
+
# refuse a frozen self here, as a write into it directly is refused.
|
|
198
|
+
if frozen?
|
|
199
|
+
raise FrozenError.new("can't modify frozen #{self.class}: #{inspect}", receiver: self)
|
|
200
|
+
end
|
|
193
201
|
raise ArgumentError, "offset length must equal ndim" if offset.length != ndim
|
|
194
202
|
# A zero-length source covers no cell, so there is nothing to write --
|
|
195
203
|
# and the empty range it would ask for is not a window CAWindow can
|
|
@@ -310,8 +318,7 @@ class CArray
|
|
|
310
318
|
# @overload clip(min, max = nil, fill_value = nil, lfill: nil, ufill: nil)
|
|
311
319
|
# Returns `self` with every element clamped to `[min, max]`.
|
|
312
320
|
#
|
|
313
|
-
# Either bound may be `nil` for a one-sided clip
|
|
314
|
-
# dispatches to the `pmax` / `pmin` binop kernels. When
|
|
321
|
+
# Either bound may be `nil` for a one-sided clip. When
|
|
315
322
|
# `fill_value` (or `lfill` / `ufill`) is given, out-of-range
|
|
316
323
|
# cells are replaced by the fill instead of clamped -- pass
|
|
317
324
|
# `UNDEF` to mask that end. `fill_value` is sugar for symmetric
|
|
@@ -320,6 +327,8 @@ class CArray
|
|
|
320
327
|
#
|
|
321
328
|
# `min` may not exceed `max` (in any cell, when a bound is an
|
|
322
329
|
# array); equal bounds give every unmasked cell that value.
|
|
330
|
+
# A NaN cell of `self` stays NaN, one-sided or two-sided (unlike
|
|
331
|
+
# `pmax` / `pmin`, which answer the other operand for it).
|
|
323
332
|
# A NaN in a bound array leaves that cell unbounded on that side,
|
|
324
333
|
# as `nil` does for the whole side. A bound is never UNDEF or
|
|
325
334
|
# masked: a missing bound is not read as either, so fill it first.
|
|
@@ -373,19 +382,30 @@ class CArray
|
|
|
373
382
|
|
|
374
383
|
if lfill.nil? && ufill.nil?
|
|
375
384
|
return clamp_between(min, max) if !min.nil? && !max.nil?
|
|
376
|
-
return
|
|
377
|
-
return
|
|
385
|
+
return clamp_below(min) if max.nil?
|
|
386
|
+
return clamp_above(max)
|
|
378
387
|
end
|
|
379
388
|
|
|
380
389
|
# A side without a fill still clamps.
|
|
381
390
|
out = self.copy
|
|
382
|
-
out = out.
|
|
383
|
-
out = out.
|
|
391
|
+
out = out.clamp_below(min) if !min.nil? && lfill.nil?
|
|
392
|
+
out = out.clamp_above(max) if !max.nil? && ufill.nil?
|
|
384
393
|
out[self.lt(min)] = lfill unless min.nil? || lfill.nil?
|
|
385
394
|
out[self.gt(max)] = ufill unless max.nil? || ufill.nil?
|
|
386
395
|
out
|
|
387
396
|
end
|
|
388
397
|
|
|
398
|
+
# A one-sided clip of a float array passes a NaN cell through, as the
|
|
399
|
+
# two-sided one does: the open side is a NaN bound, which leaves it
|
|
400
|
+
# unbounded. pmax / pmin would answer the bound for a NaN cell.
|
|
401
|
+
protected def clamp_below (min)
|
|
402
|
+
float? ? clamp_between(min, Float::NAN) : pmax(min)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
protected def clamp_above (max)
|
|
406
|
+
float? ? clamp_between(Float::NAN, max) : pmin(max)
|
|
407
|
+
end
|
|
408
|
+
|
|
389
409
|
# `contains` was retired in favour of {#is_in} (value-hash membership).
|
|
390
410
|
# `a.contains(v1, v2)` -> `a.is_in([v1, v2])`;
|
|
391
411
|
# `a.contains(v1, v2, axis: k)` -> `a.is_in([v1, v2]).any(axis: k)`.
|
|
@@ -74,6 +74,14 @@ class CABlockIterator < CAIterator
|
|
|
74
74
|
"blocks: expected #{source.ndim} tile sizes (one per axis), " \
|
|
75
75
|
"got #{blocks.size}"
|
|
76
76
|
end
|
|
77
|
+
# Each tile is read through a view with one axis per tile position and
|
|
78
|
+
# one per cell in the tile, twice the source's rank.
|
|
79
|
+
if 2 * source.ndim > CA_RANK_MAX
|
|
80
|
+
raise ArgumentError,
|
|
81
|
+
"blocks: a rank #{source.ndim} array would need #{2 * source.ndim} " \
|
|
82
|
+
"axes for its tiles, beyond the limit of #{CA_RANK_MAX}; " \
|
|
83
|
+
"blocks works up to rank #{CA_RANK_MAX / 2}"
|
|
84
|
+
end
|
|
77
85
|
|
|
78
86
|
@sndim = source.ndim
|
|
79
87
|
offsets = Array.new(@sndim, 0)
|
|
@@ -93,7 +101,9 @@ class CABlockIterator < CAIterator
|
|
|
93
101
|
# Absorb a leading offset with a zero-copy pre-slice, so the tile geometry
|
|
94
102
|
# below always starts at index 0.
|
|
95
103
|
@source =
|
|
96
|
-
if
|
|
104
|
+
if source.scalar?
|
|
105
|
+
source.flatten # a CScalar takes no index; its one-cell view does
|
|
106
|
+
elsif offsets.all?(&:zero?)
|
|
97
107
|
source
|
|
98
108
|
else
|
|
99
109
|
source[*offsets.map { |o| o..-1 }]
|
|
@@ -517,7 +527,7 @@ class CABlockIterator < CAIterator
|
|
|
517
527
|
sview = saddr[*strip_ranges].block_view(*tiles)
|
|
518
528
|
grid = (0...@sndim).map { |i| vview.shape[i] }
|
|
519
529
|
cells = tiles.inject(1) { |p, t| p * t }
|
|
520
|
-
order = vview.copy.reshape(*(grid + [cells])).
|
|
530
|
+
order = vview.copy.reshape(*(grid + [cells])).sort_index(axis: @sndim)
|
|
521
531
|
src_sorted = sview.reshape(*(grid + [cells])).take_along_axis(order, axis: @sndim)
|
|
522
532
|
out[*strip_ranges].block_view(*tiles)[] = src_sorted.reshape(*(grid + tiles))
|
|
523
533
|
end
|
|
@@ -817,7 +827,7 @@ class CABlockIterator < CAIterator
|
|
|
817
827
|
# A padded entity whose interior holds the source and whose partial-edge
|
|
818
828
|
# margin is masked (UNDEF), sized to exactly cover the ceil tile grid. When
|
|
819
829
|
# the source already divides evenly this is the source itself (no copy), so
|
|
820
|
-
# the tiles alias it.
|
|
830
|
+
# the tiles alias it. Kept for one call; feeds the iterate escape hatches.
|
|
821
831
|
def padded_source
|
|
822
832
|
@padded_source ||= begin
|
|
823
833
|
pshape = @sndim.times.map { |i| @shape[i] * @sizes[i] }
|
|
@@ -845,6 +855,8 @@ class CABlockIterator < CAIterator
|
|
|
845
855
|
def lag (*args, **keywords) = @source.__send__(:carry_attrs, super)
|
|
846
856
|
def strip_mask (**keywords) = @source.__send__(:carry_attrs, super)
|
|
847
857
|
end)
|
|
858
|
+
|
|
859
|
+
read_source_per_call :@padded_source, :@tile_grid_view
|
|
848
860
|
end
|
|
849
861
|
|
|
850
862
|
|
|
@@ -857,6 +869,8 @@ class CArray
|
|
|
857
869
|
# first for "valid" tiling.
|
|
858
870
|
# @param blocks [Array<Integer, Range>] per-axis tile sizes (or ranges).
|
|
859
871
|
# @return [CABlockIterator]
|
|
872
|
+
# @raise [ArgumentError] when `self` has a rank above `CA_RANK_MAX / 2`
|
|
873
|
+
# (8): the tiles are read through a view of twice the rank.
|
|
860
874
|
def blocks (*blocks)
|
|
861
875
|
CABlockIterator.new(self, *blocks)
|
|
862
876
|
end
|
data/lib/carray/categorical.rb
CHANGED
|
@@ -299,7 +299,7 @@ class CACategorical < CAObject
|
|
|
299
299
|
# `codes[i] = x` raises; use `codes.copy` for a mutable copy.
|
|
300
300
|
# @return [CArray]
|
|
301
301
|
def codes
|
|
302
|
-
|
|
302
|
+
face_storage
|
|
303
303
|
end
|
|
304
304
|
|
|
305
305
|
# Face hook: carry the vocabulary across lifted views (slice / reshape / …).
|
|
@@ -328,10 +328,17 @@ class CACategorical < CAObject
|
|
|
328
328
|
# Face hook: decode a per-cell code into its category label, through the one
|
|
329
329
|
# decode every code-to-label path shares (see #label_at).
|
|
330
330
|
def storage_to_element(raw)
|
|
331
|
-
code = raw.is_a?(String) ? raw.unpack1(UNPACK_FORMAT.fetch(
|
|
331
|
+
code = raw.is_a?(String) ? raw.unpack1(UNPACK_FORMAT.fetch(face_storage.data_type)) : raw
|
|
332
332
|
label_at(code)
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
+
# Face hook: a value written into a cell (a fill_value of shift and
|
|
336
|
+
# window) is a label, and the cell holds its code.
|
|
337
|
+
# @raise [ArgumentError] if the value is not one of the labels.
|
|
338
|
+
def element_to_storage (value)
|
|
339
|
+
code_for_fill(value)
|
|
340
|
+
end
|
|
341
|
+
|
|
335
342
|
# ---- category-space operations (by label, not code) -------------------
|
|
336
343
|
#
|
|
337
344
|
# The element value of a categorical IS its category, so these specialise
|
|
@@ -389,7 +396,14 @@ class CACategorical < CAObject
|
|
|
389
396
|
# (0 for an unknown label).
|
|
390
397
|
# @param label [Object] category to count.
|
|
391
398
|
# @return [Integer]
|
|
392
|
-
|
|
399
|
+
# @overload count
|
|
400
|
+
# Returns the number of cells in some category (not masked), as
|
|
401
|
+
# {CArray#count} with no value does.
|
|
402
|
+
# @return [Integer]
|
|
403
|
+
def count(*args)
|
|
404
|
+
return count_not_masked if args.empty?
|
|
405
|
+
raise ArgumentError, "count: wrong number of arguments (given #{args.size}, expected 0..1)" if args.size > 1
|
|
406
|
+
label = args[0]
|
|
393
407
|
code = @labels.index(label)
|
|
394
408
|
code ? codes.count(code) : 0
|
|
395
409
|
end
|
|
@@ -400,9 +414,9 @@ class CACategorical < CAObject
|
|
|
400
414
|
#
|
|
401
415
|
# Memoised: the codes are read-only (immutable storage), so the counts are a
|
|
402
416
|
# pure function of the categorical and stay valid for its lifetime. The same
|
|
403
|
-
# cached array backs {#reduceat_index} and {#
|
|
404
|
-
# over the same categorical pays the count once.
|
|
405
|
-
#
|
|
417
|
+
# cached array backs {#reduceat_index} and {#group_addr}, so a wide aggregate
|
|
418
|
+
# over the same categorical pays the count once. The returned array is shared,
|
|
419
|
+
# so it is frozen; take `.copy` for a scratch buffer. A derived-view /
|
|
406
420
|
# composite categorical caches too: read-only rides from the codes as a flag,
|
|
407
421
|
# not a Ruby freeze, so the object stays non-frozen and the memo ivar sticks.
|
|
408
422
|
# @overload category_sizes
|
|
@@ -415,8 +429,7 @@ class CACategorical < CAObject
|
|
|
415
429
|
bc = codes.bincount
|
|
416
430
|
out = CArray.new(bc.data_type, [@labels.size], bytes: bc.bytes) # new zero-fills
|
|
417
431
|
out[0...bc.elements] = bc if bc.elements > 0
|
|
418
|
-
@category_sizes = out
|
|
419
|
-
out
|
|
432
|
+
@category_sizes = out.freeze
|
|
420
433
|
end
|
|
421
434
|
|
|
422
435
|
# Back-compatible alias; {#category_sizes} is the canonical name (a histogram
|
|
@@ -543,7 +556,7 @@ class CACategorical < CAObject
|
|
|
543
556
|
# The code a fill value stands for: UNDEF stays UNDEF, a label is its code.
|
|
544
557
|
def code_for_fill (value)
|
|
545
558
|
return UNDEF if UNDEF.equal?(value)
|
|
546
|
-
code =
|
|
559
|
+
code = label_position(value)
|
|
547
560
|
raise ArgumentError, "#{value.inspect} is not one of the labels #{labels.inspect}" unless code
|
|
548
561
|
code
|
|
549
562
|
end
|
|
@@ -583,27 +596,27 @@ class CACategorical < CAObject
|
|
|
583
596
|
# two accessors are the building blocks for that other engine — lay the data
|
|
584
597
|
# out as category-contiguous blocks, then select per block.
|
|
585
598
|
#
|
|
586
|
-
# The grouping plan —
|
|
599
|
+
# The grouping plan — group_addr (the counting sort, the dominant cost) plus
|
|
587
600
|
# reduceat_index and category_sizes — depends only on the codes, which are
|
|
588
601
|
# read-only. It is therefore memoised (a pure function of the categorical): the
|
|
589
602
|
# first grouping access builds it and every later one reuses it. This is what
|
|
590
603
|
# lets a wide aggregate (many payload columns over one classifier) or a
|
|
591
604
|
# CACategoricalIterator run the counting sort once, not once per column /
|
|
592
605
|
# iterator. Only the plan is cached; the payload-dependent grouped copy
|
|
593
|
-
# (value.reshape(n)[
|
|
606
|
+
# (value.reshape(n)[group_addr]) is rebuilt per column (it varies with value).
|
|
594
607
|
|
|
595
|
-
# Force-build (and cache) the whole grouping plan up front —
|
|
608
|
+
# Force-build (and cache) the whole grouping plan up front — group_addr,
|
|
596
609
|
# reduceat_index, category_sizes — for prepare-ahead use before a batch of
|
|
597
610
|
# groupings. Lazy building already covers correctness; this is the explicit
|
|
598
611
|
# "pay the counting sort now" handle (e.g. right after `df.group_by(col)`).
|
|
599
612
|
# Returns self so it chains.
|
|
600
613
|
# @overload build_grouping
|
|
601
|
-
# Eagerly builds and caches the grouping plan (
|
|
614
|
+
# Eagerly builds and caches the grouping plan (group_addr / reduceat_index
|
|
602
615
|
# / category_sizes). Optional — the plan is built lazily on first grouping
|
|
603
616
|
# access — but useful to pay the counting sort once ahead of a batch.
|
|
604
617
|
# @return [self]
|
|
605
618
|
def build_grouping
|
|
606
|
-
|
|
619
|
+
group_addr # pulls category_sizes; reduceat_index shares category_sizes
|
|
607
620
|
reduceat_index
|
|
608
621
|
self
|
|
609
622
|
end
|
|
@@ -617,26 +630,25 @@ class CACategorical < CAObject
|
|
|
617
630
|
# order. The first {#category_sizes}.sum addresses are exactly the classified
|
|
618
631
|
# cells in category order — CACategoricalIterator's permutation. For an N-D
|
|
619
632
|
# categorical the addresses are into the raveled storage, so
|
|
620
|
-
# `value.reshape(elements)[cat.
|
|
633
|
+
# `value.reshape(elements)[cat.group_addr]` produces the contiguous blocks.
|
|
621
634
|
# Memoised (see the grouping-plan note above): the counting sort is the
|
|
622
635
|
# dominant grouping cost, so it is computed once and shared by every payload
|
|
623
636
|
# column and by CACategoricalIterator, and kept for the categorical's
|
|
624
|
-
# lifetime (an int64 per cell).
|
|
625
|
-
# @overload
|
|
637
|
+
# lifetime (an int64 per cell). The returned array is shared, so it is frozen.
|
|
638
|
+
# @overload group_addr
|
|
626
639
|
# Returns a length-{#elements} integer CArray of flat storage
|
|
627
640
|
# addresses that order the cells by category (excluded cells last).
|
|
628
641
|
# @return [CArray]
|
|
629
|
-
def
|
|
630
|
-
return @
|
|
642
|
+
def group_addr
|
|
643
|
+
return @group_addr if @group_addr
|
|
631
644
|
out = codes.reshape(elements).__send__(:addresses_by_category,
|
|
632
645
|
reduceat_index.copy, @labels.size,
|
|
633
646
|
category_sizes.sum)
|
|
634
|
-
@
|
|
635
|
-
out
|
|
647
|
+
@group_addr = out.freeze
|
|
636
648
|
end
|
|
637
649
|
|
|
638
650
|
# Segment start offsets into the category-contiguous layout produced by
|
|
639
|
-
# {#
|
|
651
|
+
# {#group_addr}: reduceat_index[c] is where category c's block begins, so
|
|
640
652
|
# block c spans `reduceat_index[c] ... reduceat_index[c] + category_sizes[c]`.
|
|
641
653
|
# Empty categories repeat the following start (a zero-width block). Length k,
|
|
642
654
|
# aligned to #labels; pair with {#category_sizes} for the block lengths. On
|
|
@@ -649,7 +661,7 @@ class CACategorical < CAObject
|
|
|
649
661
|
return @reduceat_index if @reduceat_index
|
|
650
662
|
# Every boundary but the last; the copy makes it an entity the
|
|
651
663
|
# reduceat kernels can read directly.
|
|
652
|
-
@reduceat_index = CArray.segment_offsets(lengths: category_sizes)[0...-1].copy
|
|
664
|
+
@reduceat_index = CArray.segment_offsets(lengths: category_sizes)[0...-1].copy.freeze
|
|
653
665
|
end
|
|
654
666
|
|
|
655
667
|
# @overload categorize(labels: nil, sort_labels: false)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# grp.median # order statistics share the same surface
|
|
16
16
|
# grp.stddev
|
|
17
17
|
#
|
|
18
|
-
# This is the consumer of the
|
|
18
|
+
# This is the consumer of the group_addr / reduceat_index foundation on
|
|
19
19
|
# CACategorical. Order statistics (median / percentile / quantile) cannot be
|
|
20
20
|
# scattered — they need every value of a group held together — so the values
|
|
21
21
|
# are materialized into contiguous blocks once, and every reduction (monoid or
|
|
@@ -82,7 +82,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
82
82
|
# once and shared by every payload column and iterator — see
|
|
83
83
|
# CACategorical's grouping-plan note). The plan gives the segment STARTS
|
|
84
84
|
# (reduceat_index) and the group-major permutation (perm[slot] = source index
|
|
85
|
-
# at that grouped slot, = the valid prefix of
|
|
85
|
+
# at that grouped slot, = the valid prefix of group_addr); gathering value
|
|
86
86
|
# through perm is the only per-column work. Excluded cells (masked or
|
|
87
87
|
# out-of-vocabulary code) are absent from perm, so they never join a group;
|
|
88
88
|
# the value mask rides the gather into the grouped copy.
|
|
@@ -109,7 +109,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
109
109
|
@offsets = cat.reduceat_index # cached segment STARTS (int64[k])
|
|
110
110
|
@bounds = CArray.segment_offsets(lengths: @elements) # every boundary (int64[k+1])
|
|
111
111
|
# The group-major source indices (#perm) are taken from the cached
|
|
112
|
-
#
|
|
112
|
+
# group_addr when first asked for.
|
|
113
113
|
@nvalid = nvalid
|
|
114
114
|
@codes = cat.codes.reshape(cat.elements) # flat codes view (map re-walk / weights)
|
|
115
115
|
# Gather value into category-contiguous order via the cached permutation
|
|
@@ -295,7 +295,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
295
295
|
# @overload wmean(weights, axis:)
|
|
296
296
|
# Per-fiber per-category weighted mean along `axis`. Same weights-shape
|
|
297
297
|
# contract as {#wsum} (weights.shape == source.shape). Empty cell → MASKED;
|
|
298
|
-
# a present cell whose weights sum to zero →
|
|
298
|
+
# a present cell whose weights sum to zero → UNDEF, as CArray#wmean.
|
|
299
299
|
# @param weights [CArray]
|
|
300
300
|
# @param axis [Integer]
|
|
301
301
|
# @return [CArray]
|
|
@@ -316,12 +316,12 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
316
316
|
|
|
317
317
|
# Both are built with the grouping plan, and only on the flat path; the
|
|
318
318
|
# per-fiber form has neither and must not build a segment's.
|
|
319
|
-
# Group-major source indices = the valid prefix of the cached
|
|
319
|
+
# Group-major source indices = the valid prefix of the cached group_addr.
|
|
320
320
|
# With no classified cell the prefix is empty (and slicing a length-0
|
|
321
|
-
#
|
|
321
|
+
# group_addr would be out of range), so take the empty permutation directly.
|
|
322
322
|
def perm
|
|
323
323
|
if @nvalid && ! @perm
|
|
324
|
-
@perm = @nvalid > 0 ? @cat.
|
|
324
|
+
@perm = @nvalid > 0 ? @cat.group_addr[0...@nvalid] : CArray.int64(0)
|
|
325
325
|
end
|
|
326
326
|
@perm
|
|
327
327
|
end
|
|
@@ -551,7 +551,7 @@ class CACategoricalIterator < CASegmentIterator
|
|
|
551
551
|
codes = @cat.codes
|
|
552
552
|
# Classified along the axis, a group is taken by index; the general way,
|
|
553
553
|
# masking the rest of a copy, gives the same cells.
|
|
554
|
-
if codes.shape == [h.shape[axis]] && CArray.__fast_paths_enabled__
|
|
554
|
+
if codes.shape == [h.shape[axis]] && CArray.__fast_paths_enabled__(:categorical_group_by_index)
|
|
555
555
|
sel = none ? CArray.boolean(codes.elements) { false } : codes.eq(c)
|
|
556
556
|
sel = sel.strip_mask(false) if sel.has_mask?
|
|
557
557
|
idx = [nil] * h.ndim
|
data/lib/carray/complex.rb
CHANGED
|
@@ -74,7 +74,7 @@ class CArray
|
|
|
74
74
|
when CA_CMPLX128 then field(0, CA_FLOAT64)
|
|
75
75
|
end
|
|
76
76
|
else
|
|
77
|
-
self[]
|
|
77
|
+
scalar? ? self : self[]
|
|
78
78
|
end
|
|
79
79
|
frozen? ? view : (@__real__ = view)
|
|
80
80
|
end
|
|
@@ -100,7 +100,7 @@ class CArray
|
|
|
100
100
|
end
|
|
101
101
|
if not complex?
|
|
102
102
|
zero = template { 0 }
|
|
103
|
-
zero
|
|
103
|
+
zero.inherit_mask(self) if has_mask?
|
|
104
104
|
return zero
|
|
105
105
|
end
|
|
106
106
|
return @__imag__ if part_of_self?(@__imag__)
|
data/lib/carray/conditional.rb
CHANGED
|
@@ -219,8 +219,9 @@ class CArray
|
|
|
219
219
|
"conditional: cond must be a boolean CArray with same shape as self"
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
src = scalar? ? flatten : self # a CScalar takes no index
|
|
223
|
+
x_then = src[cond]
|
|
224
|
+
x_else = src[cond.not]
|
|
224
225
|
y_then = then_fn.call(x_then)
|
|
225
226
|
y_else = else_fn.call(x_else)
|
|
226
227
|
text = !data_type && CArray.send(:selection_text?, [y_then, y_else])
|
data/lib/carray/const_string.rb
CHANGED
|
@@ -92,6 +92,16 @@ class CAConstString
|
|
|
92
92
|
# override the L1 generics where present.
|
|
93
93
|
include CArray::StringOperationMixin
|
|
94
94
|
|
|
95
|
+
# Face hook: a value written into a cell (a fill_value of shift and
|
|
96
|
+
# window). A cell is a byte range of the shared buffer, which has no room
|
|
97
|
+
# for a String it does not hold, so only UNDEF can be written.
|
|
98
|
+
# @raise [ArgumentError] for any value but UNDEF.
|
|
99
|
+
def element_to_storage (value)
|
|
100
|
+
raise ArgumentError,
|
|
101
|
+
"a CAConstString can't hold a new String (its strings share one " \
|
|
102
|
+
"buffer); use UNDEF, or to_string first"
|
|
103
|
+
end
|
|
104
|
+
|
|
95
105
|
# ---- ordering family ---------------------------------------------------
|
|
96
106
|
#
|
|
97
107
|
# A storage cell is a `(start, end)` byte range into the shared buffer, so
|