티스토리 뷰
반복자 패턴은 일련의 항목에 대하여 순서대로 어떤 작업을 수행할 수 있도록 해준다.
러스트에서 반복자는 항목들을 사용하기위해 반복자를 소비하는 method를 호출하기 전까지 아무런 동작을 하지 않는다.
예를 들어보자.
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
// v1_iter에는 Iter([1, 2, 3])가 들어있다.
위의 코드는 v1 변수에 vector를 할당한것이고, v1_iter변수에 v1의 반복자를 할당해둔 것이다. (v1_iter는 Iter([1, 2, 3])로 출력된다.)
iter를 이용하여 단지 할당만 했을 뿐 어떠한 일도 일어나지 않는다. 이러한 반복자를 사용하기 위해 다른 조치가 필요하다.
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for v in v1_iter {
println!("{}", v);
}
위와 같이 for문에서 반복자를 사용할 수 있다.
Iterator트레잇과 next 메서드
모든 반복자는 표준 라이브러리에 정의된 Iterator 라는 트레잇을 구현한다.
(trait의 정의)
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
type Item과 Self::Item은 이 trait과 연관 타입을 정의한다. (연관 타입은 추후 학습 예정)
이것은 Iterator트레잇을 구현하는 것은 Item타입을 정의하는 것을 요구하며, 이 Item타입이 next 메서드의 return type으로 나타낸다. 다른말로 Item타입은 반복자로부터 반환되는 타입이 될 것이다.
next method를 사용해보자.
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
println!("{:?}", v1_iter.next());
println!("{:?}", v1_iter);
// 순서대로 출력
// Some(1)
// Iter([2, 3])
(javascript의 generator function과 비슷하다. 역시 wasm에 적합하다.)
next 메서드를 사용하면 queue와 같이 첫번째 값 부터 빠져나가는 것을 확인할 수 있다.
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
println!("{:?}", v1_iter);
println!("{:?}", v1_iter.next());
println!("{:?}", v1_iter.next());
println!("{:?}", v1_iter.next());
println!("{:?}", v1_iter.next());
println!("{:?}", v1_iter);

위 코드를 실행한 결과이다.
next 메서드를 호출할 때 마다 앞에서부터 값이 빠져나가고, Iter의 모든 값이 빠져나갔을 경우 값이 없다는 의미의 None이 출력된다. 이때 iter를 확인해보면 비어있는것을 확인할 수 있다.
반복자를 소비하는 메서드들
Iterator 트레잇에는 표준 라이브러리에서 기본 구현을 제공하는 다수의 다른 메서드들이 있다.
https://doc.rust-lang.org/std/iter/trait.Iterator.html -> (iterator에 관한 rust docs)
next를 호출하는 메서드들을 어댑터 소비자(Adapter consumer) 라고 한다. 해당 메서드를 호출하면 반복자를 소비해버리기 때문에 이러한 네이밍이 붙었다.
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
println!("{}", total);
println!("{:?}", v1_iter);
반복자내 원소를 모두 더하는 sum()메서드를 호출하는 코드이다.
하지만 이 코드는 오류가 난다.
sum메서드는 호출한 반복자의 ownerShip을 가져가 버리고 v1_iter는 더이상 사용할 수 없게 된다.

다른 반복자를 생성하는 메서드들
Iterator트레잇에 정의된 다른 메서드들 중 반복자 어댑터(Iterator Adapter)들로 알려진 메서드들은 반복자를 다른 종류의 반복자로 변경하도록 허용한다.
복잡한 행위를 수행하기 위해 읽기 쉬운 방법으로 반복자 어댑터에 대한 여러 호출을 연결할 수 있다.
map
반복자 어댑터 메서드 중 하나인 map을 사용하는 예시이다.
map은 새로운 반복자를 생성하기 위해 각 항목에 대하여 호출할 closure(rust의 클로저 함수, js 클로저와 다름)를 인자로 받는다.
let v1 = vec![1, 2, 3];
let v2 = v1.iter().map(|x| x + 1);
println!("{:?}", v2);
let v3: Vec<i32> = v1.iter().map(|x| x + 1).collect();
println!("{:?}", v3);

각각 v2와 v3는 Map iter type과 Vector<i32>타입이 반환된 값을 가진다.
filter
filter 반복자 어댑터를 사용한 예시이다.
#[derive(Debug)]
struct Shoe {
size: u32,
style: String,
}
fn my_shoes(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter()
.filter(|s| s.size == shoe_size)
.collect()
}
fn vec_iterator() {
let shoes: Vec<Shoe> = vec![
Shoe { size: 240, style: String::from("슬리퍼") },
Shoe { size: 255, style: String::from("샌들") },
Shoe { size: 260, style: String::from("디올 B23 하이탑 스니커즈 화이트") },
Shoe { size: 235, style: String::from("단화") },
Shoe { size: 260, style: String::from("발렌시아가 트리플s 트레이너") },
];
let mine: Vec<Shoe> = my_shoes(shoes, 260);
println!("{:?}", mine);
}

shoes사이즈가 맞는것을 출력하는 코드이다.
shoes_in_my_size 함수는 parameter로 신발 종류와 사이즈를 가진 vector와 매칭해야 하는 shoe_size를 넘겨준다.
- vecotr의 소유권을 갖는 반복자를 생성하기위해 into_iter를 호출한다.
- 그 다음 그 반복자를 closure가 true를 반환한 요소들만 포함하는 새로운 반복자로 바꾸기 위하여 filter를 호출한다. (말 그대로 true를 반환하는 값을 filter함.)
Iterator 트레잇을 이용하여 custom iterator 만들기
vector에 대하여 iter, into_iter 또는 iter_mut를 호출하여 반복자를 생성할 수 있다.
vector뿐만아니라 hashmap과 같은 표준 라이브러리의 다른 collection 타입도 반복자를 생성할 수 있다.
Iterator트레잇을 구현함으로써 원하는 동작을 하는 반복를 생성하는것 또한 가능하다.
1. new로 정의할 수 있는 Counter 구조체를 생성하였고 초기값은 0이다.
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
2. Counter 구조체에 대한 Iterator트레잇을 구현한다.
연관 Item 타입을 u32로 지정한다.
next메서드를 호출하면 반복자가 현재 상태에 1을 더하도록 한다.
그리고 count가 6이 된 후 부터 None을 반환하도록 한다.
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
3. 실행해보자
let mut count = Counter::new();
if let Some(N) = count.next() {
println!("{}", N);
}
println!("{:?}", count.next());
println!("{:?}", count.next());
println!("{:?}", count.next());
println!("{:?}", count.next());
println!("{:?}", count.next());

count의 값이 5가 출력된 이후부터는 None이 출력된다.
※ 다른 Iterator 메서드
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
println!("{}", sum);
//결과 18
위에서 사용한 Counter 구조체를 그대로 사용한다.
zip, map, filter를 각각 실행시켜본 결과
// zip
let sum: u32 = Counter::new().zip(Counter::new().skip(1));
Zip {
a: Counter {
count: 0,
},
b: Skip {
iter: Counter {
count: 0,
},
n: 1,
},
}
// map
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a, b)| a * b);
Map {
iter: Zip {
a: Counter {
count: 0,
},
b: Skip {
iter: Counter {
count: 0,
},
n: 1,
},
},
}
// filter
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0);
Filter {
iter: Map {
iter: Zip {
a: Counter {
count: 0,
},
b: Skip {
iter: Counter {
count: 0,
},
n: 1,
},
},
},
}
https://rinthel.github.io/rust-lang-book-ko/ch13-02-iterators.html
반복자로 일련의 항목들 처리하기 - The Rust Programming Language
반복자 패턴은 일련의 항목들에 대해 순서대로 어떤 작업을 수행할 수 있도록 해줍 니다. 반복자는 각 항목들을 순회하고 언제 시퀀스가 종료될지 결정하는 로직을 담당 합니다. 반복자를 사용
rinthel.github.io
'Rust-Language' 카테고리의 다른 글
life time, 라이프 타임 (0) | 2021.09.12 |
---|---|
trait: 공유 동작 정의 (0) | 2021.09.08 |
Closure, 클로저 함수 (0) | 2021.08.05 |
스마트 포인터 (0) | 2021.05.16 |
collection - vector (0) | 2021.04.23 |