정의
- Iterator pattern(반복자 패턴) : 일련의 아이템들에 대해 순서대로 어떤 작업을 수행할 수 있도록 도와줌
- 컬렉션(벡터, 배열, 슬라이스의 요소를 하나씩 반환하는 개념
iter(), which iterates over &T.
iter_mut(), which iterates over &mut T.
into_iter(), which iterates over T.
itertor는 lazy하다.
fn main() {
let input = vec![1, 2, 3];
let parity = input
.iter()
.map(|x| {
print!("{}", x);
x % 2
});
for p in parity {
print!("{}", p);
}
}
- Rust에서 이터레이터(iterator) 는 lazy(지연 평가)
- 이터레이터를 생성해도 즉시 연산이 수행되지 않고, 값을 소비(consume)하는 메서드가 호출될 때만 동작 합니다.
112031
Iter()
iter()는 컬렉션을 불변 참조자로 순회할 수 있도록 하는 Iterator를 반환한다.
- 불변 참조를 반환 -> 원본 데이터 수정 불가능
: 메소드들도 결론적으로 원본 값을 바꾸지 못함
여기서, String과 같은 문자열의 각 문자를 순회할 수 있는 iterator를 만드는 chars는, 문자열의 개별 문자(char)을 만듦. 그래서 해당 값을 받을 때 &꼴이 아닌 그대로 받음.
collect()
fn collect<B>(self) -> B
where
B: FromIterator<Self::Item>,
Self: Sized,
- iterator의 모든 요소를 모아서
B타입의 컬렉션을 반환하는 메서드
B: FromIterator<Self::Item> 이라는 조건 때문에, B는 반드시 이터레이터에서 FromIterator를 구현한 타입
- iterator의 요소들을 모아서 새로운 컬렉션으로 변환
let v = vec![1, 2, 3];
let squared: Vec<i32> = v.iter().map(|x| x * x).collect();
println!("{:?}", squared);
유용한 Iterator method
Rust의 Iterator는 컬렉션을 순회하면서 데이터를 처리할 수 있도록 해주는 강력한 기능을 제공한다.
아래는 자주 사용하는 Iterator 메서드와 종류를 정리한 내용이다.
1. 기본 변환 (map, filter, flatten)
| 메서드 | 설명 |
|---|
map(f) | 각 요소를 함수 f를 적용하여 변환 |
filter(f) | 함수 f가 true를 반환하는 요소만 남김 |
filter_map(f) | Option<T>을 반환하는 f를 적용하고 Some 값만 남김 |
flatten() | 중첩된 Iterator를 평탄화 |
flat_map(f) | f를 적용하고 평탄화 |
let v = vec![1, 2, 3, 4];
let squared: Vec<_> = v.iter().map(|x| x * x).collect();
println!("{:?}", squared);
2. 소유권 변경 (cloned, copied)
| 메서드 | 설명 |
|---|
cloned() | &T를 T로 변환 (복사 가능 타입에서 사용 가능) |
copied() | Copy 가능한 &T를 T로 변환 |
let v = vec![1, 2, 3];
let owned: Vec<_> = v.iter().copied().collect();
println!("{:?}", owned);
3. 집계 및 연산 (sum, product, count)
| 메서드 | 설명 |
|---|
sum() | 모든 요소를 합산 |
product() | 모든 요소를 곱셈 |
count() | 요소 개수 반환 |
max() / min() | 최댓값 / 최솟값 반환 |
max_by(f) / min_by(f) | 비교 함수 f를 사용하여 최댓값 / 최솟값 반환 |
fold(init, f) | 초기값 init에서 시작하여 f를 적용하면서 축적 |
reduce(f) | 첫 번째 요소부터 시작하여 f를 적용하면서 축적 |
let v = vec![1, 2, 3, 4];
let sum: i32 = v.iter().sum();
println!("{}", sum);
let product: i32 = v.iter().product();
println!("{}", product);
4. 검색 및 확인 (any, all, find)
| 메서드 | 설명 |
|---|
any(f) | f를 만족하는 요소가 하나라도 있으면 true 반환 |
all(f) | 모든 요소가 f를 만족하면 true 반환 |
find(f) | f를 만족하는 첫 번째 요소를 반환 |
find_map(f) | Option<T>을 반환하는 f를 적용하고 Some을 반환하면 종료 |
position(f) | f를 만족하는 첫 번째 요소의 인덱스를 반환 |
let v = vec![1, 2, 3, 4];
let has_even = v.iter().any(|&x| x % 2 == 0);
println!("{}", has_even);
let all_positive = v.iter().all(|&x| x > 0);
println!("{}", all_positive);
5. 슬라이싱 및 제한 (take, skip, enumerate)
| 메서드 | 설명 |
|---|
next() | 다음 요소를 가져오고, 상태를 한 단계 앞으로 이동 |
take(n) | 처음 n개의 요소만 반환 |
skip(n) | 처음 n개의 요소를 건너뜀 |
step_by(n) | n 간격으로 요소를 선택 |
chain(other) | 두 개의 Iterator를 연결 |
enumerate() | (index, value) 쌍으로 변환 |
zip(other) | 두 개의 Iterator를 (a, b) 쌍으로 결합 |
let v = vec![1, 2, 3, 4, 5];
let first_three: Vec<_> = v.iter().take(3).collect();
println!("{:?}", first_three);
let skipped: Vec<_> = v.iter().skip(2).collect();
println!("{:?}", skipped);
비효율적으로 풀었지만, leetcode#13
impl Solution {
pub fn check_value(ss:(&char, &char), sum : i32) -> i32 {
match ss {
('I', 'V') |('I', 'X' ) => sum-1,
('I', _) => sum+1,
('V', _) => sum+5,
('X', 'L') | ('X', 'C') => sum-10,
('X', _) => sum+10,
('L', _) => sum+50,
('C', 'D') | ('C', 'M') => sum- 100,
('C', _) => sum+100,
('D', _) => sum+500,
('M', _) => sum+1000,
(_, _) => sum,
}
}
pub fn roman_to_int(s: String) -> i32 {
let mut sum = 0;
let mut last : char = ' ';
if s.len() != 1 {
for (ch1, ch2) in s.chars().zip(s.chars().skip(1)) {
sum = Solution::check_value((&ch1, &ch2), sum);
last = ch2
}
Solution::check_value((&last, &' '), sum)
}
else {
Solution::check_value((s.chars().next().unwrap_or(' '), ' '), sum)
}
}
}
사실은, 문제 자체에서, 어긋나는 케이스는 주지 않겠다고 이야기가 나왔던 상태여서, match자체를 위처럼 아주 이상하게 짤 필요는 없었지만, iterator method를 공부한 이유가 결국 이 문제를 풀다가 나왔으므로, 원래 풀이를 그냥 놓고 분석
- 의도: 두 요소씩 뽑아 내고 싶었음, match자체를 두 튜플을 매개변수로 받았기 때문
- 방법:
s.chars().skip(1)로 한칸 미뤄진 iterator를만든 후, 이를 zip을 이용하여 쌍으로 결합함. 그 후, 이를 하나씩 뽑아내면 인덱스 기준 (1,2), (2,3), (3,4)... 꼴의 튜플을 뽑아낼 수 있다고 생각. 이 때 char 그대로 반환하기 떄문에 참조자를 안붙여야함
for (ch1, ch2) in s.chars().zip(s.chars().skip(1))
Zip { a: Chars(['H', 'e', 'l', 'l', 'o']), b: Skip { iter: Chars(['H', 'e', 'l', 'l', 'o']), n: 1 } }
- 하지만 문제 발생 -> 문자열 길이가 1이면? 답이 없음, 그래서 따로 조건을 두고 분기하여 진행함(
s.len()=1).
Solution::check_value((s.chars().next().unwrap_or(' '), ' '), sum)
s.chars().next()의 출력값은 Option<T> 데이터형이므로, unwrap으로 원값 뽑아낼 수 있음
Solution::check_value((s.chars().next().unwrap_or(' '), ' '), sum)