【问题标题】:Iterate slices of size N over some contiguous container在某个连续容器上迭代大小为 N 的切片
【发布时间】:2020-10-06 13:39:30
【问题描述】:

假设我们有一个点向量,写成一个扁平的整数序列。向量的大小是精确的,我们希望将其转换为 Point 结构的向量。所以我的转换目前看起来是这样的。

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}
impl Point {
    fn new(x: i32, y : i32) -> Point {
        Point{x,y}
    }
}
impl From<Point> for String {
    fn from(point: Point) -> String {
        format!("({},{})", point.x, point.y)
    }
}

fn main() {
    let vec = vec![2,1,4,3,6,5]; // We want convert that
    let even = vec.iter().step_by(2);
    let odd = vec.iter().skip(1).step_by(2);
    let points: Vec<Point> 
                = even.zip(odd)
                .map(|(x,y)|Point::new(*x,*y))
                .collect();

    println!("{:?}", points);
}

像一个魅力一样工作,直到我们想要添加z 组件,并在每个循环中查看三个元素。我还没有从docs 找到任何合适的方法。那么,是否已经有一种通用的方法可以在向量上迭代一定大小的切片并将其解压缩到我的结构中,或者没有这种方法,我应该自己实现这样的迭代器?使用像serde 这样更重的机器是不是一个信号。

【问题讨论】:

  • itertools 有一个支持两个以上元素的zip equivalent。这就是你要找的东西吗?
  • @mcarton 可能。看不懂例子。 izip 的整体文档很差。主要问题我可以在没有外部板条箱的情况下做到这一点,只有std
  • “我可以在没有外部 crates 的情况下做到吗,只有 std”通常是错误的思考方式。 Rust 的标准库故意很小,itertools 是一个广泛使用的 crate。
  • @mcarton 不是真的。如果我可以用我已经拥有的工具做点什么,我就不需要额外的板条箱来膨胀我的二进制文件。所以,这正是问题所在——我需要外部的东西还是开箱即用的东西。另外,寻找合适的箱子很困难,但现在我知道itertools,谢谢你

标签: rust data-conversion iterable


【解决方案1】:

在 std 中使用 chunks_exact 很容易实现切片。

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
    z: i32,
}
impl Point {
    fn new(x: i32, y : i32, z: i32) -> Point {
        Point{x,y, z}
    }
}

fn main() {
    let vec = vec![2,1,4,3,6,5];
    let points: Vec<Point> 
                = vec.chunks_exact(3)
                .map(|chunk|Point::new(chunk[0], chunk[1], chunk[2]))
                .collect();

    println!("{:?}", points);
}

【讨论】:

  • 太棒了,正是我一直在寻找的东西
【解决方案2】:

我认为没有预先完成的方法,但您可以轻松编写可以快速调整的代码以包含第三个 z 组件,类似于此 (Playground):

#[derive(Debug, Default)]
struct PointBuilder {
    x: Option<i32>,
    y: Option<i32>,
    // add a z here
}

impl PointBuilder {
    fn add(&mut self, value: i32) {
        if self.x.is_none() {
            self.x = Some(value);
        } else if self.y.is_none() {
            self.y = Some(value);
        }
        // add a z here
    }

    fn try_build(&self) -> Option<Point> {
        // and one last z in this match expression
        match (self.x, self.y) {
            (Some(x), Some(y)) => Some(Point::new(x, y)),
            _ => None,
        }
    }
}

fn convert_to_points(data: impl IntoIterator<Item = i32>) -> Vec<Point> {
    data.into_iter()
        .fold((PointBuilder::default(), Vec::<Point>::new()), |(mut builder, mut points), value| {
                builder.add(value);
                if let Some(point) = builder.try_build() {
                    points.push(point);
                    (PointBuilder::default(), points)
                } else {
                    (builder, points)
                }
        })
        .1
}

fn main() {
    let vec = vec![2, 1, 4, 3, 6, 5]; // We want convert that
    let points = convert_to_points(vec);

    println!("{:?}", points);
}

这当然可以作为一个 proc-macro-derive 代码生成器来完成,如果你需要它来处理几十个结构,但我不知道有任何库已经实现了这个,所以这取决于你的用例如果这是值得的。

【讨论】:

  • Builder 模式是可以的,但是方法 add 会随着 struct 中的每个新维度而增长,而不会提供太多有价值的代码并且不容易出错。 proc 宏可以挽救局面,但仍然不是很优雅的方式。谢谢
  • @Sugar 你在问题中提到了serde,他们也使用 proc 宏。 Proc 宏本身看起来并不优雅,但它们可以让您编写优雅的代码
  • 是的,我知道 serdeminiserde 和其他一些序列化 crate 中的 proc 宏,试图减少这种例程。只是想在没有真正必要的情况下避免使用它们。
猜你喜欢
  • 2017-10-22
  • 2023-01-08
  • 1970-01-01
  • 2015-06-16
  • 2016-11-04
  • 1970-01-01
  • 2017-08-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多