carray 3.0.3 → 3.0.4

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.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -0
  3. data/README.md +3 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +1 -6
  7. data/ext/ca_axis_group.c +27 -27
  8. data/ext/ca_binop_dispatch.c +1 -29
  9. data/ext/ca_binop_dispatch.h +0 -1
  10. data/ext/ca_categorical_iterator.c +205 -134
  11. data/ext/ca_compare.c +7 -18
  12. data/ext/ca_group_iter.c +16 -17
  13. data/ext/ca_kernel_iterator.c +1234 -1994
  14. data/ext/ca_kernel_iterator.h +391 -437
  15. data/ext/ca_monop_dispatch.c +82 -59
  16. data/ext/ca_monop_dispatch.h +1 -0
  17. data/ext/ca_obj_array.c +96 -39
  18. data/ext/ca_obj_bincmp.c +24 -21
  19. data/ext/ca_obj_binop.c +15 -26
  20. data/ext/ca_obj_bitarray.c +3 -2
  21. data/ext/ca_obj_bitfield.c +5 -6
  22. data/ext/ca_obj_block.c +18 -9
  23. data/ext/ca_obj_byte_swap.c +5 -6
  24. data/ext/ca_obj_const_string.c +173 -19
  25. data/ext/ca_obj_face.c +238 -57
  26. data/ext/ca_obj_face.h +19 -0
  27. data/ext/ca_obj_fake.c +15 -29
  28. data/ext/ca_obj_farray.c +6 -3
  29. data/ext/ca_obj_field.c +17 -5
  30. data/ext/ca_obj_fixlen_string.c +2 -3
  31. data/ext/ca_obj_grid.c +406 -132
  32. data/ext/ca_obj_meld.c +172 -34
  33. data/ext/ca_obj_moncmp.c +23 -8
  34. data/ext/ca_obj_monop.c +67 -57
  35. data/ext/ca_obj_object.c +157 -51
  36. data/ext/ca_obj_record.c +12 -7
  37. data/ext/ca_obj_reduce.c +13 -8
  38. data/ext/ca_obj_refer.c +25 -27
  39. data/ext/ca_obj_remap.c +30 -9
  40. data/ext/ca_obj_repeat.c +9 -10
  41. data/ext/ca_obj_roll.c +6 -5
  42. data/ext/ca_obj_select.c +14 -9
  43. data/ext/ca_obj_select_axis.c +15 -47
  44. data/ext/ca_obj_shift.c +11 -1
  45. data/ext/ca_obj_stack.c +179 -34
  46. data/ext/ca_obj_stride.c +162 -115
  47. data/ext/ca_obj_string.c +71 -4
  48. data/ext/ca_obj_tile.c +6 -5
  49. data/ext/ca_obj_time.c +22 -6
  50. data/ext/ca_obj_timedelta.c +22 -6
  51. data/ext/ca_obj_transpose.c +5 -3
  52. data/ext/ca_obj_triop.c +23 -32
  53. data/ext/ca_obj_window.c +190 -26
  54. data/ext/ca_sweep_engine.c +2 -2
  55. data/ext/caframe_csv_reader.c +80 -1
  56. data/ext/carray.h +181 -75
  57. data/ext/carray_access.c +184 -96
  58. data/ext/carray_address_basis.c +343 -87
  59. data/ext/carray_attribute.c +89 -6
  60. data/ext/carray_bincount.c +10 -8
  61. data/ext/carray_broadcast.c +13 -8
  62. data/ext/carray_build_flags.h +3 -0
  63. data/ext/carray_call_cfunc.c +108 -0
  64. data/ext/carray_cast.c +142 -24
  65. data/ext/carray_cast_func.rb +46 -6
  66. data/ext/carray_class.c +4 -3
  67. data/ext/carray_config.h +28 -0
  68. data/ext/carray_conversion.c +16 -7
  69. data/ext/carray_copy.c +51 -14
  70. data/ext/carray_core.c +147 -55
  71. data/ext/carray_count.c +50 -20
  72. data/ext/carray_element.c +76 -33
  73. data/ext/carray_factorize.c +118 -64
  74. data/ext/carray_generate.c +191 -50
  75. data/ext/carray_histogram.c +62 -17
  76. data/ext/carray_hold.c +12 -15
  77. data/ext/carray_index_classifier.c +388 -176
  78. data/ext/carray_internal.h +336 -29
  79. data/ext/carray_kernels_bincmp.c +4453 -0
  80. data/ext/carray_kernels_binop.c +11368 -0
  81. data/ext/carray_kernels_init.c +1734 -0
  82. data/ext/carray_kernels_moncmp.c +2104 -0
  83. data/ext/carray_kernels_monop.c +18369 -0
  84. data/ext/carray_kernels_reduce_aggregate.c +23818 -0
  85. data/ext/carray_kernels_reduce_boolean.c +406 -0
  86. data/ext/carray_kernels_reduce_cumulative.c +10086 -0
  87. data/ext/carray_kernels_reduce_extreme.c +17634 -0
  88. data/ext/carray_kernels_reduce_variance.c +3849 -0
  89. data/ext/carray_kernels_scan.c +4302 -0
  90. data/ext/carray_kernels_search.c +33829 -0
  91. data/ext/carray_kernels_sort.c +10906 -0
  92. data/ext/carray_kernels_triop.c +1864 -0
  93. data/ext/carray_key_windows.c +216 -0
  94. data/ext/carray_lazy.c +170 -111
  95. data/ext/carray_mask.c +292 -72
  96. data/ext/carray_median_percentile.c +82 -28
  97. data/ext/carray_memory_view.c +669 -561
  98. data/ext/carray_operator.c +124 -47
  99. data/ext/carray_order.c +384 -76
  100. data/ext/carray_partition.c +28 -27
  101. data/ext/carray_random.c +67 -13
  102. data/ext/carray_round.c +273 -0
  103. data/ext/carray_scatter.c +63 -38
  104. data/ext/carray_segment.c +3 -3
  105. data/ext/carray_slab.c +91 -28
  106. data/ext/carray_sort.c +35 -35
  107. data/ext/carray_struct.c +19 -18
  108. data/ext/carray_test.c +26 -5
  109. data/ext/carray_undef.c +4 -0
  110. data/ext/carray_utils.c +246 -45
  111. data/ext/mk_call_cfunc.rb +19 -2
  112. data/ext/mkkernel.rb +198 -240
  113. data/ext/ruby_carray.c +10 -4
  114. data/ext/version.h +4 -4
  115. data/lib/carray/attribute.rb +126 -22
  116. data/lib/carray/autoload_carray.rb +8 -31
  117. data/lib/carray/autoload_method_extension.rb +14 -5
  118. data/lib/carray/axis_group.rb +255 -106
  119. data/lib/carray/basics.rb +78 -10
  120. data/lib/carray/bincount_nd.rb +12 -2
  121. data/lib/carray/block_iterator.rb +126 -23
  122. data/lib/carray/boolean_reduce.rb +17 -17
  123. data/lib/carray/categorical.rb +165 -43
  124. data/lib/carray/categorical_iterator.rb +130 -27
  125. data/lib/carray/complex.rb +5 -5
  126. data/lib/carray/conditional.rb +67 -18
  127. data/lib/carray/const_string.rb +14 -5
  128. data/lib/carray/construct.rb +15 -11
  129. data/lib/carray/data_type_extension.rb +1 -0
  130. data/lib/carray/distance_limit.rb +77 -0
  131. data/lib/carray/fixlen_string.rb +7 -1
  132. data/lib/carray/frame/column_verbs.rb +70 -0
  133. data/lib/carray/frame/convert.rb +69 -0
  134. data/lib/carray/frame/csv_parser.rb +111 -17
  135. data/lib/carray/frame/describe.rb +3 -2
  136. data/lib/carray/frame/frame.rb +246 -21
  137. data/lib/carray/frame/group.rb +510 -95
  138. data/lib/carray/frame/io.rb +115 -28
  139. data/lib/carray/frame/join.rb +312 -51
  140. data/lib/carray/frame/records.rb +83 -7
  141. data/lib/carray/frame/reshape.rb +42 -35
  142. data/lib/carray/frame/sort.rb +7 -19
  143. data/lib/carray/frame/verbs.rb +188 -48
  144. data/lib/carray/frame/windows.rb +117 -0
  145. data/lib/carray/frame.rb +2 -0
  146. data/lib/carray/fuse_source.rb +11 -4
  147. data/lib/carray/fusion.rb +40 -16
  148. data/lib/carray/histogram.rb +6 -1
  149. data/lib/carray/inspect.rb +31 -8
  150. data/lib/carray/iterator.rb +85 -7
  151. data/lib/carray/key_windows.rb +620 -0
  152. data/lib/carray/lazy.rb +182 -183
  153. data/lib/carray/mask_gap_fill.rb +63 -57
  154. data/lib/carray/meld_reduce.rb +12 -14
  155. data/lib/carray/methods/bin.rb +26 -18
  156. data/lib/carray/methods/bincount.rb +16 -0
  157. data/lib/carray/methods/bit_string.rb +1 -1
  158. data/lib/carray/methods/choose.rb +16 -3
  159. data/lib/carray/methods/composition.rb +3 -1
  160. data/lib/carray/methods/gather_nd.rb +14 -5
  161. data/lib/carray/methods/insert_block.rb +2 -2
  162. data/lib/carray/methods/is_between.rb +59 -0
  163. data/lib/carray/methods/is_in.rb +53 -36
  164. data/lib/carray/methods/join.rb +1 -6
  165. data/lib/carray/methods/lag.rb +49 -0
  166. data/lib/carray/methods/locate_addr.rb +47 -51
  167. data/lib/carray/methods/mode.rb +1 -1
  168. data/lib/carray/methods/nunique.rb +11 -6
  169. data/lib/carray/methods/resize.rb +3 -3
  170. data/lib/carray/methods/value_counts.rb +4 -11
  171. data/lib/carray/record_order.rb +3 -3
  172. data/lib/carray/rng.rb +3 -0
  173. data/lib/carray/runtime.rb +53 -8
  174. data/lib/carray/segment_iterator.rb +313 -82
  175. data/lib/carray/serialize.rb +24 -10
  176. data/lib/carray/slab_iterator.rb +50 -0
  177. data/lib/carray/string.rb +105 -5
  178. data/lib/carray/string_operation_extension.rb +8 -1
  179. data/lib/carray/struct.rb +9 -21
  180. data/lib/carray/struct_builder.rb +19 -8
  181. data/lib/carray/time.rb +726 -104
  182. data/lib/carray/window_iterator.rb +122 -16
  183. data/lib/carray.rb +7 -0
  184. data/yard-stubs/ca_obj_bitfield.rb +1 -1
  185. data/yard-stubs/ca_obj_field.rb +4 -0
  186. data/yard-stubs/ca_obj_grid.rb +21 -10
  187. data/yard-stubs/ca_obj_refer.rb +2 -1
  188. data/yard-stubs/ca_obj_roll.rb +2 -2
  189. data/yard-stubs/ca_obj_shift.rb +5 -1
  190. data/yard-stubs/ca_obj_tile.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +8 -3
  192. data/yard-stubs/carray_access.rb +6 -1
  193. data/yard-stubs/carray_attribute.rb +0 -5
  194. data/yard-stubs/carray_cast.rb +10 -0
  195. data/yard-stubs/carray_conversion.rb +2 -1
  196. data/yard-stubs/carray_copy.rb +4 -2
  197. data/yard-stubs/carray_count.rb +4 -4
  198. data/yard-stubs/carray_element.rb +10 -4
  199. data/yard-stubs/carray_generate.rb +21 -1
  200. data/yard-stubs/carray_mask.rb +26 -2
  201. data/yard-stubs/carray_math.rb +27 -1
  202. data/yard-stubs/carray_median_percentile.rb +5 -2
  203. data/yard-stubs/carray_memory_view.rb +40 -23
  204. data/yard-stubs/carray_order.rb +29 -18
  205. data/yard-stubs/carray_random.rb +13 -4
  206. data/yard-stubs/carray_scatter.rb +14 -9
  207. data/yard-stubs/carray_slab.rb +9 -2
  208. data/yard-stubs/carray_sort.rb +6 -5
  209. data/yard-stubs/carray_test.rb +5 -3
  210. metadata +25 -3
  211. data/lib/carray/arrow_tensor.rb +0 -401
  212. data/lib/carray/table.rb +0 -108
@@ -63,6 +63,7 @@ class CArray::Serializer # :nodoc:
63
63
 
64
64
  # @!visibility private
65
65
  MAGIC = "_CARRAY3"
66
+ Ractor.make_shareable(MAGIC)
66
67
  # @!visibility private
67
68
  HEADER_BYTES = 256
68
69
  # @!visibility private
@@ -119,7 +120,8 @@ class CArray::Serializer # :nodoc:
119
120
  # `Marshal.dump(ca)` instead), or carries a data_class the
120
121
  # v1.0 flat-primitive schema cannot express.
121
122
  # @raise [TypeError] when `ca` is a Face other than a record (save
122
- # its storage with `.parent`).
123
+ # its storage through the Face's own accessor, such as `#ticks` or
124
+ # `#codes`, or its values with `Marshal.dump(ca.to_type(:object))`).
123
125
  def save (ca, endian: nil)
124
126
  if ca.data_type == :object
125
127
  raise ArgumentError,
@@ -131,9 +133,12 @@ class CArray::Serializer # :nodoc:
131
133
  # would carry its storage without what makes it the Face (a unit, the
132
134
  # string buffer, the labels), and load could not put that back.
133
135
  if ca.face? && !ca.is_a?(CARecord)
136
+ storage = ca.__send__(:face_storage_method)
134
137
  raise TypeError,
135
138
  "CArray.save cannot carry a #{ca.class} yet (its Face would be " \
136
- "lost); save its storage with .parent"
139
+ "lost); " +
140
+ (storage ? "save its storage, which ##{storage} gives, or " : "") +
141
+ "keep its values with Marshal.dump(ca.to_type(:object))"
137
142
  end
138
143
 
139
144
  file_endian = endian || CArray.endian
@@ -390,7 +395,7 @@ class CArray::Serializer # :nodoc:
390
395
  if trailer.key?("attrs")
391
396
  attrs = trailer["attrs"]
392
397
  if attrs.is_a?(Hash) and not attrs.empty?
393
- ca.instance_variable_set(:@attr, attrs)
398
+ ca.set_attrs(attrs)
394
399
  end
395
400
  end
396
401
  ca
@@ -615,7 +620,8 @@ class CArray
615
620
  ["object",
616
621
  target.shape,
617
622
  target.value.to_a,
618
- (target.has_mask? ? target.mask.to_a : nil)]
623
+ (target.has_mask? ? target.mask.to_a : nil),
624
+ target.attrs.to_h]
619
625
  else
620
626
  ["portable", CArray.dump(target)]
621
627
  end
@@ -627,7 +633,10 @@ class CArray
627
633
  # @param data [Array] Marshal payload.
628
634
  # @return [void]
629
635
  def marshal_load (data)
630
- initialize_copy(CArray.send(:array_from_marshal_payload, data))
636
+ source = CArray.send(:array_from_marshal_payload, data)
637
+ initialize_copy(source)
638
+ attrs = source.attrs
639
+ set_attrs(attrs) unless attrs.empty?
631
640
  end
632
641
 
633
642
  # The array a {#marshal_dump} payload describes.
@@ -635,19 +644,22 @@ class CArray
635
644
  tag, *rest = data
636
645
  case tag
637
646
  when "object"
638
- shape, values, mask = rest
647
+ shape, values, mask, attrs = rest
639
648
  ca = CArray.object(*shape)
640
649
  ca[] = values
641
650
  if mask
642
651
  ca.mask = 0
643
652
  ca.mask[] = mask
644
653
  end
654
+ ca.set_attrs(attrs) if attrs && !attrs.empty?
645
655
  ca
646
656
  when "portable"
647
657
  CArray.load(StringIO.new(rest[0]))
648
658
  when "record"
649
- data_class, storage = rest
650
- CARecord.wrap(array_from_marshal_payload(storage), data_class)
659
+ data_class, storage, attrs = rest
660
+ ca = CARecord.wrap(array_from_marshal_payload(storage), data_class)
661
+ ca.set_attrs(attrs) if attrs && !attrs.empty?
662
+ ca
651
663
  else
652
664
  raise TypeError, "unrecognised CArray Marshal payload"
653
665
  end
@@ -668,9 +680,11 @@ end
668
680
  # @return [String]
669
681
  def _dump (_level)
670
682
  if face?
683
+ storage = face_storage_method
671
684
  raise TypeError,
672
685
  "Marshal cannot carry a #{self.class} yet (its Face would be lost); " \
673
- "dump its storage with .parent"
686
+ "dump its values with to_type(:object)" +
687
+ (storage ? ", or its storage, which ##{storage} gives" : "")
674
688
  end
675
689
  Marshal.dump(CArray.instance_method(:marshal_dump).bind_call(self))
676
690
  end
@@ -719,7 +733,7 @@ class CARecord
719
733
  def marshal_dump
720
734
  return CArray.instance_method(:marshal_dump).bind_call(self) unless data_class.name
721
735
  ["record", data_class,
722
- CArray.instance_method(:marshal_dump).bind_call(copy.parent)]
736
+ CArray.instance_method(:marshal_dump).bind_call(copy.parent), attrs.to_h]
723
737
  end
724
738
 
725
739
  end
@@ -174,6 +174,27 @@ class CASlabIterator < CAIterator
174
174
  RUBY
175
175
  end
176
176
 
177
+ # @!method first
178
+ # Per-slab first present value, in row-major order over the slab axes;
179
+ # UNDEF for a slab with none.
180
+ # @return [CArray] one value per slab
181
+ # @!method last
182
+ # Per-slab last present value; UNDEF for a slab with none.
183
+ # @return [CArray] one value per slab
184
+ # @!method nunique
185
+ # Per-slab number of distinct present values (int64); 0 for a slab with
186
+ # none.
187
+ # @return [CArray] one count per slab
188
+ #
189
+ # These take no `min_count:` or `fill_value:`, as the core ones do not.
190
+ [:first, :last, :nunique].each do |op|
191
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
192
+ def #{op} (**kw)
193
+ @reference.#{op}(axis: @slab_axes, **CAIterator.reduce_keywords(:#{op}, kw))
194
+ end
195
+ RUBY
196
+ end
197
+
177
198
  # @overload count(v = <none>)
178
199
  # Per-slab count, delegating to `reference.count(..., axis: slab_axes)`.
179
200
  # No argument counts present (non-masked) cells; `count(UNDEF)` counts
@@ -344,6 +365,35 @@ class CASlabIterator < CAIterator
344
365
  RUBY
345
366
  end
346
367
 
368
+ # @overload lag(n = 1)
369
+ # Per-slab value `n` steps earlier along the slab axis (UNDEF where the
370
+ # slab has none), as `reference.lag(n, axis: slab_axis)`: a view of the
371
+ # reference. A multi-axis slab has no single direction to step in, so it
372
+ # raises, as the scans do.
373
+ # @return [CArray] reference-shaped
374
+ def lag (n = 1)
375
+ @reference.lag(n, axis: single_scan_axis(:lag))
376
+ end
377
+
378
+ # @overload strip_mask(method:)
379
+ # Per-slab fill of the masked cells along the slab axis, as
380
+ # `reference.strip_mask(method:, axis: slab_axis)`: `:forward` / `:ffill`
381
+ # or `:backward` / `:bfill`. A multi-axis slab raises, as the scans do.
382
+ # @return [CArray] reference-shaped
383
+ def strip_mask (method:)
384
+ CAIterator.hold_backward?(method)
385
+ @reference.strip_mask(method: method, axis: single_scan_axis(:strip_mask))
386
+ end
387
+
388
+ # @overload order(descending: false, method: :ordinal, kind: :quick)
389
+ # Per-slab rank of each cell along the slab axis (int64), as
390
+ # `reference.order(axis: slab_axis, ...)`. A multi-axis slab has no
391
+ # single sort axis, so it raises, as sort_addr does.
392
+ # @return [CArray] reference-shaped
393
+ def order (**kw)
394
+ @reference.order(axis: single_sort_axis(:order), **CAIterator.reduce_keywords(:order, kw))
395
+ end
396
+
347
397
  private
348
398
 
349
399
  # The single slab axis for the order surface (core sort takes one integer
data/lib/carray/string.rb CHANGED
@@ -17,20 +17,28 @@ class CArray
17
17
  # CArray.string([a, nil, b]) # nil → masked element
18
18
  # CArray.string(other_ca) # from a String Face / object / raw fixlen
19
19
  #
20
- # `nil` entries become masked cells; "" (empty) is a valid distinct value.
21
- # A CArray source is normalised through a String Face ({CArray.string_face_of}):
22
- # a String Face converts, CA_OBJECT storage wraps, a raw CA_FIXLEN reads as
23
- # NUL-stripped strings; a numeric / boolean array is rejected (stringify with
24
- # {#format} / {CArray.format}).
20
+ # A cell of a CAString is a String. In the Array and block forms `nil`
21
+ # becomes a masked cell; "" (empty) is a valid distinct value. Any other
22
+ # value (a Symbol, a number, a nested Array) is refused: the values are one
23
+ # cell each, so a CAString of more than one dimension is built from an
24
+ # object array (`CArray.string(CA_OBJECT(nested))`).
25
+ # A CArray source is normalised through a String Face: a String Face
26
+ # converts, CA_OBJECT storage wraps zero-copy (every cell that is not
27
+ # masked must be a String, `nil` included: mask it instead), a raw
28
+ # CA_FIXLEN reads as NUL-stripped strings; a numeric / boolean array is
29
+ # rejected (stringify with {#format} / {CArray.format}).
25
30
  # @overload string(values)
26
31
  # Returns a {CAString} wrapping a CA_OBJECT entity of the given values.
27
32
  # @param values [Array<String, nil>] source values.
28
33
  # @return [CAString]
34
+ # @raise [TypeError] if a value is neither a String nor `nil`.
29
35
  # @overload string(ca)
30
36
  # Returns a {CAString} of the string-bearing CArray `ca`.
31
37
  # @param ca [CArray] a String Face, CA_OBJECT, or raw CA_FIXLEN array.
32
38
  # @return [CAString]
33
39
  # @raise [CArray::DataTypeError] if `ca` is numeric / boolean.
40
+ # @raise [TypeError] if a cell of an object array that is not masked is
41
+ # not a String.
34
42
  # @overload string(n) { |i| ... }
35
43
  # Returns an `n`-element {CAString} filled by the block, following the
36
44
  # arity-0 broadcast convention.
@@ -55,6 +63,12 @@ class CArray
55
63
 
56
64
  entity = CArray.object(values.size)
57
65
  values.each_with_index do |s, i|
66
+ if s.is_a?(Array)
67
+ raise TypeError,
68
+ "CArray.string: value #{i} is an Array; a CAString cell is a " \
69
+ "String (for a CAString of another shape, give " \
70
+ "CArray.string an object array: CArray.string(CA_OBJECT(values)))"
71
+ end
58
72
  entity[i] = s.nil? ? UNDEF : s
59
73
  end
60
74
  CAString.wrap(entity)
@@ -66,4 +80,90 @@ end
66
80
  class CAString
67
81
  include CArray::StringOperationMixin
68
82
  include CArray::StringOperationMixin::Mutable
83
+
84
+ # The String operators and the picks by order run on the object storage,
85
+ # cell by cell, as on any object array. A String's +, * and % give a
86
+ # String (or raise), and a pick gives one of the operands, so the result
87
+ # is text and comes back as a CAString, as the bang forms (add!, mul!)
88
+ # keep it. A lazy operand is computed here.
89
+
90
+ # Per-cell `String#+`.
91
+ # @return [CAString] shape and mask carried.
92
+ def + (other) ; as_text(super) ; end
93
+ # Per-cell `String#+` (the word form of {#+}).
94
+ # @return [CAString]
95
+ def add (other) ; as_text(super) ; end
96
+ # Per-cell `String#*`.
97
+ # @return [CAString] shape and mask carried.
98
+ def * (other) ; as_text(super) ; end
99
+ # Per-cell `String#*` (the word form of {#*}).
100
+ # @return [CAString]
101
+ def mul (other) ; as_text(super) ; end
102
+ # Per-cell `String#%` (format).
103
+ # @return [CAString] shape and mask carried.
104
+ def % (other) ; as_text(super) ; end
105
+ # Per-cell `String#%` (the word form of {#%}).
106
+ # @return [CAString]
107
+ def mod (other) ; as_text(super) ; end
108
+ # The later String of each cell and `other`, cell by cell.
109
+ # @return [CAString]
110
+ def pmax (other) ; as_text(super) ; end
111
+ # The earlier String of each cell and `other`, cell by cell.
112
+ # @return [CAString]
113
+ def pmin (other) ; as_text(super) ; end
114
+ # Another name of {#pmax} (a String has no NaN to let win).
115
+ # @return [CAString]
116
+ def maximum (other) ; as_text(super) ; end
117
+ # Another name of {#pmin}.
118
+ # @return [CAString]
119
+ def minimum (other) ; as_text(super) ; end
120
+ # Another name of {#pmax}.
121
+ # @return [CAString]
122
+ def fmax (other) ; as_text(super) ; end
123
+ # Another name of {#pmin}.
124
+ # @return [CAString]
125
+ def fmin (other) ; as_text(super) ; end
126
+
127
+ # The String a blank cell holds: the cell a fresh {#template} gives and
128
+ # the one {#shift} fills by default. Frozen, because every blank cell
129
+ # shares it.
130
+ BLANK = "".freeze
131
+
132
+ # A same-shape CAString. With no block every cell is {BLANK}; a block
133
+ # fills the cells as for any array, and its values must be Strings or
134
+ # UNDEF. Given a data type, the result is a plain array of that type.
135
+ # @overload template(type = nil, **opts)
136
+ # @return [CAString, CArray]
137
+ def template (*args, **opts, &block)
138
+ return super if block || ! args.empty? || ! opts.empty?
139
+ out = super
140
+ out[] = BLANK
141
+ out
142
+ end
143
+
144
+ # Shifts the cells as {CArray#shift} does. The cells shifted in hold
145
+ # {BLANK} unless `fill_value:` names a String or UNDEF.
146
+ # @return [CAString] a view over the shifted cells.
147
+ def shift (*shifts, fill_value: BLANK, **opts)
148
+ super(*shifts, fill_value: fill_value, **opts)
149
+ end
150
+
151
+ # A window as {CArray#window} gives. The cells outside the array hold
152
+ # {BLANK} unless `fill_value:` names a String or UNDEF.
153
+ # @return [CAString] a view over the window.
154
+ def window (*ranges, fill_value: BLANK, **opts)
155
+ super(*ranges, fill_value: fill_value, **opts)
156
+ end
157
+
158
+ # A resized copy as {CArray#resize} gives. The new cells hold {BLANK}
159
+ # unless `fill_value:` names a String or UNDEF.
160
+ # @return [CAString]
161
+ def resize (*newdim, fill_value: BLANK)
162
+ super(*newdim, fill_value: fill_value)
163
+ end
164
+
165
+ private def as_text (result)
166
+ result = result.copy if result.lazy_view?
167
+ CAString.wrap(result)
168
+ end
69
169
  end
@@ -278,8 +278,15 @@ class CArray
278
278
  # boolean array has no string reading and is rejected -- stringify explicitly
279
279
  # with #format / CArray.format. (This mirrors the numeric gate that rejects
280
280
  # numeric reductions on a String Face.)
281
- def self.string_face_of (ca)
281
+ def self.string_face_of (ca, verb = "CArray.string")
282
282
  return ca if ca.is_a?(CArray::StringOperationMixin)
283
+ # Another Face's storage is not its text: a CATime's fixlen surface
284
+ # holds ticks, a categorical's codes.
285
+ if ca.face?
286
+ raise TypeError,
287
+ "#{verb}: a #{ca.class} is not text " \
288
+ "(convert its values to Strings first)"
289
+ end
283
290
  case ca.data_type
284
291
  when :object then CAString.wrap(ca)
285
292
  when :fixlen then CAFixlenString.wrap(ca)
data/lib/carray/struct.rb CHANGED
@@ -340,14 +340,6 @@ class CAStruct
340
340
  super
341
341
  end
342
342
 
343
- protected
344
-
345
- def __data__
346
- @data
347
- end
348
-
349
- public
350
-
351
343
  # Member access. A member is named by a String (`s["lat"]`) or its
352
344
  # Integer position (`s[0]`); its accessor (`s.lat`) reads it too. A
353
345
  # Symbol is not a member name here, and a name that is not a member
@@ -509,26 +501,22 @@ class CAStruct
509
501
  # @!method <=(other)
510
502
  # @!method >(other)
511
503
  # @!method >=(other)
512
- { :< => ->(c) { c < 0 }, :<= => ->(c) { c <= 0 },
513
- :> => ->(c) { c > 0 }, :>= => ->(c) { c >= 0 } }.each do |op, test|
514
- define_method(op) do |other|
504
+ [:<, :<=, :>, :>=].each do |op|
505
+ define_method(op, &Ractor.make_shareable(proc do |other|
515
506
  c = (self <=> other)
516
507
  if c.nil?
517
508
  raise ArgumentError,
518
509
  "comparison of #{self.class.inspect} with #{other.class.inspect} failed" +
519
510
  (self.class.order_by ? "" : " (the struct declares no order_by:)")
520
511
  end
521
- test.call(c)
522
- end
512
+ c.public_send(op, 0)
513
+ end))
523
514
  end
524
515
 
525
- # One member of {#<=>}: NaN after every number, as a numeric array sorts.
516
+ # One member of {#<=>}: the order a sort puts the member's values in
517
+ # (NaN after every number).
526
518
  def self.compare_key (a, b)
527
- a_nan = a.is_a?(Float) && a.nan?
528
- b_nan = b.is_a?(Float) && b.nan?
529
- return (a_nan ? 0 : -1) if b_nan
530
- return 1 if a_nan
531
- a <=> b
519
+ CArray.send(:object_order_cmp, a, b)
532
520
  end
533
521
 
534
522
  # Byte-level identity. Two CAStruct instances are eql? iff they are
@@ -728,8 +716,8 @@ end
728
716
  # in Builder sets them up before any instance is created). Subclasses
729
717
  # created by Builder later don't need re-installation -- they inherit the
730
718
  # C methods from CAStruct.
731
- if CArray.respond_to?(:__install_castruct_methods__)
732
- CArray.__install_castruct_methods__(CAStruct)
719
+ if CArray.respond_to?(:install_struct_methods, true)
720
+ CArray.send(:install_struct_methods, CAStruct)
733
721
  end
734
722
 
735
723
  require 'carray/record_order'
@@ -288,6 +288,15 @@ class CAStruct::Builder # :nodoc:
288
288
  end
289
289
 
290
290
  # @!visibility private
291
+ # Shared with every Ractor when it can be. A member whose type is an
292
+ # array template holds a CArray, which cannot be shared, and its struct
293
+ # is then used in the main Ractor only.
294
+ def self.shareable (object)
295
+ Ractor.make_shareable(object)
296
+ rescue Ractor::Error
297
+ object.freeze
298
+ end
299
+
291
300
  ALIGN_TABLE = {
292
301
  CA_FIXLEN => CA_ALIGN_FIXLEN,
293
302
  CA_BOOLEAN => CA_ALIGN_INT8,
@@ -304,6 +313,7 @@ class CAStruct::Builder # :nodoc:
304
313
  CA_CMPLX64 => CA_ALIGN_FLOAT32,
305
314
  CA_CMPLX128 => CA_ALIGN_FLOAT64,
306
315
  }
316
+ Ractor.make_shareable(ALIGN_TABLE)
307
317
 
308
318
  # Raises ArgumentError when `opt` names a key outside `allowed`. The
309
319
  # options are read one key at a time, so a misspelt key would otherwise
@@ -463,9 +473,9 @@ class CAStruct::Builder # :nodoc:
463
473
  end
464
474
  table.freeze
465
475
  names.freeze
466
- klass.const_set(:MEMBER_TABLE, table) ### required element as data class
467
- klass.const_set(:MEMBERS, names)
468
- klass.const_set(:ORDER_BY, order_keys(table)) if @order_by
476
+ klass.const_set(:MEMBER_TABLE, self.class.shareable(table)) ### required element as data class
477
+ klass.const_set(:MEMBERS, self.class.shareable(names))
478
+ klass.const_set(:ORDER_BY, self.class.shareable(order_keys(table))) if @order_by
469
479
  # ---
470
480
  # Step 3 dispatch table: precompute a per-member [reader, writer]
471
481
  # Proc pair so `record[name]` is one Hash lookup + one Proc call,
@@ -473,7 +483,7 @@ class CAStruct::Builder # :nodoc:
473
483
  # checks and view-shape decisions happen here, once, at class
474
484
  # definition time.
475
485
  klass.const_set(:DISPATCH_TABLE,
476
- self.class.build_dispatch_table(table, @offset).freeze)
486
+ self.class.shareable(self.class.build_dispatch_table(table, @offset)))
477
487
  # Also precompute FAST_PRIMITIVES, the subset of members that the
478
488
  # C-side `[]` / `[]=` can handle without
479
489
  # allocating a CAField view. Entries are `[offset, type_code]`;
@@ -491,15 +501,16 @@ class CAStruct::Builder # :nodoc:
491
501
  klass.module_eval {
492
502
  taken = ->(m) { method_defined?(m) || private_method_defined?(m) }
493
503
  names.each do |name|
504
+ Ractor.make_shareable(name) # the accessors below capture it
494
505
  unless taken.(name)
495
- define_method(name) {
506
+ define_method(name, &Ractor.make_shareable(proc {
496
507
  return self[name]
497
- }
508
+ }))
498
509
  end
499
510
  unless taken.("#{name}=")
500
- define_method("#{name}=") { |val|
511
+ define_method("#{name}=", &Ractor.make_shareable(proc { |val|
501
512
  return self[name] = val
502
- }
513
+ }))
503
514
  end
504
515
  end
505
516
  }