Условная сортировка Vec в Rust

Допустим, я хочу отсортировать 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.

Что вы подразумеваете под «только вектором, а не элементами внутри»? Кроме того, сортировка вектора не требует его копирования. В любом случае, вы можете делать с вектором все, что захотите, поскольку вы передаете и возвращаете его по значению.

Acorn 13.12.2020 21:46

почему бы просто не отсортировать x? ссылка на детскую площадку

kmdreko 13.12.2020 21:51

Я добавил комментарий над строкой подбрасывания монеты, который, надеюсь, прояснит ситуацию — мне нужно сначала отсортировать, а затем решить, возвращать ее или нет.

xixixao 13.12.2020 22:01

@Acorn «только вектор, а не элементы внутри» - я имею в виду только то, что я не могу клонировать элементы внутри. Элементы не клонируются.

xixixao 13.12.2020 22:02

@xixixao По какой причине ты не можешь Clone? (например, ваш тип действительно такой «большой»?). Можно делать то, что вы хотите, и сейчас я пишу ответ, но для этого требуется двойная сортировка Vec.

vallentin 13.12.2020 22:22
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
5
537
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете .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()
}

Спасибо, это полезно, потому что это действительно работает. Я чувствую, что должно быть решение, которое не связано с другим видом (и связанной с этим сложностью кода, особенно в моем коде, который сложнее, чем пример), но я собираюсь принять это сейчас.

xixixao 14.12.2020 06:12

Если 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;
    }
}

Детская площадка

Если 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) };
    }
}

Детская площадка

Наконец, вот безопасное решение, которое не требует 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
}

Детская площадка

Другие вопросы по теме

Каков правильный тип возвращаемого значения функции, возвращающей итератор, созданный с замыканием, заимствующим себя
Как разрешить нескольким потокам писать в одну и ту же переменную без мьютекса в Rust?
Borrow Checker очень осторожен, ' ' является ссылкой, поэтому данные, на которые он ссылается, не могут быть заимствованы как изменяемые
Основы управления памятью в Rust lang
Компилятор Rust выдает ошибку, связанную с перемещением данных из общей ссылки
Ссылка `&`, поэтому данные, на которые она ссылается, не могут быть заимствованы как изменяемые
Проблема заимствования при возврате значения ошибки в операторе соответствия
Значение перемещено в замыкание в предыдущей итерации цикла
Rust: чем отличается метод клонирования среза?
Rust: Безопасно ли приводить изменяемое заимствование к указателю и обратно (чтобы успокоить средство проверки заимствования), если я знаю, что это будет только один экземпляр?