이터레이터
이터레이터란?
이터레이터(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)]
이터레이터를 만들어내는 메소드 중에서 가장 중요하게 봐야 하는 두 가지가 있는데 바로 map
과 filter
입니다. 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); }