【问题标题】:Conditionally sort a Vec in Rust有条件地对 Rust 中的 Vec 进行排序
【发布时间】:2020-12-13 20:41:09
【问题描述】:

假设我想对 non-Clone 项的 Vec 进行排序 - 但只是可能(这是我代码中问题的简化示例)。

我的尝试是这样的:

fn maybe_sort<T>(x: Vec<T>) -> Vec<T>
where
    T: std::cmp::Ord,
{
    // First, I need a copy of the vector - but only the vector, not the items inside
    let mut copied = x.iter().collect::<Vec<_>>();
    copied.sort();
    // In my actual code the line below depends on the sorted vec
    if rand::random() {
        return copied.into_iter().map(|x| *x).collect::<Vec<_>>();
    } else {
        return x;
    }
}

唉,借阅检查员不高兴。我对 Vec 中的每个项目都有一个共享引用,虽然我从来没有返回对同一个项目的 2 个引用,但 Rust 无法判断。

没有unsafe 有没有办法做到这一点? (如果没有,使用unsafe 最干净的方法是什么。

【问题讨论】:

  • 只有向量,而不是里面的项目”是什么意思?此外,对向量进行排序不需要复制它。无论如何,你可以对向量做任何你想做的事情,因为你是按值传递和返回它。
  • 为什么不对x进行排序? playground link
  • 我在硬币翻转线上方添加了一条评论,希望能更清楚地说明 - 我需要先排序,然后再决定是否退货。
  • @Acorn “只有向量,而不是里面的项目”——我的意思是我不能克隆里面的项目。这些项目不可复制。
  • @xixixao 你有什么理由不能Clone? (例如,您的类型真的那么“大”吗?)。可以做你想做的事,我目前正在写一个答案,但它需要对Vec 进行双重排序。

标签: rust borrow-checker


【解决方案1】:

您可以.enumerate() 这些值保留其原始索引。您可以根据其值T 对其进行排序,并决定是返回排序后的版本,还是通过按原始索引排序来反转排序。

fn maybe_sort<T: Ord>(x: Vec<T>) -> Vec<T> {
    let mut items: Vec<_> = x.into_iter().enumerate().collect();
    items.sort_by(|(_, a), (_, b)| a.cmp(b));
    
    if rand::random() {
        // return items in current order
    }
    else {
        // undo the sort
        items.sort_by_key(|(index, _)| *index);
    }
    
    items.into_iter().map(|(_, value)| value).collect()
}

【讨论】:

  • 谢谢,这很有帮助,因为它确实有效。我觉得应该有一个不涉及其他类型的解决方案(以及所涉及的代码复杂性,尤其是在我的代码中,它比示例更复杂),但我现在要接受这个。
【解决方案2】:

如果T 实现Default,您可以使用单一排序而不使用unsafe,如下所示:

fn maybe_sort<T: Ord + Default> (mut x: Vec<T>) -> Vec<T> {
    let mut idx = (0..x.len()).collect::<Vec<_>>();
    idx.sort_by_key (|&i| &x[i]);
    if rand::random() {
        return x;
    } else {
        let mut r = Vec::new();
        r.resize_with (x.len(), Default::default);
        for (i, v) in idx.into_iter().zip (x.drain(..)) {
            r[i] = v;
        }
        return r;
    }
}

Playground

如果T没有实现Default,同样的事情可以用MaybeUninit做:

use std::mem::{self, MaybeUninit};

fn maybe_sort<T: Ord> (mut x: Vec<T>) -> Vec<T> {
    let mut idx = (0..x.len()).collect::<Vec<_>>();
    idx.sort_by_key (|&i| &x[i]);
    if rand::random() {
        return x;
    } else {
        let mut r = Vec::new();
        r.resize_with (x.len(), || unsafe { MaybeUninit::uninit().assume_init() });
        for (i, v) in idx.into_iter().zip (x.drain(..)) {
            r[i] = MaybeUninit::new (v);
        }
        return unsafe { mem::transmute::<_, Vec<T>> (r) };
    }
}

Playground

最后,这是一个安全的解决方案,它不需要T 来实现Default,但会分配一个额外的缓冲区(理论上有一种方法可以对索引进行重新排序,但我将把它留作练习给读者☺):

fn maybe_sort<T: Ord> (mut x: Vec<T>) -> Vec<T> {
    let mut idx = (0..x.len()).collect::<Vec<_>>();
    idx.sort_by_key (|&i| &x[i]);
    if rand::random() {
        let mut rev = vec![0; x.len()];
        for (i, &j) in idx.iter().enumerate() {
            rev[j] = i;
        }
        for i in 0..x.len() {
            while rev[i] != i {
                let j = rev[i];
                x.swap (j, i);
                rev.swap (j, i);
            }
        }
    }
    x
}

Playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-30
    • 2015-01-06
    • 2018-11-30
    • 1970-01-01
    • 2016-05-14
    相关资源
    最近更新 更多