如何在Rust中合并列表的两个元素?

时间:2019-03-15 16:56:30

标签: performance optimization rust

我一直在努力优化我的代码部分,并且发现了一个我可以利用一些社区智慧的地方。我实质上是在尝试合并列表中的两个元素而不移动列表中的元素(通过两个remove和insert),因为据我所知,Rust这样做向量要花费O(n)时间。 / p>

看一眼捕获我问题本质的代码:

use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BinaryHeap;

#[derive(PartialOrd, Ord, PartialEq, Eq)]
pub struct Num {
    pub num: usize
}

impl Num {
    pub fn new(num: usize) -> Num {
        Num {
            num
        }
    }
}

fn main() {
    let mut a = vec![];
    for i in 0..10 {
        a.push(Rc::new(RefCell::new(Num::new(i))));
    }
    let mut b = BinaryHeap::with_capacity(a.len());
    for i in 0..a.len() - 1 {
        b.push((i, Rc::clone(&a[i]), Rc::clone(&a[i + 1])));
    }

    drop(a);

    while !b.is_empty() {
        let c = b.pop().unwrap();
        let first = c.1;
        let next = c.2;
        println!("c: c.0: {}", c.0);
        println!("c: first.num before: {}", RefCell::borrow(&first).num);
        println!("c: next.num before: {}", RefCell::borrow(&next).num);

        // Here I want to replace the two structs referenced in first and next
        // with a single new struct that first and next both point to.
        // e.g. first -> new_num <- next

        println!("c: first.num after: {}", RefCell::borrow(&first).num);
        println!("c: next.num after: {}", RefCell::borrow(&next).num);
        assert_eq!(RefCell::borrow(&first).num, RefCell::borrow(&next).num);
    }
}

我希望能够将一个列表中的两个元素合并为一个伪元素,其中前面的两个“元素”实际上只是指向同一新元素的指针。但是,我很难找到一种无需复制列表中的内存或结构的方法。

1 个答案:

答案 0 :(得分:2)

我对您的要求的理解是,您需要Vec才能容纳 值为引用另一个项目的项目,同时使结构与您呈现的内容相似

我们可以通过将商品类型更改为enum进行建模,该商品类型可以包含值或对另一个商品的引用:

pub enum Num {
    Raw(usize),
    Ref(Rc<RefCell<Num>>),
}

并添加方法以包括用于构造不同变体和访问基础数值的抽象:

impl Num {
    pub fn new(num: usize) -> Num {
        Num::Raw(num)
    }

    pub fn new_ref(other: Rc<RefCell<Num>>) -> Num {
        Num::Ref(other)    
    }

    pub fn get_num(&self) -> usize {
        match &self {
            Num::Raw(n) => *n,
            Num::Ref(r) => r.borrow().get_num()
        }
    }
}

如果您这样创建一个新值:

let new_num = Rc::new(RefCell::new(Num::new(100)));

您可以像这样在其他节点中引用它:

*first.borrow_mut() = Num::new_ref(Rc::clone(&new_num));
*next.borrow_mut() = Num::new_ref(Rc::clone(&new_num));

完整代码如下:

use std::cell::RefCell;
use std::rc::Rc;
use std::collections::BinaryHeap;

#[derive(PartialOrd, Ord, PartialEq, Eq)]
pub enum Num {
    Raw(usize),
    Ref(Rc<RefCell<Num>>),
}

impl Num {
    pub fn new(num: usize) -> Num {
        Num::Raw(num)
    }

    pub fn new_ref(other: Rc<RefCell<Num>>) -> Num {
        Num::Ref(other)    
    }

    pub fn get_num(&self) -> usize {
        match &self {
            Num::Raw(n) => *n,
            Num::Ref(r) => r.borrow().get_num()
        }
    }
}

fn main() {
    let mut a = vec![];
    for i in 0..10 {
        a.push(Rc::new(RefCell::new(Num::new(i))));
    }
    let mut b = BinaryHeap::with_capacity(a.len());
    for i in 0..a.len() - 1 {
        b.push((i, Rc::clone(&a[i]), Rc::clone(&a[i + 1])));
    }

    drop(a);

    let new_num = Rc::new(RefCell::new(Num::new(100)));

    while !b.is_empty() {
        let c = b.pop().unwrap();
        let first = c.1;
        let next = c.2;
        println!("c: c.0: {}", c.0);
        println!("c: first.num before: {}", RefCell::borrow(&first).get_num());
        println!("c: next.num before: {}", RefCell::borrow(&next).get_num());

        *first.borrow_mut() = Num::new_ref(Rc::clone(&new_num))
        *next.borrow_mut() = Num::new_ref(Rc::clone(&new_num))

        println!("c: first.num after: {}", RefCell::borrow(&first).get_num());
        println!("c: next.num after: {}", RefCell::borrow(&next).get_num());
        assert_eq!(RefCell::borrow(&first).get_num(), RefCell::borrow(&next).get_num());
    }
}

关于这种方法是否会比其他方法具有更好的性能,很难说。您的起点似乎很复杂,如果您可以简化它并使用其他基础数据结构,则应尝试使用它并进行基准测试。我常常对Vec上O(n)运算的实际速度感到惊讶,即使大小约为1000个或更多。