diff --git a/Cargo.lock b/Cargo.lock index 9b2c830f8a..f87d14eaae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,6 +984,7 @@ dependencies = [ "vortex-ree", "vortex-roaring", "vortex-schema", + "vortex-zigzag", ] [[package]] @@ -3963,6 +3964,7 @@ dependencies = [ "vortex-ree", "vortex-roaring", "vortex-schema", + "vortex-zigzag", ] [[package]] @@ -5342,6 +5344,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "vortex-zigzag" +version = "0.1.0" +dependencies = [ + "linkme", + "serde", + "vortex-alloc", + "vortex-array", + "vortex-error", + "vortex-fastlanes", + "vortex-schema", + "zigzag", +] + [[package]] name = "vsimd" version = "0.8.0" @@ -5714,6 +5730,15 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +[[package]] +name = "zigzag" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b40401a28d86ce16a330b863b86fd7dbee4d7c940587ab09ab8c019f9e3fdf" +dependencies = [ + "num-traits", +] + [[package]] name = "zstd" version = "0.13.1" diff --git a/Cargo.toml b/Cargo.toml index e6936a9084..5f2d576d11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ "vortex-ree", "vortex-roaring", "vortex-schema", - #"vortex-zigzag", + "vortex-zigzag", ] resolver = "2" diff --git a/bench-vortex/Cargo.toml b/bench-vortex/Cargo.toml index 3c72bb6759..922e1ab3e8 100644 --- a/bench-vortex/Cargo.toml +++ b/bench-vortex/Cargo.toml @@ -48,6 +48,7 @@ vortex-fastlanes = { path = "../vortex-fastlanes" } vortex-ipc = { path = "../vortex-ipc" } vortex-ree = { path = "../vortex-ree" } vortex-schema = { path = "../vortex-schema" } +vortex-zigzag = { path = "../vortex-zigzag" } [dev-dependencies] criterion = { workspace = true } diff --git a/bench-vortex/src/lib.rs b/bench-vortex/src/lib.rs index 7120f770d8..d1a46b9f14 100644 --- a/bench-vortex/src/lib.rs +++ b/bench-vortex/src/lib.rs @@ -120,7 +120,7 @@ pub fn enumerate_arrays() -> Vec { &RoaringBoolEncoding, // &RoaringIntEncoding, // Doesn't offer anything more than FoR really - // ZigZagEncoding, + // &ZigZagEncoding, ] } diff --git a/pyvortex/Cargo.toml b/pyvortex/Cargo.toml index ec8cb2c5f1..566f9d3464 100644 --- a/pyvortex/Cargo.toml +++ b/pyvortex/Cargo.toml @@ -28,7 +28,7 @@ vortex-fastlanes = { path = "../vortex-fastlanes" } vortex-ree = { path = "../vortex-ree" } vortex-roaring = { path = "../vortex-roaring" } vortex-schema = { path = "../vortex-schema" } -#vortex-zigzag = { path = "../vortex-zigzag" } +vortex-zigzag = { path = "../vortex-zigzag" } itertools = { workspace = true } log = { workspace = true } paste = { workspace = true } diff --git a/pyvortex/src/array.rs b/pyvortex/src/array.rs index 034c0c7892..2f67a8aff3 100644 --- a/pyvortex/src/array.rs +++ b/pyvortex/src/array.rs @@ -27,6 +27,7 @@ use vortex_roaring::{ OwnedRoaringBoolArray, OwnedRoaringIntArray, RoaringBool, RoaringBoolArray, RoaringBoolEncoding, RoaringInt, RoaringIntArray, RoaringIntEncoding, }; +use vortex_zigzag::{OwnedZigZagArray, ZigZag, ZigZagArray, ZigZagEncoding}; use crate::dtype::PyDType; use crate::error::PyVortexError; @@ -80,7 +81,7 @@ pyarray!(DictEncoding, DictArray, "DictArray"); pyarray!(REEEncoding, REEArray, "REEArray"); pyarray!(RoaringBoolEncoding, RoaringBoolArray, "RoaringBoolArray"); pyarray!(RoaringIntEncoding, RoaringIntArray, "RoaringIntArray"); -// pyarray!(ZigZagEncoding, ZigZagArray, "ZigZagArray"); +pyarray!(ZigZagEncoding, ZigZagArray, "ZigZagArray"); impl PyArray { pub fn wrap(py: Python<'_>, inner: ArrayData) -> PyResult> { @@ -178,6 +179,11 @@ impl PyArray { .map_err(PyVortexError::map_err)?, )? .extract(py), + ZigZag::ID => PyZigZagArray::wrap( + py, + OwnedZigZagArray::try_from(inner.into_array()).map_err(PyVortexError::map_err)?, + )? + .extract(py), _ => Py::new( py, Self { diff --git a/pyvortex/src/lib.rs b/pyvortex/src/lib.rs index fb3d08b160..f053b9deb2 100644 --- a/pyvortex/src/lib.rs +++ b/pyvortex/src/lib.rs @@ -46,7 +46,7 @@ fn _lib(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - // m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/vortex-zigzag/Cargo.toml b/vortex-zigzag/Cargo.toml index ef4b1e8e5e..b5177f1c05 100644 --- a/vortex-zigzag/Cargo.toml +++ b/vortex-zigzag/Cargo.toml @@ -16,8 +16,10 @@ linkme = { workspace = true } vortex-alloc = { path = "../vortex-alloc" } vortex-array = { path = "../vortex-array" } vortex-error = { path = "../vortex-error" } +vortex-fastlanes = { path = "../vortex-fastlanes" } vortex-schema = { path = "../vortex-schema" } zigzag = { workspace = true } +serde = { workspace = true, features = ["derive"] } [lints] workspace = true diff --git a/vortex-zigzag/src/compress.rs b/vortex-zigzag/src/compress.rs index 7cef022ef9..f29d99e254 100644 --- a/vortex-zigzag/src/compress.rs +++ b/vortex-zigzag/src/compress.rs @@ -1,26 +1,23 @@ -use vortex::array::downcast::DowncastArrayBuiltin; use vortex::array::primitive::PrimitiveArray; -use vortex::array::{Array, ArrayKind, ArrayRef}; use vortex::compress::{CompressConfig, CompressCtx, EncodingCompression}; use vortex::ptype::{NativePType, PType}; -use vortex::stats::Stat; -use vortex::validity::{OwnedValidity, ValidityView}; -use vortex::view::ToOwnedView; +use vortex::stats::{ArrayStatistics, Stat}; +use vortex::validity::Validity; +use vortex::{Array, IntoArray, OwnedArray}; use vortex_alloc::{AlignedVec, ALIGNED_ALLOCATOR}; use vortex_error::VortexResult; -use zigzag::ZigZag; +use zigzag::ZigZag as ExternalZigZag; -use crate::downcast::DowncastZigzag; -use crate::zigzag::{ZigZagArray, ZigZagEncoding}; +use crate::{OwnedZigZagArray, ZigZagArray, ZigZagEncoding}; impl EncodingCompression for ZigZagEncoding { fn can_compress( &self, - array: &dyn Array, + array: &Array, _config: &CompressConfig, ) -> Option<&dyn EncodingCompression> { // Only support primitive arrays - let parray = array.maybe_primitive()?; + let parray = PrimitiveArray::try_from(array).ok()?; // Only supports signed integers if !parray.ptype().is_signed_int() { @@ -30,63 +27,54 @@ impl EncodingCompression for ZigZagEncoding { // Only compress if the array has negative values // TODO(ngates): also check that Stat::Max is less than half the max value of the type parray - .stats() - .get_or_compute_cast::(&Stat::Min) + .statistics() + .compute_as_cast::(Stat::Min) .filter(|&min| min < 0) .map(|_| self as &dyn EncodingCompression) } fn compress( &self, - array: &dyn Array, - like: Option<&dyn Array>, + array: &Array, + like: Option<&Array>, ctx: CompressCtx, - ) -> VortexResult { - let zigzag_like = like.map(|like_arr| like_arr.as_zigzag()); - let encoded = match ArrayKind::from(array) { - ArrayKind::Primitive(p) => zigzag_encode(p), - _ => unreachable!("This array kind should have been filtered out"), - } - .unwrap(); + ) -> VortexResult { + let zigzag_like = like.map(|like_arr| ZigZagArray::try_from(like_arr).unwrap()); + let encoded = zigzag_encode(&array.as_primitive())?; - Ok( - ZigZagArray::new(ctx.compress(encoded.encoded(), zigzag_like.map(|z| z.encoded()))?) - .into_array(), - ) + Ok(OwnedZigZagArray::new(ctx.compress( + &encoded.encoded(), + zigzag_like.as_ref().map(|z| z.encoded()).as_ref(), + )?) + .into_array()) } } -pub fn zigzag_encode(parray: &PrimitiveArray) -> VortexResult { +pub fn zigzag_encode(parray: &PrimitiveArray<'_>) -> VortexResult { let encoded = match parray.ptype() { - PType::I8 => zigzag_encode_primitive::(parray.buffer().typed_data(), parray.validity()), - PType::I16 => { - zigzag_encode_primitive::(parray.buffer().typed_data(), parray.validity()) - } - PType::I32 => { - zigzag_encode_primitive::(parray.buffer().typed_data(), parray.validity()) - } - PType::I64 => { - zigzag_encode_primitive::(parray.buffer().typed_data(), parray.validity()) - } + PType::I8 => zigzag_encode_primitive::(parray.typed_data(), parray.validity()), + PType::I16 => zigzag_encode_primitive::(parray.typed_data(), parray.validity()), + PType::I32 => zigzag_encode_primitive::(parray.typed_data(), parray.validity()), + PType::I64 => zigzag_encode_primitive::(parray.typed_data(), parray.validity()), _ => panic!("Unsupported ptype {}", parray.ptype()), }; - ZigZagArray::try_new(encoded.into_array()) + OwnedZigZagArray::try_new(encoded.into_array()) } -fn zigzag_encode_primitive( - values: &[T], - validity: Option, -) -> PrimitiveArray +fn zigzag_encode_primitive<'a, T: ExternalZigZag + NativePType>( + values: &'a [T], + validity: Validity<'a>, +) -> PrimitiveArray<'a> where - ::UInt: NativePType, + ::UInt: NativePType, { - let mut encoded = AlignedVec::with_capacity_in(values.len(), ALIGNED_ALLOCATOR); + let mut encoded = Vec::with_capacity(values.len()); encoded.extend(values.iter().map(|v| T::encode(*v))); - PrimitiveArray::from_nullable_in(encoded, validity.to_owned_view()) + PrimitiveArray::from_vec(encoded.to_vec(), validity.to_owned()) } #[allow(dead_code)] -pub fn zigzag_decode(parray: &PrimitiveArray) -> PrimitiveArray { +pub fn zigzag_decode<'a>(parray: &'a PrimitiveArray<'a>) -> PrimitiveArray<'a> { match parray.ptype() { PType::U8 => zigzag_decode_primitive::(parray.buffer().typed_data(), parray.validity()), PType::U16 => { @@ -103,14 +91,39 @@ pub fn zigzag_decode(parray: &PrimitiveArray) -> PrimitiveArray { } #[allow(dead_code)] -fn zigzag_decode_primitive( - values: &[T::UInt], - validity: Option, -) -> PrimitiveArray +fn zigzag_decode_primitive<'a, T: ExternalZigZag + NativePType>( + values: &'a [T::UInt], + validity: Validity<'a>, +) -> PrimitiveArray<'a> where - ::UInt: NativePType, + ::UInt: NativePType, { let mut encoded: AlignedVec = AlignedVec::with_capacity_in(values.len(), ALIGNED_ALLOCATOR); encoded.extend(values.iter().map(|v| T::decode(*v))); - PrimitiveArray::from_nullable_in(encoded, validity.to_owned_view()) + PrimitiveArray::from_vec(encoded.to_vec(), validity) +} + +#[cfg(test)] +mod test { + use std::sync::Arc; + + use vortex::encoding::{ArrayEncoding, EncodingRef}; + use vortex_fastlanes::BitPackedEncoding; + + use super::*; + + #[test] + fn test_compress() { + let cfg = CompressConfig::new() + .with_enabled([&ZigZagEncoding as EncodingRef, &BitPackedEncoding]); + let ctx = CompressCtx::new(Arc::new(cfg)); + + let compressed = ctx + .compress( + PrimitiveArray::from(Vec::from_iter((-10_000..10_000).map(|i| i as i64))).array(), + None, + ) + .unwrap(); + assert_eq!(compressed.encoding().id(), ZigZagEncoding.id()); + } } diff --git a/vortex-zigzag/src/compute.rs b/vortex-zigzag/src/compute.rs index 3d9f1ef26b..1aca390a55 100644 --- a/vortex-zigzag/src/compute.rs +++ b/vortex-zigzag/src/compute.rs @@ -1,14 +1,14 @@ -use vortex::array::{Array, ArrayRef}; use vortex::compute::scalar_at::{scalar_at, ScalarAtFn}; use vortex::compute::slice::{slice, SliceFn}; use vortex::compute::ArrayCompute; use vortex::scalar::{PScalar, Scalar}; +use vortex::{ArrayDType, IntoArray, OwnedArray}; use vortex_error::{vortex_err, VortexResult}; -use zigzag::ZigZag; +use zigzag::ZigZag as ExternalZigZag; use crate::ZigZagArray; -impl ArrayCompute for ZigZagArray { +impl ArrayCompute for ZigZagArray<'_> { fn scalar_at(&self) -> Option<&dyn ScalarAtFn> { Some(self) } @@ -18,9 +18,9 @@ impl ArrayCompute for ZigZagArray { } } -impl ScalarAtFn for ZigZagArray { +impl ScalarAtFn for ZigZagArray<'_> { fn scalar_at(&self, index: usize) -> VortexResult { - let scalar = scalar_at(self.encoded(), index)?; + let scalar = scalar_at(&self.encoded(), index)?; match scalar { Scalar::Primitive(p) => match p.value() { None => Ok(Scalar::null(self.dtype())), @@ -37,8 +37,8 @@ impl ScalarAtFn for ZigZagArray { } } -impl SliceFn for ZigZagArray { - fn slice(&self, start: usize, stop: usize) -> VortexResult { - Ok(ZigZagArray::try_new(slice(self.encoded(), start, stop)?)?.into_array()) +impl SliceFn for ZigZagArray<'_> { + fn slice(&self, start: usize, stop: usize) -> VortexResult { + Ok(ZigZagArray::try_new(slice(&self.encoded(), start, stop)?)?.into_array()) } } diff --git a/vortex-zigzag/src/lib.rs b/vortex-zigzag/src/lib.rs index e1f8df6886..8767f61778 100644 --- a/vortex-zigzag/src/lib.rs +++ b/vortex-zigzag/src/lib.rs @@ -1,13 +1,5 @@ -use linkme::distributed_slice; -use vortex::encoding::{EncodingRef, ENCODINGS}; pub use zigzag::*; mod compress; mod compute; -mod downcast; -mod serde; -mod stats; mod zigzag; - -#[distributed_slice(ENCODINGS)] -static ENCODINGS_ZIGZAG: EncodingRef = &ZigZagEncoding; diff --git a/vortex-zigzag/src/serde.rs b/vortex-zigzag/src/serde.rs deleted file mode 100644 index ea43957ca1..0000000000 --- a/vortex-zigzag/src/serde.rs +++ /dev/null @@ -1,67 +0,0 @@ -use vortex::array::{Array, ArrayRef}; -use vortex::serde::{ArraySerde, EncodingSerde, ReadCtx, WriteCtx}; -use vortex_error::VortexResult; -use vortex_schema::{DType, Signedness}; - -use crate::{ZigZagArray, ZigZagEncoding}; - -impl ArraySerde for ZigZagArray { - fn write(&self, ctx: &mut WriteCtx) -> VortexResult<()> { - ctx.write(self.encoded()) - } - - fn metadata(&self) -> VortexResult>> { - Ok(None) - } -} - -impl EncodingSerde for ZigZagEncoding { - fn read(&self, ctx: &mut ReadCtx) -> VortexResult { - let encoded = ctx.with_schema(&encoded_dtype(ctx.schema())).read()?; - Ok(ZigZagArray::new(encoded).into_array()) - } -} - -fn encoded_dtype(schema: &DType) -> DType { - match schema { - DType::Int(w, Signedness::Signed, n) => DType::Int(*w, Signedness::Unsigned, *n), - _ => panic!("Invalid schema"), - } -} - -#[cfg(test)] -mod test { - use vortex::array::downcast::DowncastArrayBuiltin; - use vortex::array::primitive::PrimitiveArray; - use vortex::array::{Array, ArrayRef}; - use vortex::serde::{ReadCtx, WriteCtx}; - use vortex_error::VortexResult; - - use crate::compress::zigzag_encode; - use crate::downcast::DowncastZigzag; - - fn roundtrip_array(array: &dyn Array) -> VortexResult { - let mut buf = Vec::::new(); - let mut write_ctx = WriteCtx::new(&mut buf); - write_ctx.write(array)?; - let mut read = buf.as_slice(); - let mut read_ctx = ReadCtx::new(array.dtype(), &mut read); - read_ctx.read() - } - - #[test] - fn roundtrip() { - let arr = zigzag_encode(&PrimitiveArray::from(vec![-7i64, -13, 17, 23])).unwrap(); - let read_arr = roundtrip_array(&arr).unwrap(); - - let read_zigzag = read_arr.as_zigzag(); - assert_eq!( - arr.encoded().as_primitive().buffer().typed_data::(), - read_zigzag - .encoded() - .as_primitive() - .buffer() - .typed_data::() - ); - } -} diff --git a/vortex-zigzag/src/stats.rs b/vortex-zigzag/src/stats.rs deleted file mode 100644 index 39e0fb1fd2..0000000000 --- a/vortex-zigzag/src/stats.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::collections::HashMap; - -use vortex::stats::{Stat, StatsCompute, StatsSet}; -use vortex_error::VortexResult; - -use crate::zigzag::ZigZagArray; - -impl StatsCompute for ZigZagArray { - fn compute(&self, _stat: &Stat) -> VortexResult { - // TODO(ngates): implement based on the encoded array - Ok(StatsSet::from(HashMap::new())) - } -} diff --git a/vortex-zigzag/src/zigzag.rs b/vortex-zigzag/src/zigzag.rs index 1e38fc11a2..8018c47ca9 100644 --- a/vortex-zigzag/src/zigzag.rs +++ b/vortex-zigzag/src/zigzag.rs @@ -1,140 +1,84 @@ -use std::sync::{Arc, RwLock}; - -use vortex::array::{Array, ArrayKind, ArrayRef}; -use vortex::compress::EncodingCompression; -use vortex::compute::ArrayCompute; -use vortex::encoding::{Encoding, EncodingId, EncodingRef}; -use vortex::formatter::{ArrayDisplay, ArrayFormatter}; -use vortex::serde::{ArraySerde, EncodingSerde}; -use vortex::stats::{Stats, StatsSet}; -use vortex::validity::{ArrayValidity, Validity}; -use vortex::{impl_array, ArrayWalker}; +use serde::{Deserialize, Serialize}; +use vortex::array::primitive::PrimitiveArray; +use vortex::stats::ArrayStatisticsCompute; +use vortex::validity::{ArrayValidity, LogicalValidity}; +use vortex::visitor::{AcceptArrayVisitor, ArrayVisitor}; +use vortex::{impl_encoding, ArrayDType, ArrayFlatten, IntoArrayData}; use vortex_error::{vortex_bail, vortex_err, VortexResult}; -use vortex_schema::{DType, Signedness}; +use vortex_schema::Signedness; use crate::compress::zigzag_encode; -#[derive(Debug, Clone)] -pub struct ZigZagArray { - encoded: ArrayRef, - dtype: DType, - stats: Arc>, +impl_encoding!("vortex.zigzag", ZigZag); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ZigZagMetadata { + encoded_dtype: DType, } -impl ZigZagArray { - pub fn new(encoded: ArrayRef) -> Self { +impl ZigZagArray<'_> { + pub fn new(encoded: Array) -> Self { Self::try_new(encoded).unwrap() } - pub fn try_new(encoded: ArrayRef) -> VortexResult { - let dtype = match encoded.dtype() { + pub fn try_new(encoded: Array) -> VortexResult { + let encoded_dtype = encoded.dtype().clone(); + let dtype = match encoded_dtype { DType::Int(width, Signedness::Unsigned, nullability) => { - DType::Int(*width, Signedness::Signed, *nullability) + DType::Int(width, Signedness::Signed, nullability) } d => vortex_bail!(MismatchedTypes: "unsigned int", d), }; - Ok(Self { - encoded, - dtype, - stats: Arc::new(RwLock::new(StatsSet::new())), - }) - } - - pub fn encode(array: &dyn Array) -> VortexResult { - match ArrayKind::from(array) { - ArrayKind::Primitive(p) => Ok(zigzag_encode(p)?.into_array()), - _ => Err(vortex_err!("ZigZag can only encoding primitive arrays")), - } - } - - pub fn encoded(&self) -> &ArrayRef { - &self.encoded - } -} - -impl Array for ZigZagArray { - impl_array!(); - #[inline] - fn len(&self) -> usize { - self.encoded.len() - } + let children = vec![encoded.into_array_data()]; - #[inline] - fn is_empty(&self) -> bool { - self.encoded.is_empty() + let metadata = ZigZagMetadata { encoded_dtype }; + Self::try_from_parts(dtype, metadata, children.into(), HashMap::default()) } - #[inline] - fn dtype(&self) -> &DType { - &self.dtype + pub fn encode<'a>(array: &'a Array<'a>) -> VortexResult> { + PrimitiveArray::try_from(array) + .map_err(|_| vortex_err!("ZigZag can only encoding primitive arrays")) + .map(|parray| zigzag_encode(&parray))? + .map(|encoded| encoded.into_array()) } - #[inline] - fn stats(&self) -> Stats { - Stats::new(&self.stats, self) - } - - #[inline] - fn encoding(&self) -> EncodingRef { - &ZigZagEncoding - } - - #[inline] - fn nbytes(&self) -> usize { - self.encoded.nbytes() - } - - fn serde(&self) -> Option<&dyn ArraySerde> { - Some(self) - } - - #[inline] - fn with_compute_mut( - &self, - f: &mut dyn FnMut(&dyn ArrayCompute) -> VortexResult<()>, - ) -> VortexResult<()> { - f(self) - } - - fn walk(&self, walker: &mut dyn ArrayWalker) -> VortexResult<()> { - walker.visit_child(self.encoded()) + pub fn encoded(&self) -> Array { + self.array() + .child(0, &self.metadata().encoded_dtype) + .expect("Missing encoded array") } } -impl ArrayValidity for ZigZagArray { - fn logical_validity(&self) -> Validity { - self.encoded().logical_validity() +impl ArrayValidity for ZigZagArray<'_> { + fn logical_validity(&self) -> LogicalValidity { + self.encoded().with_dyn(|a| a.logical_validity()) } fn is_valid(&self, index: usize) -> bool { - self.encoded().is_valid(index) + self.encoded().with_dyn(|a| a.is_valid(index)) } } -impl ArrayDisplay for ZigZagArray { - fn fmt(&self, f: &mut ArrayFormatter) -> std::fmt::Result { - f.child("zigzag", self.encoded()) +impl AcceptArrayVisitor for ZigZagArray<'_> { + fn accept(&self, visitor: &mut dyn ArrayVisitor) -> VortexResult<()> { + visitor.visit_child("encoded", &self.encoded()) } } -#[derive(Debug)] -pub struct ZigZagEncoding; +impl ArrayStatisticsCompute for ZigZagArray<'_> {} -impl ZigZagEncoding { - pub const ID: EncodingId = EncodingId::new("vortex.zigzag"); -} - -impl Encoding for ZigZagEncoding { - fn id(&self) -> EncodingId { - Self::ID - } - - fn compression(&self) -> Option<&dyn EncodingCompression> { - Some(self) +impl ArrayFlatten for ZigZagArray<'_> { + fn flatten<'a>(self) -> VortexResult> + where + Self: 'a, + { + todo!("ZigZagArray::flatten") } +} - fn serde(&self) -> Option<&dyn EncodingSerde> { - Some(self) +impl ArrayTrait for ZigZagArray<'_> { + fn len(&self) -> usize { + self.encoded().len() } }