티스토리 뷰

Rust-Language

반복자, iterator

kmj24 2021. 9. 3. 00:12

반복자 패턴은 일련의 항목에 대하여 순서대로 어떤 작업을 수행할 수 있도록 해준다.

러스트에서 반복자는 항목들을 사용하기위해 반복자를 소비하는 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
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
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 30 31
글 보관함