[Rust] 배열 셔플해보기

개요

Rust 에서 vec! 으로 만든 배열의 값들을 셔플하는 간단한 예제를 만들어보아요. rust 가 너무 헷갈려서 이 쉬워보이는 연습도 힘겹게 했네요.

미리 알고 있으면 좋은 것들

  • let vs let mut : 변수를 선언할 때에는 기본적으로 불변형(immutable) 입니다. mut 키워드를 뒤에 붙이면 변형을 할 수 있습니다.
  • 슬라이스: 그냥 배열같은 것의 일부분의 레퍼런스 같은 것이라고 이해하고 있습니다.
  • Range: 반복적인 동작을 정형화한 어떤 것이라고 이해하고 있습니다. 이터레이터랑 비슷한 것. (0..5) 라면 0이상 5미만의 어떤 반복적인 행동을 하겠다는 맥락을 만드는 것입니다.

랜덤 수 만들기

rand 라는 크레이트를 사용합니다. 필요한 개념은 많지 않습니다.

코드 및 결과

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    const LEN: usize = 30;
    let mut arr: Vec<_> = (0..LEN).collect();
    println!("before: {:?}", arr); // 
    (0..LEN - 1).for_each(|index| {
        let change = rng.gen_range(index + 1..LEN);
        arr.swap(index, change);
    });
    println!("after: {:?}", arr);
}

아래는 그 결과입니다.

before: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
after: [21, 27, 24, 15, 29, 9, 26, 6, 0, 11, 4, 2, 8, 10, 25, 23, 14, 7, 20, 17, 16, 3, 12, 19, 13, 22, 5, 28, 18, 1]

코드 설명

일단 thread_rng를 통해 랜덤 숫자 생성기를 만듭니다. 랜덤 숫자도 사실 어떤 계산된 값인데 무작위로 보이는 것처럼 할 뿐이고, 그 계산을 담당하는 객체입니다.

길이는 30으로 그냥 두었습니다. Rangecollect 를 이용하여 배열을 생성해줍니다. 그 다음 index0 부터 LEN - 1 미만까지 반복하며 해당 인덱스 이후의 나머지 부분의 랜덤한 위치와 swap 해줍니다. 원리는 간단하고 코드도 그렇게 길지는 않지만 여전히 불변성이 몸에 익지 않네요. 허허..

후기

immutable letconst 의 기능상 유사점 때문에 헷갈림

  • const 는 컴파일-타임에 값으로써 모조리 변환됩니다. 마치 C 에서의 #define 과 같은 효과라고 보면 될 것 같습니다. let 은 실제 런타임 때 메모리에 값이 저장됩니다.
  • letmut 키워드와 함께 있어야 변수 수정이 가능합니다. mut 키워드가 없더라도 쉐도잉(Shadowing)을 통해 변수의 값을 변경할 수 있습니다. 그 외 쓰임새에서 이런저런 차이가 있습니다.
  • 그래서 드는 의문점: 컴파일러가 let mut 이 아닌 let 으로 선언된 immutable 변수의 쉐도잉 여부를 판단하여, 만약 쉐도잉이 되지 않았다면 모조리 const 처럼 컴파일타임에 상수로 변경해버릴 수 있는 건 아닌가요? 하여튼 기능상의 유사점 때문에 어느 상황에서 immutable let 변수를 쓸지, 아니면 const 상수를 쓸지 감이 잘 안옵니다.
  • 아하.. 만약 런타임 중에 새로운 값을 생성할 필요가 있는데, (예를 들어 표준 입력으로 값을 받는다던가) 그 값이 생성 이후 바뀌지 않아야 한다는 맥락을 만들기 위해서 immutable let 이 필요한 것이군요! 이제 깨달음을 얻었습니다.
  • 참고: 그나마 궁금증이 해소된 StackOverflow – What is the difference between immutable and const variables in Rust?

뭔가 반복해야 하는 것은 이터레이터를 적극적으로 활용하자

자바스크립트에서 map, filter, foreach 등을 적극적으로 활용했었는데, rust에서도 비슷한 개념이 있습니다. 어떤 컨테이너에서 iter() 를 통해서 반복자를 얻을 수 있고, 이것을 이용해 for_each 등의 반복되는 동작을 지정해줄 수 있습니다. 자바스크립트와 다른 점은 반복자의 종류에 따라서 내부의 값을 바꿀 수도 있고, 바꿀 수도 없다는 점입니다. 값을 변형하고 싶다면 iter_mut() 을 활용해야 합니다.

rand 크레이트에 shuffle 기능이 존재함.

이건 꿈에도 몰랐습니다. 하기는 없을 리가 없지요… 내부 소스를 보니까 위와 거의 비슷하게 동작하네요. 아래는 그 내부 소스입니다.

pub trait SliceRandom {
    // ...
    fn shuffle<R>(&mut self, rng: &mut R)
    where R: Rng + ?Sized {
        for i in (1..self.len()).rev() {
            // invariant: elements with index > i have been locked in place.
            self.swap(i, gen_index(rng, i + 1));
        }
    }
}

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Scroll to top