이터레이터

이터레이터란?

이터레이터(iterator)는 반복 가능한 시퀀스(sequence)를 입력으로 받아 각 원소에 특정 작업을 수행할 수 있도록 하는 기능입니다. 앞에서 배운 벡터를 이용해 값을 순서대로 출력하는 예제를 만들어 보겠습니다.

fn main() {
    let names = vec!["james", "cameron", "indo"];
    for name in names {
        println!("{}", name);
    }
    println!("{:?}", names);
}

실행 결과

error[E0382]: borrow of moved value: `names`
   --> src/main.rs:6:22
    |
2   |     let names = vec!["james", "cameron", "indo"];
    |         ----- move occurs because `names` has type `Vec<&str>`, which does not implement the `Copy` trait
3   |     for name in names {
    |                 -----
    |                 |
    |                 `names` moved due to this implicit call to `.into_iter()`
    |                 help: consider borrowing to avoid moving into the for loop: `&names`
...
6   |     println!("{:?}", names);
    |                      ^^^^^ value borrowed here after move
    |

컴파일하면 에러가 발생하는데, for name in names 에서 names가 암묵적으로 .into_iter() 메소드를 호출했다고 나옵니다. 여기서 into_iter()가 바로 이터레이터인데, 벡터 원소의 값을 for 루프 안으로 가져와 반복하는 역할을 수행합니다. 이때 값이 가져와지기 때문에 원소의 소유권도 함께 이동됩니다. 이미 이동된 소유권을 println!("{:?}", names); 에서 참조하기 때문에 에러가 발생합니다.

이를 해결하기 위해서는 명시적으로 iter() 메소드를 호출해 원소를 for 루프 안으로 전달해주어야 합니다.

fn main() {
    let names = vec!["james", "cameron", "indo"];
    for name in names.iter() {
        println!("{}", name);
    }
    println!("{:?}", names);
}

실행 결과

james
cameron
indo
["james", "cameron", "indo"]

iter() 메소드는 선언 즉시 원소를 내놓는 것이 아니라, 값이 필요해지면 그때 원소를 리턴합니다. 따라서 다음과 같은 코드가 가능합니다.

fn main() {
    let names = vec!["james", "cameron", "indo"];
    let names_iter = names.iter();
    for name in names_iter {
        println!("{}", name);
    }
    println!("{:?}", names);
}

실행 결과

james
cameron
indo
["james", "cameron", "indo"]

이터레이터를 소비하는 메소드들

이번 단원에서는 이터레이터에 속한 메소드들을 이용해 원소에 여러 작업을 수행해 보겠습니다. 파이썬에서는 합계, 최대값, 최소값을 구하는 함수인 sum, max, min을 리스트에 직접 사용합니다.

nums = [1, 2, 3]

sum = sum(nums)
max = max(nums)
min = min(nums)
print(f"sum: {sum}, max: {max}, min: {min}")

실행 결과

sum: 6, max: 3, min: 1

러스트에서는 이터레이터에서 sum, max, min 메소드를 호출합니다.

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

    let sum: i32 = num.iter().sum();
    let max = num.iter().max().unwrap();
    let min = num.iter().min().unwrap();
    println!("sum: {}, max: {}, min: {}", sum, max, min);
}

실행 결과

sum: 6, max: 3, min: 1

새로운 이터레이터를 만드는 메소드들

이터레이터 메소드 중에는 새로운 이터레이터를 만드는 메소드들이 있습니다. 대표적으로 인덱스와 원소를 함께 반복하는 enumerate 와 두 시퀀스의 원소를 순서대로 함께 묶어 반복하는 zip 입니다.

먼저 파이썬 코드는 다음과 같습니다.

nums1 = [1, 2, 3]
nums2 = [4, 5, 6]

enumer = list(enumerate(nums1))
print(enum)
zip = list(zip(nums1, nums2))
print(zip)

실행 결과

[(0, 1), (1, 2), (2, 3)]
[(1, 4), (2, 5), (3, 6)]

마찬가지로 러스트에서도 원소와 인덱스를 동시에 반복하거나 두 시퀀스의 원소를 동시에 반복할 수 있습니다.

fn main() {
    let nums1 = vec![1, 2, 3];
    let nums2 = vec![4, 5, 6];

    let enumer: Vec<(usize, &i32)> = nums1.iter().enumerate().collect();
    println!("{:?}", enumer);

    let zip: Vec<(&i32, &i32)> = nums1.iter().zip(nums2.iter()).collect();
    println!("{:?}", zip);
}

실행 결과

[(0, 1), (1, 2), (2, 3)]
[(1, 4), (2, 5), (3, 6)]

이터레이터를 만들어내는 메소드 중에서 가장 중요하게 봐야 하는 두 가지가 있는데 바로 mapfilter 입니다. map 은 주어진 함수를 각 원소에 적용합니다. filter 는 주어진 시퀀스에서 기준에 맞는 결과만 남기는 방법입니다. 아래 두 예제에서는 시퀀스의 원소에 1을 더한 새로운 시퀀스를 만들거나, 원소 중 홀수인 값만 남기도록 했습니다.

nums = [1, 2, 3]

f = lambda x: x + 1

print(list(map(f, nums)))
print(list(filter(lambda x: x % 2 == 1, nums)))

실행 결과

[2, 3, 4]
[1, 3]

러스트 코드에서는 클로저를 이용해 동일한 내용을 구현했습니다. 이때 filter 의 경우, 기존의 원소의 값을 이동해서 새로운 벡터를 만들기 때문에 into_iter 메소드로 이터레이터를 만들었습니다.

fn main() {
    let nums: Vec<i32> = vec![1, 2, 3];

    let f = |x: &i32| x + 1;

    let maps: Vec<i32> = nums.iter().map(f).collect();
    println!("{:?}", maps);

    let filters: Vec<i32> = nums.into_iter().filter(|x| x % 2 == 1).collect();
    println!("{:?}", filters);
}

실행 결과

[2, 3, 4]
[1, 3]

원본 벡터를 필터 이후에도 사용하기 위해서는 두 가지 방법이 있습니다. 원본 벡터를 복사(clone)하는 방법

fn main() {
    let nums: Vec<i32> = vec![1, 2, 3];

    let f = |x: &i32| x + 1;

    let maps: Vec<i32> = nums.iter().map(f).collect();
    println!("{:?}", maps);

    let filters: Vec<i32> = nums.clone().into_iter().filter(|x| x % 2 == 1).collect();
    println!("{:?}", filters);

    println!("{:?}", nums);
}

이터레이터를 복사하는 방법

fn main() {
    let nums: Vec<i32> = vec![1, 2, 3];

    let f = |x: &i32| x + 1;

    let maps: Vec<i32> = nums.iter().map(f).collect();
    println!("{:?}", maps);

    let filters: Vec<i32> = nums.iter().filter(|x| *x % 2 == 1).cloned().collect();
    println!("{:?}", filters);

    println!("{:?}", nums);
}