简体   繁体   English

如何根据集合中的消息对 mpsc::Receivers 进行排序?

[英]How do I sort mpsc::Receivers between collections depending on the message in it?

I have a collection containing std::sync::mpsc::Receiver<Message> and want to sort them into other collections depending on the message in it.我有一个包含std::sync::mpsc::Receiver<Message>集合,并希望根据其中的消息将它们分类到其他集合中。

I'm iterating into the receivers, testing if they received a message, if so I test the message nature, and depending on it, I move the receiver to another collection:我正在迭代接收器,测试他们是否收到消息,如果是,我测试消息性质,并根据它,我将接收器移动到另一个集合:

for receiver in receivers.iter() {
    if let Ok(msg) = receiver.try_recv() {
        match msg {
            Message::Foo => {
                // TODO remove receiver from receivers
                foos.push(receiver);
            },
            Message::Bar => {
                // TODO remove receiver from receivers
                bars.push(receiver);
            },
        }
    }
}

I do not know how to remove receiver from the receivers collection.我不知道如何从receivers集合中删除receiver receivers Since I'm borrowing it as immutable while iterating over it, I can not modify it without encountering compilation error由于我在迭代它时将它借用为不可变的,因此我无法在不遇到编译错误的情况下修改它

I have the feeling that I'm not using the right Rust tools to complete this very basic algorithm.我感觉我没有使用正确的 Rust 工具来完成这个非常基本的算法。

插图

To illustrate my problem, imagine that I'm sorting the colored balls from collection A to collections B and C, but I can not clone the balls and I can only see the color of a ball when taking it, which destroys the color when I take the next ball.为了说明我的问题,假设我正在将集合 A 中的彩色球分类到集合 B 和 C,但是我无法克隆这些球,而且我在拿球时只能看到球的颜色,这会破坏颜色接下一个球。

The solution of labeling the balls from number 0 to number 6, writing down its color aside to its index and then taking the balls to put them in the good collection is inconvenient.将球从 0 号标记到 6 号,将其颜色写在其索引旁边,然后将这些球放入好的集合中的解决方案是不方便的。

You can use Vec::retain .您可以使用Vec::retain

receivers.retain(|receiver| {
    if let Ok(msg) = receiver.try_recv() {
        match msg {
            Message::Foo => {
                foos.push(receiver);
            },
            Message::Bar => {
                bars.push(receiver);
            },
        }
        // By returning false, the receiver is removed.
        false
    } else {
        // By returning true, the receiver is kept.
        true
    }
});

To have the discarded items moved to some other vector, you can write your own retain function.要将丢弃的项目移动到其他向量,您可以编写自己的retain函数。 The algorithm found below will return the discarded items as an iterator.下面找到的算法会将丢弃的项目作为迭代器返回。 There is no guarantee of the ordering they are returned in. Unfortunately it does not support moving the channels to more than one other vector.无法保证它们返回的顺序。不幸的是,它不支持将通道移动到多个其他向量。

pub fn retain2<T, F>(vec: &mut Vec<T>, mut f: F) -> impl Iterator<Item = T> + '_
where
    F: FnMut(&mut T) -> bool,
{
    let len = vec.len();
    let mut del = 0;
    {
        let v = &mut **vec;

        for i in 0..len {
            if !f(&mut v[i]) {
                del += 1;
            } else if del > 0 {
                v.swap(i - del, i);
            }
        }
    }
    vec.drain(len - del ..)
}

This can be used like this:这可以像这样使用:

let iter = retain2(&mut receivers, |receiver| {
    if let Ok(msg) = receiver.try_recv() {
        match msg {
            Message::Foo => {
                // By returning false, the receiver is sent to the iterator.
                false
            },
        }
    } else {
        // By returning true, the receiver is kept in `receivers`.
        true
    }
});

// Append all of the removed iterators to this other vector.
// They are not guaranteed to be returned in the original order.
foos.extend(iter);

Alternatively, you can obtain the behavior described in the question exactly, but this cannot be done safely using the tools in the standard library.或者,您可以准确地获得问题中描述的行为,但这无法使用标准库中的工具安全地完成。

To do this in a way that properly handles panics, we can introduce a guard type:为了以正确处理恐慌的方式做到这一点,我们可以引入一种保护类型:

struct RetainGuard<'a, T> {
    vec: &'a mut Vec<T>,
    // How many items have been taken?
    taken: usize,
    // How many items have been inserted again?
    // This must never be larger than `taken`.
    inserted: usize,
    // The original length of the vector.
    vec_len: usize,
}

We can now define two helpers for this type:我们现在可以为这种类型定义两个助手:

impl<'a, T> RetainGuard<'a, T> {
    fn take(&mut self) -> Option<T> {
        let i = self.taken;
        if i == self.vec_len {
            return None;
        }
        self.taken += 1;
        unsafe {
            Some(std::ptr::read(&mut self.vec[i]))
        }
    }
    // Safety:
    // Must not insert more items than has already been taken.
    unsafe fn insert(&mut self, val: T) {
        std::ptr::write(&mut self.vec[self.inserted], val);
        self.inserted += 1;
        debug_assert!(self.inserted <= self.taken);
    }
}

And we can now define a destructor to properly set the length of the vector, and in the case of panics, drop the items we have yet to inspect.我们现在可以定义一个析构函数来正确设置向量的长度,并且在发生恐慌的情况下,删除我们尚未检查的项目。

impl<'a, T> Drop for RetainGuard<'a, T> {
    fn drop(&mut self) {
        unsafe {
            self.vec.set_len(self.inserted);
            // This is an empty slice unless the closure panics.
            std::ptr::drop_in_place(self.not_taken_slice());
        }
    }
}
impl<'a, T> RetainGuard<'a, T> {
    fn not_taken_slice(&mut self) -> &mut [T] {
        unsafe {
            let ptr = self.vec.as_mut_ptr();
            let start = ptr.add(self.taken);
            let len = self.vec_len - self.taken;
            std::slice::from_raw_parts_mut(start, len)
        }
    }
}

Finally we can use our guard to implement the following method.最后我们可以使用我们的守卫来实现以下方法。 Note that the destructor properly sets the length of the vector before the function returns.请注意,析构函数在函数返回之前正确设置了向量的长度。

pub fn retain3<T, F>(vec: &mut Vec<T>, mut f: F)
where
    F: FnMut(T) -> Option<T>,
{
    let mut guard = RetainGuard::new(vec);
    while let Some(item) = guard.take() {
        if let Some(item) = f(item) {
            unsafe {
                // SAFETY: We have called take more times than insert.
                guard.insert(item);
            }
        }
    }
}

Using this method, it should be rather straight-forward to implement the desired functionality.使用这种方法,实现所需的功能应该是相当直接的。 See the full code here .此处查看完整代码。

You can use Vec::drain to move items out of a vector.您可以使用Vec::drain将项目移出向量。 This moves all of the items, but we can handle the ones to be kept by moving them into a new Vec temporarily.这会移动所有项目,但我们可以通过将它们临时移动到新的Vec来处理要保留的项目。

fn distribute(receivers: &mut Vec<Receiver<Message>>) -> (Vec<Receiver<Message>>, Vec<Receiver<Message>>) {
    let mut foos: Vec<Receiver<Message>> = Vec::new();
    let mut bars: Vec<Receiver<Message>> = Vec::new();
    let mut go_back: Vec<Receiver<Message>> = Vec::new();
    for receiver in receivers.drain(..) {
        if let Ok(msg) = receiver.try_recv() {
            match msg {
                Message::Foo => &mut foos,
                Message::Bar => &mut bars,
                _ => &mut go_back,
            }.push(receiver);
        }
    }
    *receivers = go_back;
    (foos, bars)
}

Of course, this allocates and resizes a new vector every time;当然,这每次都会分配和调整一个新的向量; a slight improvement would be to swap between two vectors instead of allocating one each time.一个小小的改进是在两个向量之间交换而不是每次分配一个。 I thought the not-yet-stable Vec::drain_filter might be helpful but the filter can only return a bool , so it can't communicate where to move the item.我认为尚未稳定的Vec::drain_filter可能会有所帮助,但过滤器只能返回bool ,因此它无法传达将项目移动到何处。

Compilable version in Rust Playground Rust Playground 中的可编译版本

Ideally you'd combine Vec::retain (originally shown by @AliceRyhl, but later edited to include more complex approaches) with either the crossbeam channels, which are Clone , or with Rc<Receiver> , which is also (trivially) clonable, but incurs a miniscule run-time cost and a heap allocation.理想情况下,您可以将Vec::retain (最初由@AliceRyhl 展示,但后来编辑以包含更复杂的方法)与横梁通道( Clone )或Rc<Receiver> ,后者也是(简单地)可克隆的,但会产生极小的运行时成本和堆分配。 If those are unacceptable, I propose two variants:如果这些是不可接受的,我提出两种变体:

[✓] Variant 1: Assuming the collection is a vector, iterate using indices. [✓] 变体 1:假设集合是一个向量,使用索引进行迭代。 It's not super-elegant, but it's not that bad, and it gets the job done with minimum hassle and with code anyone can understand:它不是超级优雅,但也不是那么糟糕,它以最少的麻烦和任何人都可以理解的代码完成工作:

// [✓] this is the accepted variant
let mut pos = 0;
while pos < receivers.len() {
    if let Ok(msg) = receivers[pos].try_recv() {
        match msg {
            Message::Foo => foos.push(receivers.remove(pos)),
            Message::Bar => bars.push(receivers.remove(pos)),
            _ => pos += 1,
        }
    } else {
        pos += 1;
    }
}

Variant 2: Store receivers in Vec<Option<Receiver>> .变体 2:将接收器存储在Vec<Option<Receiver>> Then you can easily remove a receiver without mutating the Vec by taking it from the Option and leaving None in its place:然后你就可以轻松去除接收器没有突变的Vec通过采取从它Option ,留下None在它的位置:

for receiver in receivers.iter_mut() {
    if receiver.is_none() {
        continue;
    }
    if let Ok(msg) = receiver.as_ref().unwrap().try_recv() {
        match msg {
            Message::Foo => foos.push(receiver.take().unwrap()),
            Message::Bar => bars.push(receiver.take().unwrap()),
        }
    }
}

If None s remaining in receivers are an issue, remove them with something like receivers.retain(Option::is_some) .如果receivers中剩余的None是一个问题,请使用receivers.retain(Option::is_some)类的东西删除它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM