polars-df 0.15.0 → 0.26.0

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +274 -0
  3. data/Cargo.lock +1465 -867
  4. data/Cargo.toml +3 -0
  5. data/LICENSE.txt +2 -2
  6. data/README.md +87 -37
  7. data/ext/polars/Cargo.toml +47 -16
  8. data/ext/polars/src/c_api/allocator.rs +7 -0
  9. data/ext/polars/src/c_api/mod.rs +1 -0
  10. data/ext/polars/src/catalog/mod.rs +1 -0
  11. data/ext/polars/src/catalog/unity.rs +470 -0
  12. data/ext/polars/src/conversion/any_value.rs +99 -84
  13. data/ext/polars/src/conversion/categorical.rs +30 -0
  14. data/ext/polars/src/conversion/chunked_array.rs +71 -62
  15. data/ext/polars/src/conversion/datetime.rs +63 -0
  16. data/ext/polars/src/conversion/mod.rs +796 -312
  17. data/ext/polars/src/dataframe/construction.rs +6 -18
  18. data/ext/polars/src/dataframe/export.rs +30 -39
  19. data/ext/polars/src/dataframe/general.rs +294 -362
  20. data/ext/polars/src/dataframe/io.rs +33 -150
  21. data/ext/polars/src/dataframe/map.rs +175 -0
  22. data/ext/polars/src/dataframe/mod.rs +37 -5
  23. data/ext/polars/src/dataframe/serde.rs +23 -8
  24. data/ext/polars/src/error.rs +44 -7
  25. data/ext/polars/src/exceptions.rs +21 -8
  26. data/ext/polars/src/expr/array.rs +86 -22
  27. data/ext/polars/src/expr/binary.rs +50 -1
  28. data/ext/polars/src/expr/bitwise.rs +39 -0
  29. data/ext/polars/src/expr/categorical.rs +20 -0
  30. data/ext/polars/src/expr/datatype.rs +51 -0
  31. data/ext/polars/src/expr/datetime.rs +99 -41
  32. data/ext/polars/src/expr/extension.rs +12 -0
  33. data/ext/polars/src/expr/general.rs +252 -128
  34. data/ext/polars/src/expr/list.rs +56 -60
  35. data/ext/polars/src/expr/meta.rs +30 -35
  36. data/ext/polars/src/expr/mod.rs +28 -6
  37. data/ext/polars/src/expr/name.rs +29 -14
  38. data/ext/polars/src/expr/rolling.rs +111 -3
  39. data/ext/polars/src/expr/selector.rs +219 -0
  40. data/ext/polars/src/expr/serde.rs +28 -0
  41. data/ext/polars/src/expr/string.rs +118 -20
  42. data/ext/polars/src/expr/struct.rs +14 -1
  43. data/ext/polars/src/file.rs +194 -86
  44. data/ext/polars/src/functions/aggregation.rs +13 -12
  45. data/ext/polars/src/functions/business.rs +2 -3
  46. data/ext/polars/src/functions/eager.rs +3 -2
  47. data/ext/polars/src/functions/io.rs +90 -18
  48. data/ext/polars/src/functions/lazy.rs +267 -118
  49. data/ext/polars/src/functions/meta.rs +8 -7
  50. data/ext/polars/src/functions/misc.rs +1 -1
  51. data/ext/polars/src/functions/mod.rs +2 -1
  52. data/ext/polars/src/functions/range.rs +88 -31
  53. data/ext/polars/src/functions/strings.rs +6 -0
  54. data/ext/polars/src/functions/utils.rs +8 -0
  55. data/ext/polars/src/interop/arrow/mod.rs +52 -1
  56. data/ext/polars/src/interop/arrow/{to_ruby.rs → to_rb.rs} +37 -7
  57. data/ext/polars/src/interop/arrow/to_rust.rs +43 -0
  58. data/ext/polars/src/interop/numo/to_numo_df.rs +1 -1
  59. data/ext/polars/src/interop/numo/to_numo_series.rs +72 -50
  60. data/ext/polars/src/io/cloud_options.rs +107 -0
  61. data/ext/polars/src/io/mod.rs +4 -0
  62. data/ext/polars/src/io/scan_options.rs +113 -0
  63. data/ext/polars/src/io/sink_options.rs +46 -0
  64. data/ext/polars/src/io/sink_output.rs +21 -0
  65. data/ext/polars/src/lazyframe/exitable.rs +39 -0
  66. data/ext/polars/src/lazyframe/general.rs +846 -368
  67. data/ext/polars/src/lazyframe/mod.rs +58 -5
  68. data/ext/polars/src/lazyframe/optflags.rs +59 -0
  69. data/ext/polars/src/lazyframe/serde.rs +36 -4
  70. data/ext/polars/src/lazyframe/sink.rs +46 -0
  71. data/ext/polars/src/lazygroupby.rs +38 -9
  72. data/ext/polars/src/lib.rs +574 -165
  73. data/ext/polars/src/map/lazy.rs +44 -74
  74. data/ext/polars/src/map/mod.rs +18 -254
  75. data/ext/polars/src/map/series.rs +241 -1087
  76. data/ext/polars/src/on_startup.rs +192 -9
  77. data/ext/polars/src/prelude.rs +1 -0
  78. data/ext/polars/src/rb_modules.rs +10 -57
  79. data/ext/polars/src/ruby/exceptions.rs +26 -0
  80. data/ext/polars/src/ruby/gvl.rs +104 -0
  81. data/ext/polars/src/ruby/lazy.rs +46 -0
  82. data/ext/polars/src/ruby/mod.rs +11 -0
  83. data/ext/polars/src/ruby/numo.rs +52 -0
  84. data/ext/polars/src/ruby/plan_callback.rs +198 -0
  85. data/ext/polars/src/ruby/rb_modules.rs +16 -0
  86. data/ext/polars/src/ruby/ruby_convert_registry.rs +51 -0
  87. data/ext/polars/src/ruby/ruby_function.rs +11 -0
  88. data/ext/polars/src/ruby/ruby_udf.rs +164 -0
  89. data/ext/polars/src/ruby/thread.rs +65 -0
  90. data/ext/polars/src/ruby/utils.rs +39 -0
  91. data/ext/polars/src/series/aggregation.rs +116 -91
  92. data/ext/polars/src/series/arithmetic.rs +16 -22
  93. data/ext/polars/src/series/comparison.rs +101 -222
  94. data/ext/polars/src/series/construction.rs +80 -70
  95. data/ext/polars/src/series/export.rs +98 -56
  96. data/ext/polars/src/series/general.rs +323 -440
  97. data/ext/polars/src/series/import.rs +22 -5
  98. data/ext/polars/src/series/map.rs +103 -0
  99. data/ext/polars/src/series/mod.rs +57 -15
  100. data/ext/polars/src/series/scatter.rs +139 -82
  101. data/ext/polars/src/sql.rs +16 -9
  102. data/ext/polars/src/testing/frame.rs +31 -0
  103. data/ext/polars/src/testing/mod.rs +5 -0
  104. data/ext/polars/src/testing/series.rs +31 -0
  105. data/ext/polars/src/timeout.rs +105 -0
  106. data/ext/polars/src/utils.rs +105 -4
  107. data/lib/polars/array_expr.rb +500 -22
  108. data/lib/polars/array_name_space.rb +384 -10
  109. data/lib/polars/batched_csv_reader.rb +48 -66
  110. data/lib/polars/binary_expr.rb +217 -0
  111. data/lib/polars/binary_name_space.rb +155 -1
  112. data/lib/polars/cat_expr.rb +224 -0
  113. data/lib/polars/cat_name_space.rb +132 -32
  114. data/lib/polars/catalog/unity/catalog_info.rb +20 -0
  115. data/lib/polars/catalog/unity/column_info.rb +31 -0
  116. data/lib/polars/catalog/unity/namespace_info.rb +21 -0
  117. data/lib/polars/catalog/unity/table_info.rb +50 -0
  118. data/lib/polars/catalog.rb +448 -0
  119. data/lib/polars/collect_batches.rb +22 -0
  120. data/lib/polars/config.rb +3 -3
  121. data/lib/polars/convert.rb +201 -36
  122. data/lib/polars/data_frame.rb +2851 -1017
  123. data/lib/polars/data_frame_plot.rb +173 -0
  124. data/lib/polars/data_type_expr.rb +52 -0
  125. data/lib/polars/data_type_group.rb +6 -0
  126. data/lib/polars/data_types.rb +118 -18
  127. data/lib/polars/date_time_expr.rb +426 -84
  128. data/lib/polars/date_time_name_space.rb +384 -111
  129. data/lib/polars/dynamic_group_by.rb +102 -10
  130. data/lib/polars/exceptions.rb +50 -5
  131. data/lib/polars/expr.rb +2159 -915
  132. data/lib/polars/extension_expr.rb +39 -0
  133. data/lib/polars/extension_name_space.rb +39 -0
  134. data/lib/polars/functions/aggregation/horizontal.rb +11 -6
  135. data/lib/polars/functions/aggregation/vertical.rb +2 -3
  136. data/lib/polars/functions/as_datatype.rb +290 -8
  137. data/lib/polars/functions/business.rb +95 -0
  138. data/lib/polars/functions/col.rb +6 -5
  139. data/lib/polars/functions/datatype.rb +62 -0
  140. data/lib/polars/functions/eager.rb +426 -24
  141. data/lib/polars/functions/escape_regex.rb +21 -0
  142. data/lib/polars/functions/lazy.rb +813 -195
  143. data/lib/polars/functions/lit.rb +21 -10
  144. data/lib/polars/functions/range/int_range.rb +74 -2
  145. data/lib/polars/functions/range/linear_space.rb +195 -0
  146. data/lib/polars/functions/range/time_range.rb +1 -1
  147. data/lib/polars/functions/repeat.rb +7 -12
  148. data/lib/polars/functions/whenthen.rb +2 -2
  149. data/lib/polars/group_by.rb +188 -58
  150. data/lib/polars/iceberg_dataset.rb +108 -0
  151. data/lib/polars/in_process_query.rb +37 -0
  152. data/lib/polars/io/cloud.rb +18 -0
  153. data/lib/polars/io/csv.rb +336 -128
  154. data/lib/polars/io/database.rb +19 -4
  155. data/lib/polars/io/delta.rb +134 -0
  156. data/lib/polars/io/iceberg.rb +34 -0
  157. data/lib/polars/io/ipc.rb +63 -63
  158. data/lib/polars/io/json.rb +16 -0
  159. data/lib/polars/io/lines.rb +172 -0
  160. data/lib/polars/io/ndjson.rb +176 -20
  161. data/lib/polars/io/parquet.rb +173 -95
  162. data/lib/polars/io/scan_options.rb +55 -0
  163. data/lib/polars/io/sink_options.rb +27 -0
  164. data/lib/polars/io/utils.rb +17 -0
  165. data/lib/polars/lazy_frame.rb +3017 -622
  166. data/lib/polars/lazy_group_by.rb +436 -2
  167. data/lib/polars/list_expr.rb +551 -59
  168. data/lib/polars/list_name_space.rb +465 -51
  169. data/lib/polars/meta_expr.rb +146 -24
  170. data/lib/polars/name_expr.rb +87 -2
  171. data/lib/polars/query_opt_flags.rb +264 -0
  172. data/lib/polars/rolling_group_by.rb +90 -5
  173. data/lib/polars/scan_cast_options.rb +86 -0
  174. data/lib/polars/schema.rb +128 -0
  175. data/lib/polars/selector.rb +245 -0
  176. data/lib/polars/selectors.rb +1048 -201
  177. data/lib/polars/series.rb +2522 -774
  178. data/lib/polars/series_plot.rb +72 -0
  179. data/lib/polars/slice.rb +1 -1
  180. data/lib/polars/sql_context.rb +13 -6
  181. data/lib/polars/string_cache.rb +19 -72
  182. data/lib/polars/string_expr.rb +561 -107
  183. data/lib/polars/string_name_space.rb +781 -109
  184. data/lib/polars/struct_expr.rb +139 -18
  185. data/lib/polars/struct_name_space.rb +19 -1
  186. data/lib/polars/testing.rb +24 -273
  187. data/lib/polars/utils/constants.rb +2 -0
  188. data/lib/polars/utils/construction/data_frame.rb +410 -0
  189. data/lib/polars/utils/construction/series.rb +350 -0
  190. data/lib/polars/utils/construction/utils.rb +9 -0
  191. data/lib/polars/utils/convert.rb +18 -8
  192. data/lib/polars/utils/deprecation.rb +11 -0
  193. data/lib/polars/utils/parse.rb +62 -9
  194. data/lib/polars/utils/reduce_balanced.rb +43 -0
  195. data/lib/polars/utils/serde.rb +22 -0
  196. data/lib/polars/utils/unstable.rb +19 -0
  197. data/lib/polars/utils/various.rb +86 -1
  198. data/lib/polars/utils.rb +63 -48
  199. data/lib/polars/version.rb +1 -1
  200. data/lib/polars.rb +85 -2
  201. metadata +80 -28
  202. data/ext/polars/src/allocator.rs +0 -13
  203. data/ext/polars/src/batched_csv.rs +0 -138
  204. data/ext/polars/src/functions/string_cache.rs +0 -25
  205. data/ext/polars/src/map/dataframe.rs +0 -338
  206. data/lib/polars/plot.rb +0 -109
@@ -63,6 +63,13 @@ module Polars
63
63
  # in the target string.
64
64
  # @param cache [Boolean]
65
65
  # Use a cache of unique, converted datetimes to apply the conversion.
66
+ # @param ambiguous ['raise', 'earliest', 'latest', 'null']
67
+ # Determine how to deal with ambiguous datetimes:
68
+ #
69
+ # - `'raise'` (default): raise
70
+ # - `'earliest'`: use the earliest datetime
71
+ # - `'latest'`: use the latest datetime
72
+ # - `'null'`: set to null
66
73
  #
67
74
  # @return [Expr]
68
75
  #
@@ -145,9 +152,15 @@ module Polars
145
152
  # @param exact [Boolean]
146
153
  # - If true, require an exact format match.
147
154
  # - If false, allow the format to match anywhere in the target string.
148
- # @param utc [Boolean]
149
- # Parse timezone aware datetimes as UTC. This may be useful if you have data
150
- # with mixed offsets.
155
+ # @param cache [Boolean]
156
+ # Use a cache of unique, converted dates to apply the datetime conversion.
157
+ # @param ambiguous ['raise', 'earliest', 'latest', 'null']
158
+ # Determine how to deal with ambiguous datetimes:
159
+ #
160
+ # - `'raise'` (default): raise
161
+ # - `'earliest'`: use the earliest datetime
162
+ # - `'latest'`: use the latest datetime
163
+ # - `'null'`: set to null
151
164
  #
152
165
  # @return [Expr]
153
166
  #
@@ -194,7 +207,14 @@ module Polars
194
207
  # # 2022-01-31
195
208
  # # 2001-07-08
196
209
  # # ]
197
- def strptime(dtype, format = nil, strict: true, exact: true, cache: true, utc: false)
210
+ def strptime(
211
+ dtype,
212
+ format = nil,
213
+ strict: true,
214
+ exact: true,
215
+ cache: true,
216
+ ambiguous: "raise"
217
+ )
198
218
  _validate_format_argument(format)
199
219
 
200
220
  if dtype == Date
@@ -203,7 +223,15 @@ module Polars
203
223
  dtype = Datetime.new if dtype == Datetime
204
224
  time_unit = dtype.time_unit
205
225
  time_zone = dtype.time_zone
206
- to_datetime(format, time_unit: time_unit, time_zone: time_zone, strict: strict, exact: exact, cache: cache)
226
+ to_datetime(
227
+ format,
228
+ time_unit: time_unit,
229
+ time_zone: time_zone,
230
+ strict: strict,
231
+ exact: exact,
232
+ cache: cache,
233
+ ambiguous: ambiguous
234
+ )
207
235
  elsif dtype == Time
208
236
  to_time(format, strict: strict, cache: cache)
209
237
  else
@@ -213,10 +241,8 @@ module Polars
213
241
 
214
242
  # Convert a String column into a Decimal column.
215
243
  #
216
- # This method infers the needed parameters `precision` and `scale`.
217
- #
218
- # @param inference_length [Integer]
219
- # Number of elements to parse to determine the `precision` and `scale`.
244
+ # @param scale [Integer]
245
+ # Number of digits after the comma to use for the decimals.
220
246
  #
221
247
  # @return [Expr]
222
248
  #
@@ -234,13 +260,13 @@ module Polars
234
260
  # ]
235
261
  # }
236
262
  # )
237
- # df.with_columns(numbers_decimal: Polars.col("numbers").str.to_decimal)
263
+ # df.with_columns(numbers_decimal: Polars.col("numbers").str.to_decimal(scale: 2))
238
264
  # # =>
239
265
  # # shape: (7, 2)
240
266
  # # ┌───────────┬─────────────────┐
241
267
  # # │ numbers ┆ numbers_decimal │
242
268
  # # │ --- ┆ --- │
243
- # # │ str ┆ decimal[*,2]
269
+ # # │ str ┆ decimal[38,2]
244
270
  # # ╞═══════════╪═════════════════╡
245
271
  # # │ 40.12 ┆ 40.12 │
246
272
  # # │ 3420.13 ┆ 3420.13 │
@@ -250,8 +276,8 @@ module Polars
250
276
  # # │ 143.09 ┆ 143.09 │
251
277
  # # │ 143.9 ┆ 143.90 │
252
278
  # # └───────────┴─────────────────┘
253
- def to_decimal(inference_length = 100)
254
- Utils.wrap_expr(_rbexpr.str_to_decimal(inference_length))
279
+ def to_decimal(scale:)
280
+ Utils.wrap_expr(_rbexpr.str_to_decimal(scale))
255
281
  end
256
282
 
257
283
  # Get length of the strings as `:u32` (as number of bytes).
@@ -285,7 +311,6 @@ module Polars
285
311
  def len_bytes
286
312
  Utils.wrap_expr(_rbexpr.str_len_bytes)
287
313
  end
288
- alias_method :lengths, :len_bytes
289
314
 
290
315
  # Get length of the strings as `:u32` (as number of chars).
291
316
  #
@@ -318,7 +343,6 @@ module Polars
318
343
  def len_chars
319
344
  Utils.wrap_expr(_rbexpr.str_len_chars)
320
345
  end
321
- alias_method :n_chars, :len_chars
322
346
 
323
347
  # Vertically concat the values in the Series to a single string value.
324
348
  #
@@ -354,10 +378,74 @@ module Polars
354
378
  # # ╞══════╡
355
379
  # # │ null │
356
380
  # # └──────┘
357
- def join(delimiter = "-", ignore_nulls: true)
381
+ def join(delimiter = "", ignore_nulls: true)
358
382
  Utils.wrap_expr(_rbexpr.str_join(delimiter, ignore_nulls))
359
383
  end
360
- alias_method :concat, :join
384
+
385
+ # Returns string values with all regular expression meta characters escaped.
386
+ #
387
+ # @return [Expr]
388
+ #
389
+ # @example
390
+ # df = Polars::DataFrame.new({"text" => ["abc", "def", nil, "abc(\\w+)"]})
391
+ # df.with_columns(Polars.col("text").str.escape_regex.alias("escaped"))
392
+ # # =>
393
+ # # shape: (4, 2)
394
+ # # ┌──────────┬──────────────┐
395
+ # # │ text ┆ escaped │
396
+ # # │ --- ┆ --- │
397
+ # # │ str ┆ str │
398
+ # # ╞══════════╪══════════════╡
399
+ # # │ abc ┆ abc │
400
+ # # │ def ┆ def │
401
+ # # │ null ┆ null │
402
+ # # │ abc(\w+) ┆ abc\(\\w\+\) │
403
+ # # └──────────┴──────────────┘
404
+ def escape_regex
405
+ Utils.wrap_expr(_rbexpr.str_escape_regex)
406
+ end
407
+
408
+ # Returns the Unicode normal form of the string values.
409
+ #
410
+ # This uses the forms described in Unicode Standard Annex 15: <https://www.unicode.org/reports/tr15/>.
411
+ #
412
+ # @param form ['NFC', 'NFKC', 'NFD', 'NFKD']
413
+ # Unicode form to use.
414
+ #
415
+ # @return [Expr]
416
+ #
417
+ # @example
418
+ # df = Polars::DataFrame.new({"text" => ["01²", "KADOKAWA"]})
419
+ # new = df.with_columns(
420
+ # nfc: Polars.col("text").str.normalize("NFC"),
421
+ # nfkc: Polars.col("text").str.normalize("NFKC")
422
+ # )
423
+ # # =>
424
+ # # shape: (2, 3)
425
+ # # ┌──────────────────┬──────────────────┬──────────┐
426
+ # # │ text ┆ nfc ┆ nfkc │
427
+ # # │ --- ┆ --- ┆ --- │
428
+ # # │ str ┆ str ┆ str │
429
+ # # ╞══════════════════╪══════════════════╪══════════╡
430
+ # # │ 01² ┆ 01² ┆ 012 │
431
+ # # │ KADOKAWA ┆ KADOKAWA ┆ KADOKAWA │
432
+ # # └──────────────────┴──────────────────┴──────────┘
433
+ #
434
+ # @example
435
+ # new.select(Polars.all.str.len_bytes)
436
+ # # =>
437
+ # # shape: (2, 3)
438
+ # # ┌──────┬─────┬──────┐
439
+ # # │ text ┆ nfc ┆ nfkc │
440
+ # # │ --- ┆ --- ┆ --- │
441
+ # # │ u32 ┆ u32 ┆ u32 │
442
+ # # ╞══════╪═════╪══════╡
443
+ # # │ 4 ┆ 4 ┆ 3 │
444
+ # # │ 24 ┆ 24 ┆ 8 │
445
+ # # └──────┴─────┴──────┘
446
+ def normalize(form = "NFC")
447
+ Utils.wrap_expr(_rbexpr.str_normalize(form))
448
+ end
361
449
 
362
450
  # Transform to uppercase variant.
363
451
  #
@@ -433,7 +521,7 @@ module Polars
433
521
  #
434
522
  # @example
435
523
  # df = Polars::DataFrame.new({"foo" => [" lead", "trail ", " both "]})
436
- # df.select(Polars.col("foo").str.strip)
524
+ # df.select(Polars.col("foo").str.strip_chars)
437
525
  # # =>
438
526
  # # shape: (3, 1)
439
527
  # # ┌───────┐
@@ -449,7 +537,6 @@ module Polars
449
537
  characters = Utils.parse_into_expression(characters, str_as_lit: true)
450
538
  Utils.wrap_expr(_rbexpr.str_strip_chars(characters))
451
539
  end
452
- alias_method :strip, :strip_chars
453
540
 
454
541
  # Remove leading whitespace.
455
542
  #
@@ -460,7 +547,7 @@ module Polars
460
547
  #
461
548
  # @example
462
549
  # df = Polars::DataFrame.new({"foo" => [" lead", "trail ", " both "]})
463
- # df.select(Polars.col("foo").str.lstrip)
550
+ # df.select(Polars.col("foo").str.strip_chars_start)
464
551
  # # =>
465
552
  # # shape: (3, 1)
466
553
  # # ┌────────┐
@@ -476,7 +563,6 @@ module Polars
476
563
  characters = Utils.parse_into_expression(characters, str_as_lit: true)
477
564
  Utils.wrap_expr(_rbexpr.str_strip_chars_start(characters))
478
565
  end
479
- alias_method :lstrip, :strip_chars_start
480
566
 
481
567
  # Remove trailing whitespace.
482
568
  #
@@ -487,7 +573,7 @@ module Polars
487
573
  #
488
574
  # @example
489
575
  # df = Polars::DataFrame.new({"foo" => [" lead", "trail ", " both "]})
490
- # df.select(Polars.col("foo").str.rstrip)
576
+ # df.select(Polars.col("foo").str.strip_chars_end)
491
577
  # # =>
492
578
  # # shape: (3, 1)
493
579
  # # ┌───────┐
@@ -503,7 +589,6 @@ module Polars
503
589
  characters = Utils.parse_into_expression(characters, str_as_lit: true)
504
590
  Utils.wrap_expr(_rbexpr.str_strip_chars_end(characters))
505
591
  end
506
- alias_method :rstrip, :strip_chars_end
507
592
 
508
593
  # Remove prefix.
509
594
  #
@@ -590,9 +675,9 @@ module Polars
590
675
  # # │ null ┆ null │
591
676
  # # └──────────────┴──────────────┘
592
677
  def pad_start(length, fill_char = " ")
678
+ length = Utils.parse_into_expression(length)
593
679
  Utils.wrap_expr(_rbexpr.str_pad_start(length, fill_char))
594
680
  end
595
- alias_method :rjust, :pad_start
596
681
 
597
682
  # Pad the end of the string until it reaches the given length.
598
683
  #
@@ -620,9 +705,9 @@ module Polars
620
705
  # # │ null ┆ null │
621
706
  # # └──────────────┴──────────────┘
622
707
  def pad_end(length, fill_char = " ")
708
+ length = Utils.parse_into_expression(length)
623
709
  Utils.wrap_expr(_rbexpr.str_pad_end(length, fill_char))
624
710
  end
625
- alias_method :ljust, :pad_end
626
711
 
627
712
  # Fills the string with zeroes.
628
713
  #
@@ -664,6 +749,9 @@ module Polars
664
749
  # A valid regex pattern.
665
750
  # @param literal [Boolean]
666
751
  # Treat pattern as a literal string.
752
+ # @param strict [Boolean]
753
+ # Raise an error if the underlying pattern is not a valid regex,
754
+ # otherwise mask out with a null value.
667
755
  #
668
756
  # @return [Expr]
669
757
  #
@@ -693,16 +781,78 @@ module Polars
693
781
  Utils.wrap_expr(_rbexpr.str_contains(pattern, literal, strict))
694
782
  end
695
783
 
784
+ # Return the bytes offset of the first substring matching a pattern.
785
+ #
786
+ # If the pattern is not found, returns nil.
787
+ #
788
+ # @param pattern [String]
789
+ # A valid regular expression pattern, compatible with the [regex crate](https://docs.rs/regex/latest/regex/).
790
+ # @param literal [Boolean]
791
+ # Treat `pattern` as a literal string, not as a regular expression.
792
+ # @param strict [Boolean]
793
+ # Raise an error if the underlying pattern is not a valid regex,
794
+ # otherwise mask out with a null value.
795
+ #
796
+ # @return [Expr]
797
+ #
798
+ # @note
799
+ # To modify regular expression behaviour (such as case-sensitivity) with
800
+ # flags, use the inline `(?iLmsuxU)` syntax.
801
+ #
802
+ # @example Find the index of the first substring matching a regex or literal pattern:
803
+ # df = Polars::DataFrame.new(
804
+ # {
805
+ # "txt" => ["Crab", "Lobster", nil, "Crustacean"],
806
+ # "pat" => ["a[bc]", "b.t", "[aeiuo]", "(?i)A[BC]"]
807
+ # }
808
+ # )
809
+ # df.select(
810
+ # Polars.col("txt"),
811
+ # Polars.col("txt").str.find("a|e").alias("a|e (regex)"),
812
+ # Polars.col("txt").str.find("e", literal: true).alias("e (lit)"),
813
+ # )
814
+ # # =>
815
+ # # shape: (4, 3)
816
+ # # ┌────────────┬─────────────┬─────────┐
817
+ # # │ txt ┆ a|e (regex) ┆ e (lit) │
818
+ # # │ --- ┆ --- ┆ --- │
819
+ # # │ str ┆ u32 ┆ u32 │
820
+ # # ╞════════════╪═════════════╪═════════╡
821
+ # # │ Crab ┆ 2 ┆ null │
822
+ # # │ Lobster ┆ 5 ┆ 5 │
823
+ # # │ null ┆ null ┆ null │
824
+ # # │ Crustacean ┆ 5 ┆ 7 │
825
+ # # └────────────┴─────────────┴─────────┘
826
+ #
827
+ # @example Match against a pattern found in another column or (expression):
828
+ # df.with_columns(Polars.col("txt").str.find(Polars.col("pat")).alias("find_pat"))
829
+ # # =>
830
+ # # shape: (4, 3)
831
+ # # ┌────────────┬───────────┬──────────┐
832
+ # # │ txt ┆ pat ┆ find_pat │
833
+ # # │ --- ┆ --- ┆ --- │
834
+ # # │ str ┆ str ┆ u32 │
835
+ # # ╞════════════╪═══════════╪══════════╡
836
+ # # │ Crab ┆ a[bc] ┆ 2 │
837
+ # # │ Lobster ┆ b.t ┆ 2 │
838
+ # # │ null ┆ [aeiuo] ┆ null │
839
+ # # │ Crustacean ┆ (?i)A[BC] ┆ 5 │
840
+ # # └────────────┴───────────┴──────────┘
841
+ def find(pattern, literal: false, strict: true)
842
+ pattern = Utils.parse_into_expression(pattern, str_as_lit: true)
843
+ Utils.wrap_expr(_rbexpr.str_find(pattern, literal, strict))
844
+ end
845
+
696
846
  # Check if string values end with a substring.
697
847
  #
698
- # @param sub [String]
848
+ # @param suffix [String]
699
849
  # Suffix substring.
700
850
  #
701
851
  # @return [Expr]
702
852
  #
703
853
  # @example
704
854
  # df = Polars::DataFrame.new({"fruits" => ["apple", "mango", nil]})
705
- # df.with_column(
855
+ # df.with_columns(
706
856
  # Polars.col("fruits").str.ends_with("go").alias("has_suffix")
707
857
  # )
708
858
  # # =>
@@ -728,21 +878,21 @@ module Polars
728
878
  # # ╞════════╡
729
879
  # # │ mango │
730
880
  # # └────────┘
731
- def ends_with(sub)
732
- sub = Utils.parse_into_expression(sub, str_as_lit: true)
733
- Utils.wrap_expr(_rbexpr.str_ends_with(sub))
881
+ def ends_with(suffix)
882
+ suffix_rbexpr = Utils.parse_into_expression(suffix, str_as_lit: true)
883
+ Utils.wrap_expr(_rbexpr.str_ends_with(suffix_rbexpr))
734
884
  end
735
885
 
736
886
  # Check if string values start with a substring.
737
887
  #
738
- # @param sub [String]
888
+ # @param prefix [String]
739
889
  # Prefix substring.
740
890
  #
741
891
  # @return [Expr]
742
892
  #
743
893
  # @example
744
894
  # df = Polars::DataFrame.new({"fruits" => ["apple", "mango", nil]})
745
- # df.with_column(
895
+ # df.with_columns(
746
896
  # Polars.col("fruits").str.starts_with("app").alias("has_prefix")
747
897
  # )
748
898
  # # =>
@@ -768,9 +918,9 @@ module Polars
768
918
  # # ╞════════╡
769
919
  # # │ apple │
770
920
  # # └────────┘
771
- def starts_with(sub)
772
- sub = Utils.parse_into_expression(sub, str_as_lit: true)
773
- Utils.wrap_expr(_rbexpr.str_starts_with(sub))
921
+ def starts_with(prefix)
922
+ prefix_rbexpr = Utils.parse_into_expression(prefix, str_as_lit: true)
923
+ Utils.wrap_expr(_rbexpr.str_starts_with(prefix_rbexpr))
774
924
  end
775
925
 
776
926
  # Parse string values as JSON.
@@ -778,8 +928,9 @@ module Polars
778
928
  # Throw errors if encounter invalid JSON strings.
779
929
  #
780
930
  # @param dtype [Object]
781
- # The dtype to cast the extracted value to. If nil, the dtype will be
782
- # inferred from the JSON value.
931
+ # The dtype to cast the extracted value to.
932
+ # @param infer_schema_length [Integer]
933
+ # Deprecated and ignored.
783
934
  #
784
935
  # @return [Expr]
785
936
  #
@@ -788,25 +939,27 @@ module Polars
788
939
  # {"json" => ['{"a":1, "b": true}', nil, '{"a":2, "b": false}']}
789
940
  # )
790
941
  # dtype = Polars::Struct.new([Polars::Field.new("a", Polars::Int64), Polars::Field.new("b", Polars::Boolean)])
791
- # df.select(Polars.col("json").str.json_decode(dtype))
942
+ # df.with_columns(decoded: Polars.col("json").str.json_decode(dtype))
792
943
  # # =>
793
- # # shape: (3, 1)
794
- # # ┌───────────┐
795
- # # │ json
796
- # # │ --- │
797
- # # │ struct[2] │
798
- # # ╞═══════════╡
799
- # # │ {1,true} │
800
- # # │ null │
801
- # # │ {2,false} │
802
- # # └───────────┘
803
- def json_decode(dtype = nil, infer_schema_length: 100)
804
- if !dtype.nil?
805
- dtype = Utils.rb_type_to_dtype(dtype)
944
+ # # shape: (3, 2)
945
+ # # ┌─────────────────────┬───────────┐
946
+ # # │ json ┆ decoded
947
+ # # │ --- ┆ ---
948
+ # # │ str ┆ struct[2] │
949
+ # # ╞═════════════════════╪═══════════╡
950
+ # # │ {"a":1, "b": true} ┆ {1,true}
951
+ # # │ null ┆ null
952
+ # # │ {"a":2, "b": false} ┆ {2,false}
953
+ # # └─────────────────────┴───────────┘
954
+ def json_decode(dtype, infer_schema_length: nil)
955
+ if dtype.nil?
956
+ msg = "`Expr.str.json_decode` needs an explicitly given `dtype` otherwise Polars is not able to determine the output type. If you want to eagerly infer datatype you can use `Series.str.json_decode`."
957
+ raise TypeError, msg
806
958
  end
807
- Utils.wrap_expr(_rbexpr.str_json_decode(dtype, infer_schema_length))
959
+
960
+ dtype_expr = Utils.parse_into_datatype_expr(dtype)._rbdatatype_expr
961
+ Utils.wrap_expr(_rbexpr.str_json_decode(dtype_expr))
808
962
  end
809
- alias_method :json_extract, :json_decode
810
963
 
811
964
  # Extract the first match of json string with provided JSONPath expression.
812
965
  #
@@ -1036,6 +1189,8 @@ module Polars
1036
1189
  #
1037
1190
  # @param pattern [String]
1038
1191
  # A valid regex pattern
1192
+ # @param literal [Boolean]
1193
+ # Treat `pattern` as a literal string, not as a regular expression.
1039
1194
  #
1040
1195
  # @return [Expr]
1041
1196
  #
@@ -1043,7 +1198,7 @@ module Polars
1043
1198
  # df = Polars::DataFrame.new({"foo" => ["123 bla 45 asd", "xyz 678 910t"]})
1044
1199
  # df.select(
1045
1200
  # [
1046
- # Polars.col("foo").str.count_match('\d').alias("count_digits")
1201
+ # Polars.col("foo").str.count_matches('\d').alias("count_digits")
1047
1202
  # ]
1048
1203
  # )
1049
1204
  # # =>
@@ -1060,7 +1215,6 @@ module Polars
1060
1215
  pattern = Utils.parse_into_expression(pattern, str_as_lit: true)
1061
1216
  Utils.wrap_expr(_rbexpr.str_count_matches(pattern, literal))
1062
1217
  end
1063
- alias_method :count_match, :count_matches
1064
1218
 
1065
1219
  # Split the string by a substring.
1066
1220
  #
@@ -1068,6 +1222,11 @@ module Polars
1068
1222
  # Substring to split by.
1069
1223
  # @param inclusive [Boolean]
1070
1224
  # If true, include the split character/string in the results.
1225
+ # @param literal [Boolean]
1226
+ # Treat `by` as a literal string, not as a regular expression.
1227
+ # @param strict [Boolean]
1228
+ # Raise an error if the underlying pattern is not a valid regex,
1229
+ # otherwise mask out with a null value.
1071
1230
  #
1072
1231
  # @return [Expr]
1073
1232
  #
@@ -1085,12 +1244,22 @@ module Polars
1085
1244
  # # │ ["foo-bar"] │
1086
1245
  # # │ ["foo", "bar", "baz"] │
1087
1246
  # # └───────────────────────┘
1088
- def split(by, inclusive: false)
1089
- by = Utils.parse_into_expression(by, str_as_lit: true)
1247
+ def split(by, inclusive: false, literal: true, strict: true)
1248
+ by_rbexpr = Utils.parse_into_expression(by, str_as_lit: true)
1249
+
1250
+ if !literal
1251
+ if inclusive
1252
+ return Utils.wrap_expr(
1253
+ _rbexpr.str_split_regex_inclusive(by_rbexpr, strict)
1254
+ )
1255
+ end
1256
+ return Utils.wrap_expr(_rbexpr.str_split_regex(by_rbexpr, strict))
1257
+ end
1258
+
1090
1259
  if inclusive
1091
- return Utils.wrap_expr(_rbexpr.str_split_inclusive(by))
1260
+ return Utils.wrap_expr(_rbexpr.str_split_inclusive(by_rbexpr))
1092
1261
  end
1093
- Utils.wrap_expr(_rbexpr.str_split(by))
1262
+ Utils.wrap_expr(_rbexpr.str_split(by_rbexpr))
1094
1263
  end
1095
1264
 
1096
1265
  # Split the string by a substring using `n` splits.
@@ -1177,12 +1346,14 @@ module Polars
1177
1346
  # Replacement string.
1178
1347
  # @param literal [Boolean]
1179
1348
  # Treat pattern as a literal string.
1349
+ # @param n [Integer]
1350
+ # Number of matches to replace.
1180
1351
  #
1181
1352
  # @return [Expr]
1182
1353
  #
1183
1354
  # @example
1184
1355
  # df = Polars::DataFrame.new({"id" => [1, 2], "text" => ["123abc", "abc456"]})
1185
- # df.with_column(
1356
+ # df.with_columns(
1186
1357
  # Polars.col("text").str.replace('abc\b', "ABC")
1187
1358
  # )
1188
1359
  # # =>
@@ -1214,7 +1385,7 @@ module Polars
1214
1385
  #
1215
1386
  # @example
1216
1387
  # df = Polars::DataFrame.new({"id" => [1, 2], "text" => ["abcabc", "123a123"]})
1217
- # df.with_column(Polars.col("text").str.replace_all("a", "-"))
1388
+ # df.with_columns(Polars.col("text").str.replace_all("a", "-"))
1218
1389
  # # =>
1219
1390
  # # shape: (2, 2)
1220
1391
  # # ┌─────┬─────────┐
@@ -1265,7 +1436,7 @@ module Polars
1265
1436
  #
1266
1437
  # @example
1267
1438
  # df = Polars::DataFrame.new({"s" => ["pear", nil, "papaya", "dragonfruit"]})
1268
- # df.with_column(
1439
+ # df.with_columns(
1269
1440
  # Polars.col("s").str.slice(-3).alias("s_sliced")
1270
1441
  # )
1271
1442
  # # =>
@@ -1286,6 +1457,130 @@ module Polars
1286
1457
  Utils.wrap_expr(_rbexpr.str_slice(offset, length))
1287
1458
  end
1288
1459
 
1460
+ # Return the first n characters of each string in a String Series.
1461
+ #
1462
+ # @param n [Integer]
1463
+ # Length of the slice (integer or expression). Negative indexing is supported;
1464
+ # see note (2) below.
1465
+ #
1466
+ # @return [Expr]
1467
+ #
1468
+ # @note
1469
+ # 1) The `n` input is defined in terms of the number of characters in the (UTF8)
1470
+ # string. A character is defined as a [Unicode scalar value](https://www.unicode.org/glossary/#unicode_scalar_value). A single
1471
+ # character is represented by a single byte when working with ASCII text, and a
1472
+ # maximum of 4 bytes otherwise.
1473
+ #
1474
+ # 2) When the `n` input is negative, `head` returns characters up to the `n`th
1475
+ # from the end of the string. For example, if `n = -3`, then all characters
1476
+ # except the last three are returned.
1477
+ #
1478
+ # 3) If the length of the string has fewer than `n` characters, the full string is
1479
+ # returned.
1480
+ #
1481
+ # @example Return up to the first 5 characters:
1482
+ # df = Polars::DataFrame.new({"s" => ["pear", nil, "papaya", "dragonfruit"]})
1483
+ # df.with_columns(Polars.col("s").str.head(5).alias("s_head_5"))
1484
+ # # =>
1485
+ # # shape: (4, 2)
1486
+ # # ┌─────────────┬──────────┐
1487
+ # # │ s ┆ s_head_5 │
1488
+ # # │ --- ┆ --- │
1489
+ # # │ str ┆ str │
1490
+ # # ╞═════════════╪══════════╡
1491
+ # # │ pear ┆ pear │
1492
+ # # │ null ┆ null │
1493
+ # # │ papaya ┆ papay │
1494
+ # # │ dragonfruit ┆ drago │
1495
+ # # └─────────────┴──────────┘
1496
+ #
1497
+ # @example Return characters determined by column `n`:
1498
+ # df = Polars::DataFrame.new(
1499
+ # {
1500
+ # "s" => ["pear", nil, "papaya", "dragonfruit"],
1501
+ # "n" => [3, 4, -2, -5]
1502
+ # }
1503
+ # )
1504
+ # df.with_columns(Polars.col("s").str.head("n").alias("s_head_n"))
1505
+ # # =>
1506
+ # # shape: (4, 3)
1507
+ # # ┌─────────────┬─────┬──────────┐
1508
+ # # │ s ┆ n ┆ s_head_n │
1509
+ # # │ --- ┆ --- ┆ --- │
1510
+ # # │ str ┆ i64 ┆ str │
1511
+ # # ╞═════════════╪═════╪══════════╡
1512
+ # # │ pear ┆ 3 ┆ pea │
1513
+ # # │ null ┆ 4 ┆ null │
1514
+ # # │ papaya ┆ -2 ┆ papa │
1515
+ # # │ dragonfruit ┆ -5 ┆ dragon │
1516
+ # # └─────────────┴─────┴──────────┘
1517
+ def head(n)
1518
+ n = Utils.parse_into_expression(n)
1519
+ Utils.wrap_expr(_rbexpr.str_head(n))
1520
+ end
1521
+
1522
+ # Return the last n characters of each string in a String Series.
1523
+ #
1524
+ # @param n [Integer]
1525
+ # Length of the slice (integer or expression). Negative indexing is supported;
1526
+ # see note (2) below.
1527
+ #
1528
+ # @return [Expr]
1529
+ #
1530
+ # @note
1531
+ # 1) The `n` input is defined in terms of the number of characters in the (UTF8)
1532
+ # string. A character is defined as a [Unicode scalar value](https://www.unicode.org/glossary/#unicode_scalar_value). A single
1533
+ # character is represented by a single byte when working with ASCII text, and a
1534
+ # maximum of 4 bytes otherwise.
1535
+ #
1536
+ # 2) When the `n` input is negative, `tail` returns characters starting from the
1537
+ # `n`th from the beginning of the string. For example, if `n = -3`, then all
1538
+ # characters except the first three are returned.
1539
+ #
1540
+ # 3) If the length of the string has fewer than `n` characters, the full string is
1541
+ # returned.
1542
+ #
1543
+ # @example Return up to the last 5 characters:
1544
+ # df = Polars::DataFrame.new({"s" => ["pear", nil, "papaya", "dragonfruit"]})
1545
+ # df.with_columns(Polars.col("s").str.tail(5).alias("s_tail_5"))
1546
+ # # =>
1547
+ # # shape: (4, 2)
1548
+ # # ┌─────────────┬──────────┐
1549
+ # # │ s ┆ s_tail_5 │
1550
+ # # │ --- ┆ --- │
1551
+ # # │ str ┆ str │
1552
+ # # ╞═════════════╪══════════╡
1553
+ # # │ pear ┆ pear │
1554
+ # # │ null ┆ null │
1555
+ # # │ papaya ┆ apaya │
1556
+ # # │ dragonfruit ┆ fruit │
1557
+ # # └─────────────┴──────────┘
1558
+ #
1559
+ # @example Return characters determined by column `n`:
1560
+ # df = Polars::DataFrame.new(
1561
+ # {
1562
+ # "s" => ["pear", nil, "papaya", "dragonfruit"],
1563
+ # "n" => [3, 4, -2, -5]
1564
+ # }
1565
+ # )
1566
+ # df.with_columns(Polars.col("s").str.tail("n").alias("s_tail_n"))
1567
+ # # =>
1568
+ # # shape: (4, 3)
1569
+ # # ┌─────────────┬─────┬──────────┐
1570
+ # # │ s ┆ n ┆ s_tail_n │
1571
+ # # │ --- ┆ --- ┆ --- │
1572
+ # # │ str ┆ i64 ┆ str │
1573
+ # # ╞═════════════╪═════╪══════════╡
1574
+ # # │ pear ┆ 3 ┆ ear │
1575
+ # # │ null ┆ 4 ┆ null │
1576
+ # # │ papaya ┆ -2 ┆ paya │
1577
+ # # │ dragonfruit ┆ -5 ┆ nfruit │
1578
+ # # └─────────────┴─────┴──────────┘
1579
+ def tail(n)
1580
+ n = Utils.parse_into_expression(n)
1581
+ Utils.wrap_expr(_rbexpr.str_tail(n))
1582
+ end
1583
+
1289
1584
  # Convert an Utf8 column into an Int64 column with base radix.
1290
1585
  #
1291
1586
  # @param base [Integer]
@@ -1328,41 +1623,9 @@ module Polars
1328
1623
  # # │ cafe ┆ 51966 │
1329
1624
  # # │ null ┆ null │
1330
1625
  # # └──────┴────────┘
1331
- def to_integer(base: 10, strict: true)
1626
+ def to_integer(base: 10, dtype: Int64, strict: true)
1332
1627
  base = Utils.parse_into_expression(base, str_as_lit: false)
1333
- Utils.wrap_expr(_rbexpr.str_to_integer(base, strict))
1334
- end
1335
-
1336
- # Parse integers with base radix from strings.
1337
- #
1338
- # By default base 2. ParseError/Overflows become Nulls.
1339
- #
1340
- # @param radix [Integer]
1341
- # Positive integer which is the base of the string we are parsing.
1342
- # Default: 2.
1343
- # @param strict [Boolean]
1344
- # Bool, Default=true will raise any ParseError or overflow as ComputeError.
1345
- # False silently convert to Null.
1346
- #
1347
- # @return [Expr]
1348
- #
1349
- # @example
1350
- # df = Polars::DataFrame.new({"bin" => ["110", "101", "010", "invalid"]})
1351
- # df.select(Polars.col("bin").str.parse_int(2, strict: false))
1352
- # # =>
1353
- # # shape: (4, 1)
1354
- # # ┌──────┐
1355
- # # │ bin │
1356
- # # │ --- │
1357
- # # │ i32 │
1358
- # # ╞══════╡
1359
- # # │ 6 │
1360
- # # │ 5 │
1361
- # # │ 2 │
1362
- # # │ null │
1363
- # # └──────┘
1364
- def parse_int(radix = 2, strict: true)
1365
- to_integer(base: 2, strict: strict).cast(Int32, strict: strict)
1628
+ Utils.wrap_expr(_rbexpr.str_to_integer(base, dtype, strict))
1366
1629
  end
1367
1630
 
1368
1631
  # Use the aho-corasick algorithm to find matches.
@@ -1403,7 +1666,7 @@ module Polars
1403
1666
  # # │ Can you feel the love tonight ┆ true │
1404
1667
  # # └─────────────────────────────────┴──────────────┘
1405
1668
  def contains_any(patterns, ascii_case_insensitive: false)
1406
- patterns = Utils.parse_into_expression(patterns, str_as_lit: false, list_as_series: true)
1669
+ patterns = Utils.parse_into_expression(patterns, str_as_lit: false)
1407
1670
  Utils.wrap_expr(
1408
1671
  _rbexpr.str_contains_any(patterns, ascii_case_insensitive)
1409
1672
  )
@@ -1411,15 +1674,19 @@ module Polars
1411
1674
 
1412
1675
  # Use the aho-corasick algorithm to replace many matches.
1413
1676
  #
1414
- # @param patterns [String]
1677
+ # @param patterns [Object]
1415
1678
  # String patterns to search and replace.
1416
- # @param replace_with [String]
1679
+ # @param replace_with [Object]
1417
1680
  # Strings to replace where a pattern was a match.
1418
1681
  # This can be broadcasted. So it supports many:one and many:many.
1419
1682
  # @param ascii_case_insensitive [Boolean]
1420
1683
  # Enable ASCII-aware case insensitive matching.
1421
1684
  # When this option is enabled, searching will be performed without respect
1422
1685
  # to case for ASCII letters (a-z and A-Z) only.
1686
+ # @param leftmost [Boolean]
1687
+ # Guarantees in case there are overlapping matches that the leftmost match
1688
+ # is used. In case there are multiple candidates for the leftmost match
1689
+ # the pattern which comes first in patterns is used.
1423
1690
  #
1424
1691
  # @return [Expr]
1425
1692
  #
@@ -1437,7 +1704,7 @@ module Polars
1437
1704
  # Polars.col("lyrics")
1438
1705
  # .str.replace_many(
1439
1706
  # ["me", "you", "they"],
1440
- # ""
1707
+ # [""]
1441
1708
  # )
1442
1709
  # .alias("removes_pronouns")
1443
1710
  # )
@@ -1473,22 +1740,209 @@ module Polars
1473
1740
  # # │ Tell me what you want, what yo… ┆ Tell you what me want, what me… │
1474
1741
  # # │ Can you feel the love tonight ┆ Can me feel the love tonight │
1475
1742
  # # └─────────────────────────────────┴─────────────────────────────────┘
1476
- def replace_many(patterns, replace_with, ascii_case_insensitive: false)
1477
- patterns = Utils.parse_into_expression(patterns, str_as_lit: false, list_as_series: true)
1478
- replace_with = Utils.parse_into_expression(
1479
- replace_with, str_as_lit: true, list_as_series: true
1480
- )
1743
+ def replace_many(
1744
+ patterns,
1745
+ replace_with = NO_DEFAULT,
1746
+ ascii_case_insensitive: false,
1747
+ leftmost: false
1748
+ )
1749
+ if replace_with == NO_DEFAULT
1750
+ if !patterns.is_a?(Hash)
1751
+ msg = "`replace_with` argument is required if `patterns` argument is not a Hash type"
1752
+ raise TypeError, msg
1753
+ end
1754
+ # Early return in case of an empty mapping.
1755
+ if patterns.empty?
1756
+ return Utils.wrap_expr(_rbexpr)
1757
+ end
1758
+ replace_with = patterns.values
1759
+ patterns = patterns.keys
1760
+ end
1761
+
1762
+ patterns = Utils.parse_into_expression(patterns, str_as_lit: false)
1763
+ replace_with = Utils.parse_into_expression(replace_with, str_as_lit: true)
1481
1764
  Utils.wrap_expr(
1482
1765
  _rbexpr.str_replace_many(
1483
- patterns, replace_with, ascii_case_insensitive
1766
+ patterns, replace_with, ascii_case_insensitive, leftmost
1484
1767
  )
1485
1768
  )
1486
1769
  end
1487
1770
 
1771
+ # Use the Aho-Corasick algorithm to extract many matches.
1772
+ #
1773
+ # @param patterns [Object]
1774
+ # String patterns to search.
1775
+ # @param ascii_case_insensitive [Boolean]
1776
+ # Enable ASCII-aware case-insensitive matching.
1777
+ # When this option is enabled, searching will be performed without respect
1778
+ # to case for ASCII letters (a-z and A-Z) only.
1779
+ # @param overlapping [Boolean]
1780
+ # Whether matches may overlap.
1781
+ # @param leftmost [Boolean]
1782
+ # Guarantees in case there are overlapping matches that the leftmost match
1783
+ # is used. In case there are multiple candidates for the leftmost match
1784
+ # the pattern which comes first in patterns is used. May not be used
1785
+ # together with overlapping: true.
1786
+ #
1787
+ # @return [Expr]
1788
+ #
1789
+ # @note
1790
+ # This method supports matching on string literals only, and does not support
1791
+ # regular expression matching.
1792
+ #
1793
+ # @example
1794
+ # df = Polars::DataFrame.new({"values" => ["discontent"]})
1795
+ # patterns = ["winter", "disco", "onte", "discontent"]
1796
+ # df.with_columns(
1797
+ # Polars.col("values")
1798
+ # .str.extract_many(patterns, overlapping: false)
1799
+ # .alias("matches"),
1800
+ # Polars.col("values")
1801
+ # .str.extract_many(patterns, overlapping: true)
1802
+ # .alias("matches_overlapping"),
1803
+ # )
1804
+ # # =>
1805
+ # # shape: (1, 3)
1806
+ # # ┌────────────┬───────────┬─────────────────────────────────┐
1807
+ # # │ values ┆ matches ┆ matches_overlapping │
1808
+ # # │ --- ┆ --- ┆ --- │
1809
+ # # │ str ┆ list[str] ┆ list[str] │
1810
+ # # ╞════════════╪═══════════╪═════════════════════════════════╡
1811
+ # # │ discontent ┆ ["disco"] ┆ ["disco", "onte", "discontent"… │
1812
+ # # └────────────┴───────────┴─────────────────────────────────┘
1813
+ #
1814
+ # @example
1815
+ # df = Polars::DataFrame.new(
1816
+ # {
1817
+ # "values" => ["discontent", "rhapsody"],
1818
+ # "patterns" => [
1819
+ # ["winter", "disco", "onte", "discontent"],
1820
+ # ["rhap", "ody", "coalesce"]
1821
+ # ]
1822
+ # }
1823
+ # )
1824
+ # df.select(Polars.col("values").str.extract_many("patterns"))
1825
+ # # =>
1826
+ # # shape: (2, 1)
1827
+ # # ┌─────────────────┐
1828
+ # # │ values │
1829
+ # # │ --- │
1830
+ # # │ list[str] │
1831
+ # # ╞═════════════════╡
1832
+ # # │ ["disco"] │
1833
+ # # │ ["rhap", "ody"] │
1834
+ # # └─────────────────┘
1835
+ def extract_many(
1836
+ patterns,
1837
+ ascii_case_insensitive: false,
1838
+ overlapping: false,
1839
+ leftmost: false
1840
+ )
1841
+ if overlapping && leftmost
1842
+ msg = "can not match overlapping patterns when leftmost == true"
1843
+ raise ArgumentError, msg
1844
+ end
1845
+ patterns = Utils.parse_into_expression(patterns, str_as_lit: false)
1846
+ Utils.wrap_expr(
1847
+ _rbexpr.str_extract_many(patterns, ascii_case_insensitive, overlapping, leftmost)
1848
+ )
1849
+ end
1850
+
1851
+ # Use the Aho-Corasick algorithm to find many matches.
1852
+ #
1853
+ # The function will return the bytes offset of the start of each match.
1854
+ # The return type will be `List<UInt32>`
1855
+ #
1856
+ # @param patterns [Object]
1857
+ # String patterns to search.
1858
+ # @param ascii_case_insensitive [Boolean]
1859
+ # Enable ASCII-aware case-insensitive matching.
1860
+ # When this option is enabled, searching will be performed without respect
1861
+ # to case for ASCII letters (a-z and A-Z) only.
1862
+ # @param overlapping [Boolean]
1863
+ # Whether matches may overlap.
1864
+ # @param leftmost [Boolean]
1865
+ # Guarantees in case there are overlapping matches that the leftmost match
1866
+ # is used. In case there are multiple candidates for the leftmost match
1867
+ # the pattern which comes first in patterns is used. May not be used
1868
+ # together with overlapping: true.
1869
+ #
1870
+ # @return [Expr]
1871
+ #
1872
+ # @note
1873
+ # This method supports matching on string literals only, and does not support
1874
+ # regular expression matching.
1875
+ #
1876
+ # @example
1877
+ # df = Polars::DataFrame.new({"values" => ["discontent"]})
1878
+ # patterns = ["winter", "disco", "onte", "discontent"]
1879
+ # df.with_columns(
1880
+ # Polars.col("values")
1881
+ # .str.extract_many(patterns, overlapping: false)
1882
+ # .alias("matches"),
1883
+ # Polars.col("values")
1884
+ # .str.extract_many(patterns, overlapping: true)
1885
+ # .alias("matches_overlapping"),
1886
+ # )
1887
+ # # =>
1888
+ # # shape: (1, 3)
1889
+ # # ┌────────────┬───────────┬─────────────────────────────────┐
1890
+ # # │ values ┆ matches ┆ matches_overlapping │
1891
+ # # │ --- ┆ --- ┆ --- │
1892
+ # # │ str ┆ list[str] ┆ list[str] │
1893
+ # # ╞════════════╪═══════════╪═════════════════════════════════╡
1894
+ # # │ discontent ┆ ["disco"] ┆ ["disco", "onte", "discontent"… │
1895
+ # # └────────────┴───────────┴─────────────────────────────────┘
1896
+ #
1897
+ # @example
1898
+ # df = Polars::DataFrame.new(
1899
+ # {
1900
+ # "values" => ["discontent", "rhapsody"],
1901
+ # "patterns" => [
1902
+ # ["winter", "disco", "onte", "discontent"],
1903
+ # ["rhap", "ody", "coalesce"]
1904
+ # ]
1905
+ # }
1906
+ # )
1907
+ # df.select(Polars.col("values").str.find_many("patterns"))
1908
+ # # =>
1909
+ # # shape: (2, 1)
1910
+ # # ┌───────────┐
1911
+ # # │ values │
1912
+ # # │ --- │
1913
+ # # │ list[u32] │
1914
+ # # ╞═══════════╡
1915
+ # # │ [0] │
1916
+ # # │ [0, 5] │
1917
+ # # └───────────┘
1918
+ def find_many(
1919
+ patterns,
1920
+ ascii_case_insensitive: false,
1921
+ overlapping: false,
1922
+ leftmost: false
1923
+ )
1924
+ if overlapping && leftmost
1925
+ msg = "can not match overlapping patterns when leftmost == true"
1926
+ raise ArgumentError, msg
1927
+ end
1928
+ patterns = Utils.parse_into_expression(patterns, str_as_lit: false)
1929
+ Utils.wrap_expr(
1930
+ _rbexpr.str_find_many(patterns, ascii_case_insensitive, overlapping, leftmost)
1931
+ )
1932
+ end
1933
+
1488
1934
  private
1489
1935
 
1490
1936
  def _validate_format_argument(format)
1491
- # TODO
1937
+ if !format.nil? && format.include?(".%f")
1938
+ message = (
1939
+ "Detected the pattern `.%f` in the chrono format string." +
1940
+ " This pattern should not be used to parse values after a decimal point." +
1941
+ " Use `%.f` instead." +
1942
+ " See the full specification: https://docs.rs/chrono/latest/chrono/format/strftime"
1943
+ )
1944
+ warn message
1945
+ end
1492
1946
  end
1493
1947
  end
1494
1948
  end