Rust std::iter::zip의 내부 가변성에 대한 설명
std::iter::zip의 작동 방식
std::iter::zip은 여러 반복자를 받아 각 반복자에서 하나씩 요소를 추출하여 새로운 반복자를 만듭니다. 예를 들어, 다음 코드는 두 개의 벡터를 결합하여 새로운 벡터를 만듭니다.
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let zipped = numbers.iter().zip(letters.iter());
for (number, letter) in zipped {
println!("{} {}", number, letter);
}
이 코드는 다음과 같은 결과를 출력합니다.
1 a
2 b
3 c
std::iter::zip은 내부적으로 Iterator struct를 사용하여 구현됩니다. 이 struct는 다음과 같은 필드를 가지고 있습니다.
iterators
: 여러 반복자를 저장하는 벡터current
: 현재 위치를 저장하는 변수
내부 가변성
std::iter::zip의 내부 가변성은 current
변수에서 발생합니다. next()
메서드가 호출될 때마다 current
변수는 다음 위치로 이동합니다. 이것은 다음과 같은 문제를 야기할 수 있습니다.
- 반복자의 길이가 다를 경우: 한 반복자가 끝나더라도 다른 반복자는 계속해서 다음 요소를 추출할 수 있습니다. 이 경우
next()
메서드는None
값을 반환합니다. - 값을 변경할 경우:
zip
반복자를 통해 요소를 변경하면 원래 반복자의 값도 변경됩니다.
다음 코드는 이러한 문제를 보여줍니다.
let mut numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let zipped = numbers.iter_mut().zip(letters.iter());
for (number, letter) in zipped {
*number = 4;
println!("{} {}", number, letter);
}
println!("Numbers: {:?}", numbers);
4 a
4 b
4 c
Numbers: [4, 4, 4]
위 코드에서 numbers
벡터의 값이 변경된 것을 확인할 수 있습니다.
내부 가변성을 피하는 방법
std::iter::zip의 내부 가변성을 피하려면 다음과 같은 방법을 사용할 수 있습니다.
collect()
메서드 사용:collect()
메서드를 사용하여 zip 반복자를 벡터로 변환하면 내부 가변성을 피할 수 있습니다.
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let zipped: Vec<(i32, char)> = numbers.iter().zip(letters.iter()).collect();
for (number, letter) in zipped {
println!("{} {}", number, letter);
}
for
루프 사용:for
루프를 직접 사용하여 여러 반복자를 순회하면 내부 가변성을 제어할 수 있습니다.
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let mut iter_numbers = numbers.iter();
let mut iter_letters = letters.iter();
while let (Some(number), Some(letter)) = (iter_numbers.next(), iter_letters.next()) {
println!("{} {}", number, letter);
}
결론
예제 코드
예제 1: 반복자 길이가 다를 경우
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b'];
let zipped = numbers.iter().zip(letters.iter());
for (number, letter) in zipped {
println!("{} {}", number, letter);
}
// 'c'는 출력되지 않습니다.
println!("{}", letters[2]);
}
1 a
2 b
예제 2: 값을 변경할 경우
fn main() {
let mut numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let zipped = numbers.iter_mut().zip(letters.iter());
for (number, letter) in zipped {
*number = 4;
println!("{} {}", number, letter);
}
println!("Numbers: {:?}", numbers);
}
4 a
4 b
4 c
Numbers: [4, 4, 4]
예제 3: 내부 가변성을 피하는 방법
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
// collect() 메서드 사용
let zipped: Vec<(i32, char)> = numbers.iter().zip(letters.iter()).collect();
for (number, letter) in zipped {
println!("{} {}", number, letter);
}
// for 루프 사용
let mut iter_numbers = numbers.iter();
let mut iter_letters = letters.iter();
while let (Some(number), Some(letter)) = (iter_numbers.next(), iter_letters.next()) {
println!("{} {}", number, letter);
}
}
1 a
2 b
3 c
1 a
2 b
3 c
std::iter::zip 대체 방법
for 루프 사용
가장 간단한 방법은 for
루프를 직접 사용하여 여러 반복자를 순회하는 것입니다. 이 방법은 내부 가변성을 제어할 수 있지만 코드가 다소 길어질 수 있습니다.
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let mut iter_numbers = numbers.iter();
let mut iter_letters = letters.iter();
while let (Some(number), Some(letter)) = (iter_numbers.next(), iter_letters.next()) {
println!("{} {}", number, letter);
}
}
zip_longest() 함수 사용
zip_longest()
함수는 길이가 다른 반복자를 처리하는데 유용합니다. 이 함수는 짧은 반복자의 끝에 None
값을 채워서 모든 반복자가 동일한 길이가 되도록 합니다.
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b'];
let zipped = numbers.iter().zip_longest(letters.iter());
for (number, letter) in zipped {
match (number, letter) {
(Some(n), Some(l)) => println!("{} {}", n, l),
(Some(n), None) => println!("{} -", n),
(None, Some(l)) => println!("- {}", l),
}
}
}
itertools crate 사용
itertools
crate는 여러 유용한 반복자 함수를 제공합니다. 이 crate에서 제공하는 zip_eq()
함수는 길이가 같은 반복자를 결합하고 zip_longest_eq()
함수는 길이가 다른 반복자를 처리합니다.
use itertools::Itertools;
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let zipped = numbers.iter().zip_eq(letters.iter());
for (number, letter) in zipped {
println!("{} {}", number, letter);
}
let zipped = numbers.iter().zip_longest_eq(letters.iter());
for (number, letter) in zipped {
match (number, letter) {
(Some(n), Some(l)) => println!("{} {}", n, l),
(Some(n), None) => println!("{} -", n),
(None, Some(l)) => println!("- {}", l),
}
}
}
직접 구현하기
필요에 따라 직접 zip 기능을 구현할 수도 있습니다. 이 방법은 더 많은 제어권을 제공하지만, 코드 작성에 더 많은 노력이 필요합니다.
fn zip<T, U>(iter1: impl Iterator<Item = T>, iter2: impl Iterator<Item = U>) -> impl Iterator<Item = (T, U)> {
struct Zip<T, U> {
iter1: T,
iter2: U,
}
impl<T, U> Iterator for Zip<T, U>
where
T: Iterator,
U: Iterator,
{
type Item = (T::Item, U::Item);
fn next(&mut self) -> Option<Self::Item> {
self.iter1.next().and_then(|t| self.iter2.next().map(|u| (t, u)))
}
}
Zip { iter1, iter2 }
}
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let zipped = zip(numbers.iter(), letters.iter());
for (number, letter) in zipped {
println!("{} {}", number, letter);
}
}
결
rust