8

I have a byte vector and want to replace every [1, 2, 3] with [4, 5, 6]. How is this possible in Rust?

let mut buf = vec![1, 2, 3, 7, 8];

// ?

assert_eq!(buf, vec![4, 5, 6, 7, 8]);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Uncreative Name
  • 311
  • 2
  • 9
  • 1
    Is the replacement slice necessarily the same length as the slice to replace? Or might you have to shrink/grow the `Vec`? – trent Jan 11 '19 at 16:29
  • 1
    I don't think there is any convenient general routine defined on `&[u8]` currently that does this for you, similar to the `str::replace` method. I'd probably just implement it myself, possibly by copying the [implementation for `str::replace`](https://doc.rust-lang.org/src/alloc/str.rs.html#268-278). (I am working on a byte string library for Rust, which will certainly support operations like this. But it's not finished yet.) – BurntSushi5 Jan 11 '19 at 16:37
  • Possible duplicate of [How to idiomatically copy a slice?](https://stackoverflow.com/questions/28219231/how-to-idiomatically-copy-a-slice) – trent Jan 11 '19 at 17:26
  • Applying the linked question here suggests `buf[0..3].copy_from_slice(&[4, 5, 6]);`. If the slices are not the same size, it's probably a duplicate of [Efficiently insert or replace multiple elements in the middle or at the beginning of a Vec?](https://stackoverflow.com/q/28678615/3650362) instead – trent Jan 11 '19 at 17:28
  • My mistake, I overlooked the word "every" in the question. But the links above are still good to look at. – trent Jan 11 '19 at 17:46
  • Another question: if you replace `[1, 2, 3]` with `[5, 1, 2]` within `[1, 2, 3, 3]`, do you expect `[5, 1, 2, 3]`? Both the existing answers will give you `[5, 5, 1, 2]`. – trent Jan 11 '19 at 20:51
  • @trentcl title for the question points : **"replace a subslice of a byte Vec/slice with another slice?"** . This is not valid for your second case because you are not replacing last element( " **3** ") with **another slice** ( just replacing with an element which is from **another slice**) – Ömer Erden Jan 11 '19 at 21:33
  • @ÖmerErden [This is what I mean](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=74ed2faa1f9cfd126b0c6bb35ee187f5). I took the `replace_slice` function straight from your answer. I don't expect this behavior is what OP wants. – trent Jan 11 '19 at 21:50
  • @trentcl i see, thanks i updated the code now it works with your case too. – Ömer Erden Jan 11 '19 at 22:06
  • See also [How can I find a subsequence in a &\[u8\] slice?](https://stackoverflow.com/questions/35901547/how-can-i-find-a-subsequence-in-a-u8-slice). – Shepmaster Jan 13 '19 at 18:49

3 Answers3

2

This function can do the job:

fn replace_slice<T>(source: &mut [T], from: &[T], to: &[T])
where
    T: Clone + PartialEq,
{
    let iteration = if source.starts_with(from) {
        source[..from.len()].clone_from_slice(to);
        from.len()
    } else {
        1
    };

    if source.len() > from.len() {
        replace_slice(&mut source[iteration..], from, to);
    }
}

This function is recursive but you can rewrite it using loops as well.


Example 1:

fn main() {
    let mut buf = vec![1, 2, 3, 7, 8, 1, 2, 3];

    replace_slice(&mut buf[..], &[1, 2, 3], &[4, 5, 6]);

    assert_eq!(buf, vec![4, 5, 6, 7, 8, 4, 5, 6]);
}

Playground


Example 2: (From the comment by trentcl)

fn main() {
    let mut buf = vec![1, 2, 3, 3, 4, 1, 2, 3];

    replace_slice(&mut buf[..], &[1, 2, 3], &[5, 1, 2]);

    assert_eq!(buf, vec![5, 1, 2, 3, 4, 5, 1, 2]);
}

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ömer Erden
  • 7,680
  • 5
  • 36
  • 45
1

This works when the slices have different lengths (similar to replace for str):

fn replace<T>(source: &[T], from: &[T], to: &[T]) -> Vec<T>
where
    T: Clone + PartialEq
{
    let mut result = source.to_vec();
    let from_len = from.len();
    let to_len = to.len();

    let mut i = 0;
    while i + from_len <= result.len() {
        if result[i..].starts_with(from) {
            result.splice(i..i + from_len, to.iter().cloned());
            i += to_len;
        } else {
            i += 1;
        }
    }

    result
}
burubum
  • 640
  • 1
  • 6
  • 18
0

Without recursion:

fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
where
    T: Clone + PartialEq,
{
    for i in 0..=buf.len() - from.len() {
        if buf[i..].starts_with(from) {
            buf[i..(i + from.len())].clone_from_slice(to);
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Nate
  • 481
  • 3
  • 11
  • 1
    You may consider to change your loop into this : `for i in 0..=buf.len() - from.len()` to skip unnecessary checks. – Ömer Erden Jan 11 '19 at 19:51
  • @ÖmerErden `source.len() - from.len()` will panic when `from` is larger than `source`. – trent Jan 11 '19 at 20:49
  • @trentcl thanks for pointing it out, it will also panic when "from" and "to" are not equally sized, due to assertion in "starts_with". Adding guards before loop will help. – Ömer Erden Jan 11 '19 at 21:28
  • @ÖmerErden [`starts_with`](https://doc.rust-lang.org/std/primitive.slice.html#method.starts_with) doesn't panic if `self` and `needle` are different sizes; it just returns `false`. – trent Jan 11 '19 at 21:52
  • @trentcl sorry it should be "clone_from_slice" – Ömer Erden Jan 11 '19 at 22:08