polars_core/chunked_array/
from_iterator_par.rs1use std::collections::LinkedList;
15use std::sync::Mutex;
16
17use arrow::bitmap::Bitmap;
18use arrow::pushable::{NoOption, Pushable};
19use rayon::prelude::*;
20
21use super::from_iterator::PolarsAsRef;
22use crate::chunked_array::builder::get_list_builder;
23use crate::datatypes::BooleanChunked;
24use crate::prelude::*;
25use crate::utils::NoNull;
26use crate::utils::flatten::flatten_par;
27
28fn vec_push<T>(mut vec: Vec<T>, elem: T) -> Vec<T> {
31 vec.push(elem);
32 vec
33}
34
35fn as_list<T>(item: T) -> LinkedList<T> {
36 let mut list = LinkedList::new();
37 list.push_back(item);
38 list
39}
40
41fn list_append<T>(mut list1: LinkedList<T>, mut list2: LinkedList<T>) -> LinkedList<T> {
42 list1.append(&mut list2);
43 list1
44}
45
46fn collect_into_linked_list_vec<I>(par_iter: I) -> LinkedList<Vec<I::Item>>
47where
48 I: IntoParallelIterator,
49{
50 let it = par_iter.into_par_iter();
51 it.fold(Vec::new, vec_push)
55 .map(as_list)
56 .reduce(LinkedList::new, list_append)
57}
58
59fn collect_into_linked_list<I, P, F>(par_iter: I, identity: F) -> LinkedList<P::Freeze>
60where
61 I: IntoParallelIterator,
62 P: Pushable<I::Item> + Send + Sync,
63 F: Fn() -> P + Sync + Send,
64 P::Freeze: Send,
65{
66 let it = par_iter.into_par_iter();
67 it.fold(identity, |mut v, item| {
68 v.push(item);
69 v
70 })
71 .map(|p| as_list(p.freeze()))
73 .reduce(LinkedList::new, list_append)
74}
75
76fn get_capacity_from_par_results<T>(ll: &LinkedList<Vec<T>>) -> usize {
77 ll.iter().map(|list| list.len()).sum()
78}
79
80impl<T> FromParallelIterator<T::Native> for NoNull<ChunkedArray<T>>
81where
82 T: PolarsNumericType,
83{
84 fn from_par_iter<I: IntoParallelIterator<Item = T::Native>>(iter: I) -> Self {
85 let vectors = collect_into_linked_list_vec(iter);
87 let vectors = vectors.into_iter().collect::<Vec<_>>();
88 let values = flatten_par(&vectors);
89 NoNull::new(ChunkedArray::new_vec(PlSmallStr::EMPTY, values))
90 }
91}
92
93impl<T> FromParallelIterator<Option<T::Native>> for ChunkedArray<T>
94where
95 T: PolarsNumericType,
96{
97 fn from_par_iter<I: IntoParallelIterator<Item = Option<T::Native>>>(iter: I) -> Self {
98 let chunks = collect_into_linked_list(iter, MutablePrimitiveArray::new);
99 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
100 }
101}
102
103impl FromParallelIterator<bool> for BooleanChunked {
104 fn from_par_iter<I: IntoParallelIterator<Item = bool>>(iter: I) -> Self {
105 let chunks = collect_into_linked_list(iter, MutableBooleanArray::new);
106 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
107 }
108}
109
110impl FromParallelIterator<Option<bool>> for BooleanChunked {
111 fn from_par_iter<I: IntoParallelIterator<Item = Option<bool>>>(iter: I) -> Self {
112 let chunks = collect_into_linked_list(iter, MutableBooleanArray::new);
113 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
114 }
115}
116
117impl<Ptr> FromParallelIterator<Ptr> for StringChunked
118where
119 Ptr: PolarsAsRef<str> + Send + Sync + NoOption,
120{
121 fn from_par_iter<I: IntoParallelIterator<Item = Ptr>>(iter: I) -> Self {
122 let chunks = collect_into_linked_list(iter, MutableBinaryViewArray::new);
123 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
124 }
125}
126
127impl<Ptr> FromParallelIterator<Ptr> for BinaryChunked
128where
129 Ptr: PolarsAsRef<[u8]> + Send + Sync + NoOption,
130{
131 fn from_par_iter<I: IntoParallelIterator<Item = Ptr>>(iter: I) -> Self {
132 let chunks = collect_into_linked_list(iter, MutableBinaryViewArray::new);
133 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
134 }
135}
136
137impl<Ptr> FromParallelIterator<Option<Ptr>> for StringChunked
138where
139 Ptr: AsRef<str> + Send + Sync,
140{
141 fn from_par_iter<I: IntoParallelIterator<Item = Option<Ptr>>>(iter: I) -> Self {
142 let chunks = collect_into_linked_list(iter, MutableBinaryViewArray::new);
143 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
144 }
145}
146
147impl<Ptr> FromParallelIterator<Option<Ptr>> for BinaryChunked
148where
149 Ptr: AsRef<[u8]> + Send + Sync,
150{
151 fn from_par_iter<I: IntoParallelIterator<Item = Option<Ptr>>>(iter: I) -> Self {
152 let chunks = collect_into_linked_list(iter, MutableBinaryViewArray::new);
153 Self::from_chunk_iter(PlSmallStr::EMPTY, chunks).optional_rechunk()
154 }
155}
156
157pub trait FromParIterWithDtype<K> {
158 fn from_par_iter_with_dtype<I>(iter: I, name: PlSmallStr, dtype: DataType) -> Self
159 where
160 I: IntoParallelIterator<Item = K>,
161 Self: Sized;
162}
163
164fn get_value_cap(vectors: &LinkedList<Vec<Option<Series>>>) -> usize {
165 vectors
166 .iter()
167 .map(|list| {
168 list.iter()
169 .map(|opt_s| opt_s.as_ref().map(|s| s.len()).unwrap_or(0))
170 .sum::<usize>()
171 })
172 .sum::<usize>()
173}
174
175fn get_dtype(vectors: &LinkedList<Vec<Option<Series>>>) -> DataType {
176 for v in vectors {
177 for s in v.iter().flatten() {
178 let dtype = s.dtype();
179 if !matches!(dtype, DataType::Null) {
180 return dtype.clone();
181 }
182 }
183 }
184 DataType::Null
185}
186
187fn materialize_list(
188 name: PlSmallStr,
189 vectors: &LinkedList<Vec<Option<Series>>>,
190 dtype: DataType,
191 value_capacity: usize,
192 list_capacity: usize,
193) -> PolarsResult<ListChunked> {
194 let mut builder = get_list_builder(&dtype, value_capacity, list_capacity, name);
195 for v in vectors {
196 for val in v {
197 builder.append_opt_series(val.as_ref())?;
198 }
199 }
200 Ok(builder.finish())
201}
202
203impl FromParallelIterator<Option<Series>> for ListChunked {
204 fn from_par_iter<I>(par_iter: I) -> Self
205 where
206 I: IntoParallelIterator<Item = Option<Series>>,
207 {
208 list_from_par_iter(par_iter, PlSmallStr::EMPTY).unwrap()
209 }
210}
211
212pub fn list_from_par_iter<I>(par_iter: I, name: PlSmallStr) -> PolarsResult<ListChunked>
213where
214 I: IntoParallelIterator<Item = Option<Series>>,
215{
216 let vectors = collect_into_linked_list_vec(par_iter);
217
218 let list_capacity: usize = get_capacity_from_par_results(&vectors);
219 let value_capacity = get_value_cap(&vectors);
220 let dtype = get_dtype(&vectors);
221 if let DataType::Null = dtype {
222 Ok(ListChunked::full_null_with_dtype(
223 name,
224 list_capacity,
225 &DataType::Null,
226 ))
227 } else {
228 materialize_list(name, &vectors, dtype, value_capacity, list_capacity)
229 }
230}
231
232pub fn try_list_from_par_iter<I>(par_iter: I, name: PlSmallStr) -> PolarsResult<ListChunked>
233where
234 I: IntoParallelIterator<Item = PolarsResult<Option<Series>>>,
235{
236 fn ok<T, E>(saved: &Mutex<Option<E>>) -> impl Fn(Result<T, E>) -> Option<T> + '_ {
237 move |item| match item {
238 Ok(item) => Some(item),
239 Err(error) => {
240 if let Ok(mut guard) = saved.try_lock() {
244 if guard.is_none() {
245 *guard = Some(error);
246 }
247 }
248 None
249 },
250 }
251 }
252
253 let saved_error = Mutex::new(None);
254 let iter = par_iter.into_par_iter().map(ok(&saved_error)).while_some();
255
256 let collection = list_from_par_iter(iter, name)?;
257
258 match saved_error.into_inner().unwrap() {
259 Some(error) => Err(error),
260 None => Ok(collection),
261 }
262}
263
264impl FromParIterWithDtype<Option<Series>> for ListChunked {
265 fn from_par_iter_with_dtype<I>(iter: I, name: PlSmallStr, dtype: DataType) -> Self
266 where
267 I: IntoParallelIterator<Item = Option<Series>>,
268 Self: Sized,
269 {
270 let vectors = collect_into_linked_list_vec(iter);
271
272 let list_capacity: usize = get_capacity_from_par_results(&vectors);
273 let value_capacity = get_value_cap(&vectors);
274 if let DataType::List(dtype) = dtype {
275 materialize_list(name, &vectors, *dtype, value_capacity, list_capacity).unwrap()
276 } else {
277 panic!("expected list dtype")
278 }
279 }
280}
281
282pub trait ChunkedCollectParIterExt: ParallelIterator {
283 fn collect_ca_with_dtype<B: FromParIterWithDtype<Self::Item>>(
284 self,
285 name: PlSmallStr,
286 dtype: DataType,
287 ) -> B
288 where
289 Self: Sized,
290 {
291 B::from_par_iter_with_dtype(self, name, dtype)
292 }
293}
294
295impl<I: ParallelIterator> ChunkedCollectParIterExt for I {}
296
297impl<C, T, E> FromParIterWithDtype<Result<T, E>> for Result<C, E>
299where
300 C: FromParIterWithDtype<T>,
301 T: Send,
302 E: Send,
303{
304 fn from_par_iter_with_dtype<I>(par_iter: I, name: PlSmallStr, dtype: DataType) -> Self
305 where
306 I: IntoParallelIterator<Item = Result<T, E>>,
307 {
308 fn ok<T, E>(saved: &Mutex<Option<E>>) -> impl Fn(Result<T, E>) -> Option<T> + '_ {
309 move |item| match item {
310 Ok(item) => Some(item),
311 Err(error) => {
312 if let Ok(mut guard) = saved.try_lock() {
316 if guard.is_none() {
317 *guard = Some(error);
318 }
319 }
320 None
321 },
322 }
323 }
324
325 let saved_error = Mutex::new(None);
326 let iter = par_iter.into_par_iter().map(ok(&saved_error)).while_some();
327
328 let collection = C::from_par_iter_with_dtype(iter, name, dtype);
329
330 match saved_error.into_inner().unwrap() {
331 Some(error) => Err(error),
332 None => Ok(collection),
333 }
334 }
335}
336
337pub(crate) fn collect_bool_par<F>(len: usize, f: F) -> BooleanChunked
339where
340 F: Fn(usize) -> bool + Send + Sync,
341{
342 let n_bytes = len.div_ceil(8);
343 let mut values: Vec<u8> = Vec::with_capacity(n_bytes);
344
345 (0..n_bytes)
346 .into_par_iter()
347 .map(|b| {
348 let lo = b * 8;
349 let hi = (lo + 8).min(len);
350 let mut v = 0u8;
351 for (bit, g) in (lo..hi).enumerate() {
352 v |= (f(g) as u8) << bit;
353 }
354 v
355 })
356 .collect_into_vec(&mut values);
357
358 BooleanChunked::from_bitmap(PlSmallStr::EMPTY, Bitmap::from_u8_vec(values, len))
359}
360
361pub(crate) fn collect_bool_opt_par<F>(len: usize, f: F) -> BooleanChunked
362where
363 F: Fn(usize) -> Option<bool> + Send + Sync,
364{
365 let n_bytes = len.div_ceil(8);
366 let mut values: Vec<u8> = Vec::with_capacity(n_bytes);
367 let mut validity: Vec<u8> = Vec::with_capacity(n_bytes);
368
369 (0..n_bytes)
370 .into_par_iter()
371 .map(|b| {
372 let lo = b * 8;
373 let hi = (lo + 8).min(len);
374 let (mut v, mut m) = (0u8, 0u8);
375 for (bit, g) in (lo..hi).enumerate() {
376 if let Some(x) = f(g) {
377 m |= 1 << bit;
378 v |= (x as u8) << bit;
379 }
380 }
381 (v, m)
382 })
383 .unzip_into_vecs(&mut values, &mut validity);
384
385 let values = Bitmap::from_u8_vec(values, len);
386 let validity = Bitmap::from_u8_vec(validity, len);
387 let validity = (validity.unset_bits() > 0).then_some(validity);
388 BooleanChunked::with_chunk(
389 PlSmallStr::EMPTY,
390 BooleanArray::new(ArrowDataType::Boolean, values, validity),
391 )
392}
393
394#[allow(unused)]
396pub(crate) fn collect_primitive_par<T, F>(len: usize, f: F) -> ChunkedArray<T>
397where
398 T: PolarsNumericType,
399 F: Fn(usize) -> T::Native + Send + Sync,
400{
401 let mut values: Vec<T::Native> = Vec::new();
402 (0..len)
403 .into_par_iter()
404 .map(f)
405 .collect_into_vec(&mut values);
406 ChunkedArray::from_vec(PlSmallStr::EMPTY, values)
407}
408
409pub(crate) fn collect_primitive_opt_par<T, F>(len: usize, f: F) -> ChunkedArray<T>
410where
411 T: PolarsNumericType,
412 F: Fn(usize) -> Option<T::Native> + Send + Sync,
413{
414 let mut values: Vec<T::Native> = vec![T::Native::default(); len];
415 let mut validity: Vec<u8> = Vec::with_capacity(len.div_ceil(8));
416
417 values
418 .par_chunks_mut(8)
419 .enumerate()
420 .map(|(b, out)| {
421 let lo = b * 8;
422 let mut m = 0u8;
423 for (bit, slot) in out.iter_mut().enumerate() {
424 if let Some(x) = f(lo + bit) {
425 *slot = x;
426 m |= 1 << bit;
427 }
428 }
429 m
430 })
431 .collect_into_vec(&mut validity);
432
433 let validity = Bitmap::from_u8_vec(validity, len);
434 let validity = (validity.unset_bits() > 0).then_some(validity);
435 ChunkedArray::from_vec_validity(PlSmallStr::EMPTY, values, validity)
436}