Reputation: 3330
I want to sort (reorder) a Vec
in-place by a predefined ordering in Rust.
For example:
let i = vec![0, 3, 2, 1];
let mut v = vec!["a", "b", "c", "d"];
v.sort_by_indices(&i);
assert_eq!(v, &["a", "d", "c", "b"]);
I would like to do this in-place. In my use case, v
takes up a lot of memory.
This question is a follow-up to How to get the indices that would sort a Vec?
Upvotes: 6
Views: 2800
Reputation: 59882
An in-place implementation is tricky but possible in O(n) time. It works by chasing indices and swapping elements until it gets back to where it started. Unfortunately, this does require scratch space to keep track of what elements are already sorted. Here's an implementation that reuses the index array if its allowed to consume it:
fn sort_by_indices<T>(data: &mut [T], mut indices: Vec<usize>) {
for idx in 0..data.len() {
if indices[idx] != idx {
let mut current_idx = idx;
loop {
let target_idx = indices[current_idx];
indices[current_idx] = current_idx;
if indices[target_idx] == target_idx {
break;
}
data.swap(current_idx, target_idx);
current_idx = target_idx;
}
}
}
}
See it working on the playground (with improvements by @Jmb).
Otherwise you would need a separate allocation for the scratch space (perhaps a BitVec
). Feel free to comment or edit if this method is possible without keeping track of the sorted elements.
Upvotes: 8
Reputation: 42678
You can create a HashMap
or BTreeMap
lookup table and use it as key searcher:
use std::collections::HashMap;
fn main() {
let i = vec![0, 3, 2, 1];
let mut v = vec!["a", "b", "c", "d"];
let keys: HashMap<_, _> = v.iter().cloned().zip(i.iter()).collect();
v.sort_by_cached_key(|e| keys[e]);
assert_eq!(v, &["a", "d", "c", "b"]);
}
Upvotes: 1
Reputation: 1235
In the same way it enough to implement yourself
let i = vec![0, 3, 2, 1];
let mut v = vec!["a", "b", "c", "d"];
v = i.into_iter().map(|x| v[x]).collect::<Vec<&str>>();
assert_eq!(v, &["a", "d", "c", "b"]);
Upvotes: 1