1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use std::fmt::{Display, Write};

use polars_arrow::array::default_arrays::FromDataUtf8;

use super::StrConcat;
use crate::prelude::*;

fn fmt_and_write<T: Display>(value: Option<T>, buf: &mut String) {
    match value {
        None => buf.push_str("null"),
        Some(v) => {
            write!(buf, "{}", v).unwrap();
        }
    }
}

fn str_concat_impl<I, T>(mut iter: I, delimiter: &str, name: &str) -> Utf8Chunked
where
    I: Iterator<Item = Option<T>>,
    T: Display,
{
    let mut buf = String::with_capacity(iter.size_hint().0 * 5);

    if let Some(first) = iter.next() {
        fmt_and_write(first, &mut buf);

        for val in iter {
            buf.push_str(delimiter);
            fmt_and_write(val, &mut buf);
        }
    }
    buf.shrink_to_fit();
    let buf = buf.into_bytes();
    let offsets = vec![0, buf.len() as i64];
    let arr = unsafe { Utf8Array::from_data_unchecked_default(offsets.into(), buf.into(), None) };
    Utf8Chunked::from_chunks(name, vec![Box::new(arr)])
}

impl<T> StrConcat for ChunkedArray<T>
where
    T: PolarsNumericType,
    T::Native: Display,
{
    fn str_concat(&self, delimiter: &str) -> Utf8Chunked {
        let iter = self.into_iter();
        str_concat_impl(iter, delimiter, self.name())
    }
}

impl StrConcat for Utf8Chunked {
    fn str_concat(&self, delimiter: &str) -> Utf8Chunked {
        let iter = self.into_iter();
        str_concat_impl(iter, delimiter, self.name())
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_str_concat() {
        let ca = Int32Chunked::new("foo", &[Some(1), None, Some(3)]);
        let out = ca.str_concat("-");

        let out = out.get(0);
        assert_eq!(out, Some("1-null-3"));
    }
}