use std::fmt;
use crate::buffer::Buffer;
#[derive(Clone)]
pub struct StrBuffer {
offsets: Buffer<i64>,
data: Buffer<u8>,
}
impl StrBuffer {
#[inline]
pub fn from_vec(v: Vec<String>) -> Self {
v.into_iter().collect()
}
#[inline]
pub fn from_buffers(offsets: Buffer<i64>, data: Buffer<u8>) -> Self {
StrBuffer { offsets, data }
}
#[inline]
pub fn buffers(&self) -> (&Buffer<i64>, &Buffer<u8>) {
(&self.offsets, &self.data)
}
#[inline]
pub fn len(&self) -> usize {
self.offsets.len().saturating_sub(1)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn get(&self, i: usize) -> &str {
let off = self.offsets.as_slice();
let (s, e) = (off[i] as usize, off[i + 1] as usize);
unsafe { std::str::from_utf8_unchecked(&self.data.as_slice()[s..e]) }
}
#[inline]
pub unsafe fn get_unchecked(&self, i: usize) -> &str {
let off = self.offsets.as_slice();
let (s, e) = (*off.get_unchecked(i) as usize, *off.get_unchecked(i + 1) as usize);
std::str::from_utf8_unchecked(self.data.as_slice().get_unchecked(s..e))
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &str> + '_ {
let off = self.offsets.as_slice();
let data = self.data.as_slice();
(0..self.len()).map(move |i| {
unsafe {
let s = *off.get_unchecked(i) as usize;
let e = *off.get_unchecked(i + 1) as usize;
std::str::from_utf8_unchecked(data.get_unchecked(s..e))
}
})
}
pub fn to_vec(&self) -> Vec<String> {
self.iter().map(String::from).collect()
}
pub fn slice(&self, start: usize, end: usize) -> StrBuffer {
(start..end).map(|i| self.get(i)).collect()
}
pub fn extend<S: AsRef<str>>(&mut self, items: impl IntoIterator<Item = S>) {
let data = self.data.make_mut();
let offsets = self.offsets.make_mut();
for s in items {
data.extend_from_slice(s.as_ref().as_bytes());
offsets.push(data.len() as i64);
}
}
}
#[derive(Default)]
pub struct StrBufferBuilder {
offsets: Vec<i64>,
data: Vec<u8>,
}
impl StrBufferBuilder {
pub fn with_capacity(n: usize) -> Self {
let mut offsets = Vec::with_capacity(n + 1);
offsets.push(0);
StrBufferBuilder { offsets, data: Vec::new() }
}
#[inline]
pub fn push(&mut self, s: &str) {
self.data.extend_from_slice(s.as_bytes());
self.offsets.push(self.data.len() as i64);
}
pub fn finish(mut self) -> StrBuffer {
if self.offsets.is_empty() {
self.offsets.push(0);
}
StrBuffer {
offsets: Buffer::from_vec(self.offsets),
data: Buffer::from_vec(self.data),
}
}
}
impl PartialEq for StrBuffer {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}
impl fmt::Debug for StrBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl<S: AsRef<str>> FromIterator<S> for StrBuffer {
fn from_iter<I: IntoIterator<Item = S>>(it: I) -> Self {
let mut offsets = vec![0i64];
let mut data = Vec::new();
for s in it {
data.extend_from_slice(s.as_ref().as_bytes());
offsets.push(data.len() as i64);
}
StrBuffer {
offsets: Buffer::from_vec(offsets),
data: Buffer::from_vec(data),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_access_and_emptiness() {
let sb = StrBuffer::from_vec(vec!["a".into(), "".into(), "cd".into()]);
assert_eq!(sb.len(), 3);
assert!(!sb.is_empty());
assert_eq!(sb.get(2), "cd");
assert_eq!(unsafe { sb.get_unchecked(1) }, "");
assert_eq!(sb.iter().collect::<Vec<_>>(), ["a", "", "cd"]);
assert_eq!(sb.to_vec(), vec!["a".to_string(), "".into(), "cd".into()]);
assert!(StrBuffer::from_vec(vec![]).is_empty());
}
#[test]
fn buffers_round_trip_zero_copy() {
let sb = StrBuffer::from_vec(vec!["xy".into(), "z".into()]);
let (offsets, data) = sb.buffers();
let rebuilt = StrBuffer::from_buffers(offsets.clone(), data.clone());
assert_eq!(rebuilt, sb);
assert_eq!(rebuilt.slice(1, 2), StrBuffer::from_vec(vec!["z".into()]));
}
#[test]
fn builder_default_finishes_empty() {
let empty = StrBufferBuilder::default().finish();
assert!(empty.is_empty());
let mut b = StrBufferBuilder::with_capacity(2);
b.push("p");
b.push("qr");
assert_eq!(b.finish(), StrBuffer::from_vec(vec!["p".into(), "qr".into()]));
}
#[test]
fn debug_lists_cells() {
let sb = StrBuffer::from_vec(vec!["a".into(), "b".into()]);
assert_eq!(format!("{sb:?}"), r#"["a", "b"]"#);
}
}