carray 2.0.1 → 3.0.1

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 (386) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -25
  3. data/CHANGELOG.md +338 -0
  4. data/{NEWS.md → CHANGELOG.v1.md} +3 -0
  5. data/LICENSE +1 -1
  6. data/README.md +120 -36
  7. data/carray.gemspec +32 -30
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1244 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +124 -0
  15. data/ext/ca_binop_dispatch.h +152 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +239 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4367 -0
  26. data/ext/ca_kernel_iterator.h +2596 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +839 -0
  33. data/ext/ca_obj_binop.c +948 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +967 -0
  39. data/ext/ca_obj_face.c +750 -0
  40. data/ext/ca_obj_face.h +279 -0
  41. data/ext/ca_obj_fake.c +239 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +308 -0
  45. data/ext/ca_obj_grid.c +866 -440
  46. data/ext/ca_obj_meld.c +1039 -0
  47. data/ext/ca_obj_moncmp.c +588 -0
  48. data/ext/ca_obj_monop.c +1123 -0
  49. data/ext/ca_obj_object.c +866 -296
  50. data/ext/ca_obj_record.c +470 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +593 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +624 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1306 -0
  58. data/ext/ca_obj_shift.c +231 -793
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2584 -0
  62. data/ext/ca_obj_string.c +270 -0
  63. data/ext/ca_obj_tile.c +622 -0
  64. data/ext/ca_obj_time.c +548 -0
  65. data/ext/ca_obj_timedelta.c +437 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +785 -0
  68. data/ext/ca_obj_window.c +1202 -565
  69. data/ext/ca_op_byte_swap.c +175 -0
  70. data/ext/ca_op_cmplx64.h +123 -0
  71. data/ext/ca_op_ipower.c +316 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +473 -0
  75. data/ext/ca_sweep_engine.h +166 -0
  76. data/ext/ca_transform_common.c +235 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +810 -420
  80. data/ext/carray_access.c +873 -731
  81. data/ext/carray_attribute.c +98 -329
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +376 -0
  84. data/ext/carray_build_flags.h +3 -0
  85. data/ext/carray_call_cfunc.c +2897 -874
  86. data/ext/carray_call_cfunc.h +313 -0
  87. data/ext/carray_cast.c +1264 -315
  88. data/ext/carray_cast_func.rb +81 -40
  89. data/ext/carray_class.c +53 -63
  90. data/ext/carray_config.h +28 -0
  91. data/ext/carray_conversion.c +350 -346
  92. data/ext/carray_copy.c +168 -270
  93. data/ext/carray_core.c +1396 -206
  94. data/ext/carray_count.c +312 -0
  95. data/ext/carray_data_type.c +43 -19
  96. data/ext/carray_element.c +585 -213
  97. data/ext/carray_factorize.c +2542 -0
  98. data/ext/carray_generate.c +230 -559
  99. data/ext/carray_histogram.c +490 -0
  100. data/ext/carray_hold.c +228 -0
  101. data/ext/carray_index_classifier.c +1021 -0
  102. data/ext/carray_index_classifier.h +27 -0
  103. data/ext/carray_internal.h +136 -0
  104. data/ext/carray_kernels_bincmp.c +4446 -0
  105. data/ext/carray_kernels_binop.c +11001 -0
  106. data/ext/carray_kernels_init.c +1131 -0
  107. data/ext/carray_kernels_map.c +3467 -0
  108. data/ext/carray_kernels_moncmp.c +2097 -0
  109. data/ext/carray_kernels_monop.c +18313 -0
  110. data/ext/carray_kernels_reduce_aggregate.c +25837 -0
  111. data/ext/carray_kernels_reduce_boolean.c +330 -0
  112. data/ext/carray_kernels_reduce_cumulative.c +14593 -0
  113. data/ext/carray_kernels_reduce_extreme.c +16948 -0
  114. data/ext/carray_kernels_reduce_variance.c +3910 -0
  115. data/ext/carray_kernels_scan.c +3693 -0
  116. data/ext/carray_kernels_search.c +32138 -0
  117. data/ext/carray_kernels_sort.c +10626 -0
  118. data/ext/carray_kernels_triop.c +1392 -0
  119. data/ext/carray_lazy.c +737 -0
  120. data/ext/carray_loop.c +88 -200
  121. data/ext/carray_mask.c +853 -158
  122. data/ext/carray_math_kernel.h +120 -0
  123. data/ext/carray_mathfunc.c +10 -241
  124. data/ext/carray_median_percentile.c +1257 -0
  125. data/ext/carray_memory_view.c +1650 -0
  126. data/ext/carray_operator.c +1525 -320
  127. data/ext/carray_order.c +664 -1394
  128. data/ext/carray_partition.c +416 -0
  129. data/ext/carray_random.c +518 -0
  130. data/ext/carray_scatter.c +357 -0
  131. data/ext/carray_slab.c +1219 -0
  132. data/ext/carray_slab.h +84 -0
  133. data/ext/carray_sort.c +829 -0
  134. data/ext/carray_sort_kernel.c +620 -0
  135. data/ext/carray_struct.c +695 -0
  136. data/ext/carray_test.c +343 -229
  137. data/ext/carray_undef.c +34 -17
  138. data/ext/carray_utils.c +175 -74
  139. data/ext/extconf.rb +234 -55
  140. data/ext/mk_call_cfunc.rb +671 -0
  141. data/ext/mkkernel.rb +9096 -0
  142. data/ext/ruby_carray.c +211 -108
  143. data/ext/version.h +4 -14
  144. data/ext/version.rb +5 -13
  145. data/lib/carray/arrow_tensor.rb +401 -0
  146. data/lib/carray/attribute.rb +166 -0
  147. data/lib/carray/autoload_carray.rb +239 -0
  148. data/lib/carray/autoload_method_extension.rb +45 -0
  149. data/lib/carray/axis_group.rb +711 -0
  150. data/lib/carray/basics.rb +481 -0
  151. data/lib/carray/bincount_nd.rb +358 -0
  152. data/lib/carray/block_iterator.rb +614 -0
  153. data/lib/carray/boolean_reduce.rb +109 -0
  154. data/lib/carray/categorical.rb +561 -0
  155. data/lib/carray/categorical_iterator.rb +1084 -0
  156. data/lib/carray/complex.rb +150 -0
  157. data/lib/carray/conditional.rb +216 -0
  158. data/lib/carray/const_string.rb +228 -0
  159. data/lib/carray/construct.rb +160 -328
  160. data/lib/carray/core_extensions.rb +297 -0
  161. data/lib/carray/data_type_extension.rb +250 -0
  162. data/lib/carray/fixlen_string.rb +95 -0
  163. data/lib/carray/frame/concat.rb +132 -0
  164. data/lib/carray/frame/convert.rb +95 -0
  165. data/lib/carray/frame/csv_parser.rb +211 -0
  166. data/lib/carray/frame/frame.rb +642 -0
  167. data/lib/carray/frame/group.rb +186 -0
  168. data/lib/carray/frame/io.rb +321 -0
  169. data/lib/carray/frame/join.rb +248 -0
  170. data/lib/carray/frame/records.rb +99 -0
  171. data/lib/carray/frame/sort.rb +113 -0
  172. data/lib/carray/frame/verbs.rb +316 -0
  173. data/lib/carray/frame.rb +16 -0
  174. data/lib/carray/fuse_source.rb +123 -0
  175. data/lib/carray/fusion.rb +218 -0
  176. data/lib/carray/histogram.rb +512 -0
  177. data/lib/carray/inspect.rb +37 -26
  178. data/lib/carray/iterator.rb +58 -349
  179. data/lib/carray/lazy.rb +941 -0
  180. data/lib/carray/mask_gap_fill.rb +200 -0
  181. data/lib/carray/math.rb +78 -342
  182. data/lib/carray/meld_reduce.rb +289 -0
  183. data/lib/carray/methods/align_addr.rb +116 -0
  184. data/lib/carray/methods/bin.rb +128 -0
  185. data/lib/carray/methods/bincount.rb +87 -0
  186. data/lib/carray/methods/bit_string.rb +92 -0
  187. data/lib/carray/methods/broadcast.rb +63 -0
  188. data/lib/carray/methods/choose.rb +39 -0
  189. data/lib/carray/methods/composition.rb +280 -0
  190. data/lib/carray/methods/gather_nd.rb +206 -0
  191. data/lib/carray/methods/index.rb +39 -0
  192. data/lib/carray/methods/insert_block.rb +99 -0
  193. data/lib/carray/methods/is_in.rb +141 -0
  194. data/lib/carray/methods/join.rb +90 -0
  195. data/lib/carray/methods/locate_addr.rb +52 -0
  196. data/lib/carray/methods/mask_duplicates.rb +41 -0
  197. data/lib/carray/methods/meshgrid.rb +90 -0
  198. data/lib/carray/methods/mode.rb +126 -0
  199. data/lib/carray/methods/nunique.rb +46 -0
  200. data/lib/carray/methods/resize.rb +56 -0
  201. data/lib/carray/methods/snap.rb +161 -0
  202. data/lib/carray/methods/string_format.rb +57 -0
  203. data/lib/carray/methods/unique.rb +47 -0
  204. data/lib/carray/methods/value_counts.rb +71 -0
  205. data/lib/carray/mkmf.rb +124 -101
  206. data/lib/carray/runtime.rb +89 -0
  207. data/lib/carray/serialize.rb +478 -167
  208. data/lib/carray/slab_iterator.rb +305 -0
  209. data/lib/carray/stack.rb +291 -0
  210. data/lib/carray/string.rb +56 -180
  211. data/lib/carray/string_operation_extension.rb +289 -0
  212. data/lib/carray/struct.rb +335 -323
  213. data/lib/carray/struct_builder.rb +697 -0
  214. data/lib/carray/table.rb +41 -2
  215. data/lib/carray/time.rb +2654 -38
  216. data/lib/carray/window_iterator.rb +927 -0
  217. data/lib/carray.rb +55 -57
  218. data/yard-stubs/ca_obj_array.rb +385 -0
  219. data/yard-stubs/ca_obj_bitarray.rb +38 -0
  220. data/yard-stubs/ca_obj_bitfield.rb +43 -0
  221. data/yard-stubs/ca_obj_block.rb +73 -0
  222. data/yard-stubs/ca_obj_byte_swap.rb +56 -0
  223. data/yard-stubs/ca_obj_fake.rb +31 -0
  224. data/yard-stubs/ca_obj_farray.rb +32 -0
  225. data/yard-stubs/ca_obj_field.rb +45 -0
  226. data/yard-stubs/ca_obj_grid.rb +35 -0
  227. data/yard-stubs/ca_obj_refer.rb +72 -0
  228. data/yard-stubs/ca_obj_roll.rb +45 -0
  229. data/yard-stubs/ca_obj_shift.rb +43 -0
  230. data/yard-stubs/ca_obj_stride.rb +181 -0
  231. data/yard-stubs/ca_obj_tile.rb +29 -0
  232. data/yard-stubs/ca_obj_transpose.rb +40 -0
  233. data/yard-stubs/ca_obj_window.rb +49 -0
  234. data/yard-stubs/carray_access.rb +131 -0
  235. data/yard-stubs/carray_attribute.rb +246 -0
  236. data/yard-stubs/carray_broadcast.rb +37 -0
  237. data/yard-stubs/carray_cast.rb +489 -0
  238. data/yard-stubs/carray_class.rb +65 -0
  239. data/yard-stubs/carray_conversion.rb +76 -0
  240. data/yard-stubs/carray_copy.rb +79 -0
  241. data/yard-stubs/carray_core.rb +114 -0
  242. data/yard-stubs/carray_count.rb +79 -0
  243. data/yard-stubs/carray_element.rb +108 -0
  244. data/yard-stubs/carray_generate.rb +66 -0
  245. data/yard-stubs/carray_lazy.rb +23 -0
  246. data/yard-stubs/carray_loop.rb +140 -0
  247. data/yard-stubs/carray_mask.rb +259 -0
  248. data/yard-stubs/carray_math.rb +132 -0
  249. data/yard-stubs/carray_mathfunc.rb +45 -0
  250. data/yard-stubs/carray_median_percentile.rb +89 -0
  251. data/yard-stubs/carray_memory_view.rb +163 -0
  252. data/yard-stubs/carray_order.rb +312 -0
  253. data/yard-stubs/carray_random.rb +89 -0
  254. data/yard-stubs/carray_scatter.rb +106 -0
  255. data/yard-stubs/carray_slab.rb +57 -0
  256. data/yard-stubs/carray_sort.rb +163 -0
  257. data/yard-stubs/carray_test.rb +85 -0
  258. data/yard-stubs/carray_undef.rb +64 -0
  259. data/yard-stubs/carray_utils.rb +97 -0
  260. data/yard-stubs/ruby_carray.rb +193 -0
  261. metadata +220 -138
  262. data/Rakefile +0 -51
  263. data/TODO.md +0 -18
  264. data/ext/ca_iter_block.c +0 -257
  265. data/ext/ca_iter_dimension.c +0 -299
  266. data/ext/ca_iter_window.c +0 -214
  267. data/ext/ca_obj_mapping.c +0 -644
  268. data/ext/ca_obj_unbound_repeat.c +0 -529
  269. data/ext/carray_iterator.c +0 -641
  270. data/ext/carray_math.rb +0 -850
  271. data/ext/carray_numeric.c +0 -259
  272. data/ext/carray_sort_addr.c +0 -254
  273. data/ext/carray_stat.c +0 -2100
  274. data/ext/carray_stat_proc.rb +0 -1999
  275. data/ext/mkmath.rb +0 -741
  276. data/ext/ruby_ccomplex.c +0 -509
  277. data/ext/ruby_float_func.c +0 -86
  278. data/lib/carray/array.rb +0 -8
  279. data/lib/carray/autoload/autoload_base.rb +0 -19
  280. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  281. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  282. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  283. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  284. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  285. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  286. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  287. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  288. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  289. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  290. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  291. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  292. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  293. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  294. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  295. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  296. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  297. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  298. data/lib/carray/autoload.rb +0 -141
  299. data/lib/carray/basic.rb +0 -191
  300. data/lib/carray/broadcast.rb +0 -101
  301. data/lib/carray/compose.rb +0 -315
  302. data/lib/carray/convert.rb +0 -115
  303. data/lib/carray/info.rb +0 -110
  304. data/lib/carray/io/imagemagick.rb +0 -235
  305. data/lib/carray/mask.rb +0 -102
  306. data/lib/carray/math/histogram.rb +0 -177
  307. data/lib/carray/math/recurrence.rb +0 -93
  308. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  309. data/lib/carray/object/ca_obj_link.rb +0 -50
  310. data/lib/carray/object/ca_obj_pack.rb +0 -99
  311. data/lib/carray/obsolete.rb +0 -256
  312. data/lib/carray/ordering.rb +0 -181
  313. data/lib/carray/testing.rb +0 -51
  314. data/lib/carray/transform.rb +0 -109
  315. data/mailmap +0 -1
  316. data/misc/Methods.ja.md +0 -182
  317. data/misc/NOTE +0 -51
  318. data/spec/Classes/CABitfield_spec.rb +0 -58
  319. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  320. data/spec/Classes/CABlock_spec.rb +0 -205
  321. data/spec/Classes/CAField_spec.rb +0 -39
  322. data/spec/Classes/CAGrid_spec.rb +0 -75
  323. data/spec/Classes/CAMap_spec.rb +0 -0
  324. data/spec/Classes/CAMapping_spec.rb +0 -105
  325. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  326. data/spec/Classes/CAObject_spec.rb +0 -33
  327. data/spec/Classes/CARefer_spec.rb +0 -93
  328. data/spec/Classes/CARepeat_spec.rb +0 -65
  329. data/spec/Classes/CASelect_spec.rb +0 -22
  330. data/spec/Classes/CAShift_spec.rb +0 -16
  331. data/spec/Classes/CAStruct_spec.rb +0 -71
  332. data/spec/Classes/CATranspose_spec.rb +0 -60
  333. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  334. data/spec/Classes/CAWindow_spec.rb +0 -54
  335. data/spec/Classes/CAWrap_spec.rb +0 -8
  336. data/spec/Classes/CArray_spec.rb +0 -184
  337. data/spec/Classes/CScalar_spec.rb +0 -55
  338. data/spec/Features/feature_130_spec.rb +0 -19
  339. data/spec/Features/feature_attributes_spec.rb +0 -280
  340. data/spec/Features/feature_boolean_spec.rb +0 -98
  341. data/spec/Features/feature_broadcast.rb +0 -116
  342. data/spec/Features/feature_cast_function.rb +0 -19
  343. data/spec/Features/feature_cast_spec.rb +0 -33
  344. data/spec/Features/feature_class_spec.rb +0 -84
  345. data/spec/Features/feature_complex_spec.rb +0 -42
  346. data/spec/Features/feature_composite_spec.rb +0 -124
  347. data/spec/Features/feature_convert_spec.rb +0 -46
  348. data/spec/Features/feature_copy_spec.rb +0 -123
  349. data/spec/Features/feature_creation_spec.rb +0 -84
  350. data/spec/Features/feature_element_spec.rb +0 -144
  351. data/spec/Features/feature_extream_spec.rb +0 -54
  352. data/spec/Features/feature_generate_spec.rb +0 -74
  353. data/spec/Features/feature_index_spec.rb +0 -69
  354. data/spec/Features/feature_mask_spec.rb +0 -580
  355. data/spec/Features/feature_math_spec.rb +0 -97
  356. data/spec/Features/feature_order_spec.rb +0 -146
  357. data/spec/Features/feature_ref_store_spec.rb +0 -209
  358. data/spec/Features/feature_serialization_spec.rb +0 -125
  359. data/spec/Features/feature_stat_spec.rb +0 -397
  360. data/spec/Features/feature_virtual_spec.rb +0 -48
  361. data/spec/Features/method_eq_spec.rb +0 -81
  362. data/spec/Features/method_is_nan_spec.rb +0 -12
  363. data/spec/Features/method_map_spec.rb +0 -54
  364. data/spec/Features/method_max_with.rb +0 -20
  365. data/spec/Features/method_min_with.rb +0 -19
  366. data/spec/Features/method_ne_spec.rb +0 -18
  367. data/spec/Features/method_project_spec.rb +0 -188
  368. data/spec/Features/method_ref_spec.rb +0 -27
  369. data/spec/Features/method_round_spec.rb +0 -11
  370. data/spec/Features/method_s_linspace_spec.rb +0 -48
  371. data/spec/Features/method_s_span_spec.rb +0 -14
  372. data/spec/Features/method_seq_spec.rb +0 -47
  373. data/spec/Features/method_sort_with.rb +0 -43
  374. data/spec/Features/method_sorted_with.rb +0 -29
  375. data/spec/Features/method_span_spec.rb +0 -42
  376. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  377. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  378. data/spec/spec_all.rb +0 -10
  379. data/utils/ca_ase.rb +0 -21
  380. data/utils/ca_methods.rb +0 -15
  381. data/utils/cast_checker.rb +0 -30
  382. data/utils/convert_test.rb +0 -73
  383. data/utils/extract_yard.rb +0 -22
  384. data/utils/guess_shape.rb +0 -76
  385. data/utils/monkey_patch_methods.rb +0 -62
  386. data/utils/remove_resource_fork.sh +0 -5
@@ -0,0 +1,132 @@
1
+ # CAFrame row concatenation (memo §5, spine = addressing + gather).
2
+ #
3
+ # Two entry points, mirroring the CArray-level taxonomy:
4
+ #
5
+ # +CAFrame.meld+ view frame, strict same data type per column,
6
+ # each column is a CAMeld view over the inputs
7
+ # +CAFrame.concatenate+ eager frame, auto-casts per column,
8
+ # each column is a materialised entity
9
+ #
10
+ # Both are class methods (symmetric N-ary; no frame is privileged), like
11
+ # +CArray.meld+ / +CArray.concatenate+, not instance verbs.
12
+
13
+ class CAFrame
14
+ # Weld frames along the row axis, view-style. Each output column is
15
+ # +CArray.meld+ of that column across the input frames, so the result
16
+ # is a view frame that shares storage with the inputs (chain composability
17
+ # preserved: writes to the result flow back to whichever input frame owns
18
+ # the target segment, and vice versa).
19
+ #
20
+ # Per-column +data_type+ must match across frames (CArray.meld is a view
21
+ # constructor that refuses to auto-cast — silent promotion here would
22
+ # hide schema drift). For an eager, auto-casting alternative use
23
+ # {CAFrame.concatenate}.
24
+ #
25
+ # Column matching is by name (output order follows the first frame);
26
+ # every frame must carry the same column-name set. N-D columns carry
27
+ # their trailing dimensions along; masks are preserved.
28
+ #
29
+ # The index is welded too when every frame has one (their +axis_name+
30
+ # must agree); if none do, the result has no index; a mix raises.
31
+ # Column-set mismatch raises — a union-with-UNDEF mode is a possible
32
+ # future opt-in, kept out here to stay explicit (memo §4.2).
33
+ #
34
+ # CAFrame.meld(jan, feb, mar) # view over three months
35
+ # CAFrame.meld([jan, feb, mar]) # an Array is accepted too
36
+ def self.meld(*frames)
37
+ frames = frames.flatten
38
+ check_concat_inputs(frames, verb: "meld")
39
+ first = frames.first
40
+ names = first.variable_names
41
+ check_column_sets(frames, names, verb: "meld")
42
+ cols = {}
43
+ names.each do |name|
44
+ cols[name] = CArray.meld(frames.map { |f| f[name] }, axis: 0)
45
+ end
46
+ new(cols, axis_name: first.axis_name, index: meld_index(frames))
47
+ end
48
+
49
+ # Concatenate frames along the row axis, eagerly. Each output column is
50
+ # +CArray.concatenate+ of that column across the input frames, so per-column
51
+ # data types auto-promote to a common type. The result is a fresh, independent
52
+ # frame — writes to it do not propagate back to the input frames.
53
+ #
54
+ # For a view frame that shares storage with the inputs (strict same data type
55
+ # per column, chain composability preserved) use {CAFrame.meld}.
56
+ #
57
+ # Column matching, index handling, and column-set / index-mix rules
58
+ # match {CAFrame.meld}.
59
+ #
60
+ # CAFrame.concatenate(jan, feb, mar) # eager, independent result
61
+ # CAFrame.concatenate([jan, feb, mar]) # an Array is accepted too
62
+ def self.concatenate(*frames)
63
+ frames = frames.flatten
64
+ check_concat_inputs(frames, verb: "concatenate")
65
+ first = frames.first
66
+ names = first.variable_names
67
+ check_column_sets(frames, names, verb: "concatenate")
68
+ cols = {}
69
+ names.each do |name|
70
+ cols[name] = CArray.concatenate(frames.map { |f| f[name] })
71
+ end
72
+ new(cols, axis_name: first.axis_name, index: concatenate_index(frames))
73
+ end
74
+
75
+ # ---- shared validators -------------------------------------------------
76
+
77
+ def self.check_concat_inputs(frames, verb:)
78
+ raise ArgumentError, "#{verb} requires at least one frame" if frames.empty?
79
+ unless frames.all? { |f| f.is_a?(CAFrame) }
80
+ raise ArgumentError, "#{verb} expects CAFrame arguments"
81
+ end
82
+ end
83
+ private_class_method :check_concat_inputs
84
+
85
+ def self.check_column_sets(frames, names, verb:)
86
+ expected = names.sort
87
+ frames.each_with_index do |f, i|
88
+ next if i.zero?
89
+ if f.variable_names.sort != expected
90
+ raise ArgumentError,
91
+ "#{verb}: frame #{i} has columns #{f.variable_names.inspect}, " \
92
+ "expected the same set as #{names.inspect}"
93
+ end
94
+ end
95
+ end
96
+ private_class_method :check_column_sets
97
+
98
+ # ---- index helpers -----------------------------------------------------
99
+
100
+ # Index welded via CArray.meld (view). Every frame must agree: all
101
+ # indexed (axis names must match) or none.
102
+ def self.meld_index(frames)
103
+ indexes, axis = index_pieces(frames, verb: "meld")
104
+ return nil unless indexes
105
+ axis # unused; kept for symmetry
106
+ CArray.meld(indexes, axis: 0)
107
+ end
108
+ private_class_method :meld_index
109
+
110
+ # Index concatenated eagerly via CArray.concatenate.
111
+ def self.concatenate_index(frames)
112
+ indexes, _axis = index_pieces(frames, verb: "concatenate")
113
+ return nil unless indexes
114
+ CArray.concatenate(indexes)
115
+ end
116
+ private_class_method :concatenate_index
117
+
118
+ def self.index_pieces(frames, verb:)
119
+ indexes = frames.map(&:index)
120
+ return nil if indexes.none?
121
+ unless indexes.all?
122
+ raise ArgumentError, "#{verb}: some frames have an index and others do not"
123
+ end
124
+ axis_names = frames.map(&:axis_name).uniq
125
+ unless axis_names.size == 1
126
+ raise ArgumentError,
127
+ "#{verb}: indexed frames have different axis names #{axis_names.inspect}"
128
+ end
129
+ [indexes, axis_names.first]
130
+ end
131
+ private_class_method :index_pieces
132
+ end
@@ -0,0 +1,95 @@
1
+ # CAFrame row iteration and matrix conversion (memo §11.9, §11.11).
2
+
3
+ class CAFrame
4
+ # Iterate rows as Ruby Hashes (memo §11.11). This is an escape path — the
5
+ # primary idiom is column-vectorized work (§4.3); each_row is for touching
6
+ # heterogeneous / Face-carrying rows now and then, not hot loops. Without a
7
+ # block, returns an Enumerator.
8
+ def each_row
9
+ return enum_for(:each_row) unless block_given?
10
+ nrow.times { |i| yield row(i) }
11
+ self
12
+ end
13
+
14
+ # Export rows as an Array of plain Ruby Hashes (memo §11.11) — the inverse of
15
+ # +from_records+ and the shape +JSON.generate+ wants. Each row is one Hash of
16
+ # column name => value; a scalar cell is a Ruby value, an N-D cell is a Ruby
17
+ # Array, and a masked cell (UNDEF) is +nil+. Normalizing UNDEF -> nil and
18
+ # CArray -> Array (unlike +each_row+, which yields the raw view with UNDEF /
19
+ # CArray slices) is what makes it round-trip: +from_records(df.to_records)+
20
+ # rebuilds the same typing, and the result is JSON-serializable.
21
+ def to_records
22
+ each_row.map { |row| row.transform_values { |v| record_value(v) } }
23
+ end
24
+
25
+ # Hand the frame over as a CArray with the minimum work (memo §11.9): a
26
+ # 2-D **view** of shape (nrow, nvar), one column per variable in column
27
+ # order (§12-C). Nothing is materialised — the result is a +CAStack+ over
28
+ # the stored columns, so reads gather from them and writes flow back
29
+ # (§3.6). Call +copy+ on it for an independent, owned matrix; +CArray.tabulate+
30
+ # is the eager sibling that builds one directly.
31
+ #
32
+ # Only same-shape scalar (1-D) columns qualify. An N-D column has no single
33
+ # matrix form and raises — escape it per column with +df["name"]+. A mixed
34
+ # data type set is promoted to a common type (+result_type+, §12-F) through
35
+ # lazy cast lanes, so the promotion costs no buffer either.
36
+ #
37
+ # +writable: true+ demands a result whose writes reach this frame's own
38
+ # columns (the 3.0 +to_ca+ contract). That holds only when every column
39
+ # enters the stack unchanged, so it is refused when a column is read-only
40
+ # or when the common type promotes it: the promoted column is stacked
41
+ # through a cast lane, and a write there is no longer the value the caller
42
+ # handed over. Cast the frame first (+df.cast+) when the promoted matrix is
43
+ # what should be written to.
44
+ def to_ca(writable: false)
45
+ cols = @columns.values
46
+ raise ArgumentError, "frame has no columns to stack into a matrix" if cols.empty?
47
+ nd = @columns.find { |_, c| c.ndim != 1 }
48
+ if nd
49
+ raise ArgumentError,
50
+ "to_ca needs all-scalar (1-D) columns; #{nd.first.inspect} is " \
51
+ "#{nd.last.ndim}-D — escape per column with df[name]"
52
+ end
53
+ refuse_unshared_columns(cols) if writable
54
+ begin
55
+ CArray.stack(cols, axis: 1)
56
+ rescue ArgumentError, RuntimeError => e
57
+ # The columns have no common type (a text or Face column beside a
58
+ # numeric one). Point at the frame-level verb that gives them one.
59
+ raise e.class,
60
+ "#{e.message} -- df.promote(:object) brings every column to its " \
61
+ "surface values, and that frame stacks"
62
+ end
63
+ end
64
+
65
+ # The +writable: true+ half of +to_ca+: raise unless the stack's parents are
66
+ # the stored columns themselves. +promote_list+ hands back each column
67
+ # untouched when no coercion is needed, so identity is the exact test for
68
+ # "this column is shared, not re-expressed".
69
+ private def refuse_unshared_columns(cols)
70
+ ro = @columns.find { |_, c| c.read_only? }
71
+ if ro
72
+ raise "CAFrame#to_ca cannot satisfy `writable: true': column " \
73
+ "#{ro.first.inspect} is read-only"
74
+ end
75
+ promoted = CArray.promote_list(cols)
76
+ recast = @columns.keys.zip(cols, promoted).find { |_, col, p| !p.equal?(col) }
77
+ if recast
78
+ raise "CAFrame#to_ca cannot satisfy `writable: true': column " \
79
+ "#{recast[0].inspect} (#{recast[1].data_type}) is promoted to " \
80
+ "#{recast[2].data_type} for the common type, so it is stacked " \
81
+ "through a cast lane; cast the frame first (df.cast) or take the " \
82
+ "column with df[name]"
83
+ end
84
+ end
85
+
86
+ private def record_value(v)
87
+ if UNDEF.equal?(v)
88
+ nil
89
+ elsif v.is_a?(CArray)
90
+ v.to_a.map { |e| UNDEF.equal?(e) ? nil : e }
91
+ else
92
+ v
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,211 @@
1
+ # A small, self-contained CSV tokenizer for CAFrame.from_csv.
2
+ #
3
+ # It produces raw String cells and does no type inference — casting is a
4
+ # separate step. The tokenizer is tuned for the
5
+ # common shape of scientific / ETL CSV: records with no quote character take a
6
+ # single String#split fast path, and only quote-bearing records fall back to
7
+ # the field scanner. Quoted fields carry embedded separators, embedded newlines
8
+ # (multi-line records) and doubled-quote ("") escapes.
9
+ #
10
+ # Correctness choices:
11
+ # * RFC 4180 spacing by default -- spaces are significant and preserved.
12
+ # Pass +strip: true+ to trim unquoted fields (handy for numeric columns).
13
+ # * An empty unquoted field is nil (missing); an empty quoted field ("") is
14
+ # the empty String. Both cast to UNDEF for numeric columns (parse-mask).
15
+ # * A UTF-8 BOM is stripped via the "bom|utf-8" read mode by default.
16
+ # * A parse error names the record number.
17
+ #
18
+ # Two entry points share the Tokenizer:
19
+ # * CSVParser.parse / parse_file -- whole-file parse into [headers, rows].
20
+ # * CSVReader -- the block reading-control DSL used by
21
+ # CAFrame.from_csv (header / skip / body /
22
+ # column_names).
23
+
24
+ require "strscan"
25
+
26
+ class CAFrame
27
+ # CSV tokenizer behind `CAFrame.from_csv`. It produces raw String cells
28
+ # and does no type inference — casting is a separate step.
29
+ #
30
+ # Records with no quote character take a `String#split` fast path; only
31
+ # quote-bearing records fall back to the field scanner, which handles
32
+ # embedded separators, embedded newlines and doubled-quote escapes.
33
+ # Spacing follows RFC 4180 (significant and preserved) unless `strip:` is
34
+ # given. An empty unquoted field is `nil` (missing); an empty quoted field
35
+ # is the empty String.
36
+ module CSVParser
37
+ module_function
38
+
39
+ # Raised when the input cannot be tokenized, e.g. a quoted field that
40
+ # never closes before end of input.
41
+ class MalformedCSV < StandardError; end
42
+
43
+ # Parse a file into [headers, rows]. +encoding+ is an IO open-mode encoding
44
+ # string; the default strips a leading BOM and reads UTF-8.
45
+ def parse_file(path, encoding: "bom|utf-8", **opts)
46
+ File.open(path, "r:#{encoding}") { |io| parse(io, **opts) }
47
+ end
48
+
49
+ # Parse an IO (or anything answering +gets+). The first record supplies the
50
+ # headers; the rest are data rows. Fully blank lines are skipped.
51
+ def parse(io, sep: ",", quote: '"', strip: false)
52
+ tok = Tokenizer.new(sep, quote, strip)
53
+ headers = nil
54
+ rows = []
55
+ while (fields = tok.read(io))
56
+ if headers.nil?
57
+ headers = fields.map(&:to_s)
58
+ else
59
+ rows << fields
60
+ end
61
+ end
62
+ [headers, rows]
63
+ end
64
+
65
+ # Read one logical record, joining continuation lines while a quoted field
66
+ # is still open (an odd number of quote characters means unbalanced).
67
+ def read_record(io, quote)
68
+ line = io.gets
69
+ return nil if line.nil?
70
+ rec = line.dup
71
+ while rec.count(quote).odd?
72
+ more = io.gets
73
+ raise MalformedCSV, "unterminated quoted field at end of input" if more.nil?
74
+ rec << more
75
+ end
76
+ rec.chomp!
77
+ rec
78
+ end
79
+
80
+ # Splits records into fields. Regexps are compiled once and reused across
81
+ # every record, so per-row cost stays low.
82
+ class Tokenizer
83
+ def initialize(sep, quote, strip)
84
+ @sep = sep
85
+ @quote = quote
86
+ @strip = strip
87
+ @sep_re = /#{Regexp.escape(sep)}/
88
+ @quote_re = /#{Regexp.escape(quote)}/
89
+ @unquoted = /[^#{Regexp.escape(sep)}]*/
90
+ @inner = /[^#{Regexp.escape(quote)}]*/
91
+ @recno = 0
92
+ end
93
+
94
+ # Fields of the next non-blank record, or nil at EOF.
95
+ def read(io)
96
+ loop do
97
+ rec = CSVParser.read_record(io, @quote)
98
+ return nil if rec.nil?
99
+ @recno += 1
100
+ next if rec.empty?
101
+ return rec.count(@quote).zero? ? simple(rec) : scan(rec)
102
+ end
103
+ end
104
+
105
+ private def simple(rec)
106
+ rec.split(@sep, -1).map do |cell|
107
+ cell = cell.strip if @strip
108
+ cell.empty? ? nil : cell
109
+ end
110
+ end
111
+
112
+ private def scan(rec)
113
+ sc = StringScanner.new(rec)
114
+ fields = []
115
+ loop do
116
+ if sc.scan(@quote_re)
117
+ buf = +""
118
+ loop do
119
+ buf << sc.scan(@inner)
120
+ unless sc.scan(@quote_re)
121
+ raise MalformedCSV, "unterminated quoted field in record #{@recno}"
122
+ end
123
+ if sc.scan(@quote_re) # doubled quote -> literal quote, field continues
124
+ buf << @quote
125
+ else
126
+ break
127
+ end
128
+ end
129
+ fields << buf
130
+ else
131
+ cell = sc.scan(@unquoted) || ""
132
+ cell = cell.strip if @strip
133
+ fields << (cell.empty? ? nil : cell)
134
+ end
135
+ break unless sc.scan(@sep_re)
136
+ end
137
+ fields
138
+ end
139
+ end
140
+ end
141
+
142
+ # The block reading-control DSL for CAFrame.from_csv. A file
143
+ # often has preamble lines, a units row, or no header at all; the block says,
144
+ # in order, how to consume the stream:
145
+ #
146
+ # CAFrame.from_csv(path) do
147
+ # skip 3 # drop 3 preamble lines
148
+ # header # next record supplies the column names
149
+ # skip 1 # drop a units row
150
+ # body # the rest are data rows
151
+ # end
152
+ #
153
+ # CAFrame.from_csv(path) do # headerless file
154
+ # column_names "date", "temp", "rh"
155
+ # body
156
+ # end
157
+ #
158
+ # The verbs are +skip+ / +header+ / +column_names+ / +body+; each returns a
159
+ # value useful inline (header returns its fields) and the ordering is the
160
+ # script. Without a block, from_csv runs the default +header+ then +body+.
161
+ class CSVReader
162
+ def initialize(io, sep: ",", quote: '"', strip: false)
163
+ @io = io
164
+ @tok = CSVParser::Tokenizer.new(sep, quote, strip)
165
+ @names = nil
166
+ @rows = nil
167
+ end
168
+
169
+ # Drop +n+ raw lines (preamble, units, notes).
170
+ def skip(n = 1)
171
+ n.times { @io.gets }
172
+ self
173
+ end
174
+
175
+ # Read one record. With no argument it becomes the column names; with a
176
+ # name it is a secondary header (e.g. units) -- read, returned, not used as
177
+ # names. Returns the record's fields either way.
178
+ def header(name = nil)
179
+ fields = @tok.read(@io)
180
+ raise CSVParser::MalformedCSV, "header expected but input ended" if fields.nil?
181
+ if name.nil?
182
+ @names = fields.map(&:to_s)
183
+ else
184
+ (@named_headers ||= {})[name.to_s] = fields
185
+ end
186
+ fields
187
+ end
188
+
189
+ # Set the column names explicitly (headerless files).
190
+ def column_names(*names)
191
+ @names = names.flatten.map(&:to_s)
192
+ self
193
+ end
194
+
195
+ # Consume the remaining records as data rows.
196
+ def body
197
+ rows = []
198
+ while (fields = @tok.read(@io))
199
+ rows << fields
200
+ end
201
+ @rows = rows
202
+ self
203
+ end
204
+
205
+ # [names_or_nil, rows] for CAFrame.from_csv to build from. names is nil when
206
+ # neither header nor column_names ran (positional names are generated).
207
+ def result
208
+ [@names, @rows || []]
209
+ end
210
+ end
211
+ end