Общая итерация по карте или вектору из двух кортежей

По причинам, я хочу определить универсальную функцию, которая может перебирать пары ключ-значение, выраженные либо как отображение, либо как вектор из 2-х кортежей (или чего-либо еще, что удовлетворяет IntoIterator<Item=(K, V)>, где K а также V стянуты). Конкретно, я хочу, чтобы это работало:

use std::collections::HashMap;

fn main() {
    let vc = vec![
        ("a", "foo"),
        ("b", "bar"),
        ("c", "baz")
    ];
    operate(&vc);

    let mut map = HashMap::new();
    map.insert("d", "blurf");
    map.insert("e", "quux");
    map.insert("f", "xyzzy");
    operate(&map);
}

У меня есть определение operate это работает для HashMap, но не для вектора:

fn operate<I, K, V>(x: I)
    where I: IntoIterator<Item=(K, V)>,
          K: AsRef<str>, V: AsRef<str>
{
    for (ref k, ref v) in x {
        println!("{}: {}", k.as_ref(), v.as_ref());
    }
}

Я получаю сообщение об ошибке:

error[E0271]: type mismatch resolving `<&std::vec::Vec<(&str, &str)> as std::iter::IntoIterator>::Item == (_, _)`
  --> test.rs:18:5
   |
18 |     operate(&vc);
   |     ^^^^^^^ expected reference, found tuple
   |
   = note: expected type `&(&str, &str)`
   = note:    found type `(_, _)`
   = note: required by `operate`

и я совсем не понимаю С одной стороны, кажется, что это задом наперед, а с другой, почему я получаю только ошибку для Vec а не HashMap?

2 ответа

Функция предоставлена IntoIterator потребляет себя.

fn into_iter(self) -> Self::IntoIter

Для того, чтобы разрешить использование IntoIterator без потребления коллекции, оба Vec а также HashMapесть реализации IntoIterator за &'a Vec<T> а также &'a HashMap<K,V,S> соответственно. Однако они не совсем одинаковы.

Для хэш-карты каждый Item это (&K, &V), что не создает проблемы, потому что код эффективно принимает элементы в виде двухразмерных кортежей ключей и значений, которые приводят к &str, А также &&str действительно принуждает к &str, Для вектора каждый Item это &T (таким образом, &(K, V) в этом случае), а потому что функция ожидает (K, V) как итерирующий элемент, он в настоящее время не может иметь дело с элементами &(K, V),

Как таковая, функция работает, если вы перемещаете вектор, что дает IntoIterator где Item = (K, V):

let vc = vec![
    ("a", "foo"),
    ("b", "bar"),
    ("c", "baz")
];
operate(vc);

Но что, если мы хотим, чтобы он работал для обеих коллекций, не потребляя ни одной из них? Ну, я только что разработал два решения.

# 1

Это включает в себя сокрытие кортежа за новой чертой:

/// for stuff that can be turned into a pair of references
trait AsRefPair<K, V> {
    fn as_ref_pair(&self) -> (&K, &V);
}

Реализация его для &(K,V) а также (&K,&V):

impl<'a, K, V> AsRefPair<K, V> for (&'a K, &'a V) {
    fn as_ref_pair(&self) -> (&K, &V) {
        (self.0, self.1)
    }
}

impl<'a, K, V> AsRefPair<K, V> for &'a (K, V) {
    fn as_ref_pair(&self) -> (&K, &V) {
        (&self.0, &self.1)
    }
}

И теперь эта функция работает:

fn operate<I, T, K, V>(x: I)
    where I: IntoIterator<Item=T>,
          T: AsRefPair<K, V>,
          K: AsRef<str>, V: AsRef<str>
{
    for p in x {
        let (ref k, ref v) = p.as_ref_pair();
        println!("{}: {}", k.as_ref(), v.as_ref());
    }
}

Детская площадка Поначалу это может показаться немного сумасшедшим, но...!

# 2

В этом просто перестаньте работать с кортежами... и начните работать со значениями ключей!

trait KeyValue<K, V> {
    fn key_value(&self) -> (&K, &V) {
        (self.key(), self.value())
    }

    fn key(&self) -> &K;
    fn value(&self) -> &V;
}

impl<K, V> KeyValue<K, V> for (K, V) {
    fn key(&self) -> &K {
        &self.0
    }
    fn value(&self) -> &V {
        &self.1
    }
}

impl<'a, K, V> KeyValue<K, V> for &'a (K, V) {
    fn key(&self) -> &K {
        &self.0
    }
    fn value(&self) -> &V {
        &self.1
    }
}

fn operate<I, T, K, V>(x: I)
    where I: IntoIterator<Item=T>,
          T: KeyValue<K, V>,
          K: AsRef<str>, V: AsRef<str>
{
    for p in x {
        let (ref k, ref v) = p.key_value();
        println!("{}: {}", k.as_ref(), v.as_ref());
    }
}

Детская площадка Я нахожу это немного более идиоматичным.

Если вы перейдете к функции operate() итератор вместо ссылки на вектор, вы можете использовать Iterator адаптеры для преобразования Iterator::Item к тому, что вам нужно:

operate(vc.iter().map(|&(ref a, ref b)| (a, b)));
Другие вопросы по тегам