Как я могу перебрать индексы универсальной коллекции, которая реализует Index и IntoIterator? - PullRequest
1 голос
/ 06 октября 2019

Я хочу реализовать небольшую черту ориентированного графа для любой общей коллекции C, которая реализует std::ops::Index и std::iter::IntoIterator. Я хочу, чтобы коллекция представляла узлы графа. Каждый узел представлен своим индексом в C, который может быть индексом usize в ключе Vec или String в HashMap. Я не знаю, является ли это лучшим подходом к библиотеке графов, но я также хочу узнать о Rust, общих чертах и ​​стандартной библиотеке Rust.

В некоторых моментах моей реализации мне нужно выполнить итерациюпо всем показателям C экземпляра. Единственный метод, который я нашел для этого, - это функция enumerate, но в ней реализован только счетчик usize для итератора, а не мой универсальный тип, поэтому он будет работать для Vec, но не для HashMap.

Вот как выглядит реализация с использованием enumerate. Требуются функции nodes и children, children возвращает информацию о смежности графа. Чтобы получить все предшественники, использующие функцию parents, мне нужно перебрать индексы универсального типа контейнера.

pub trait SimpleGraph {
    /// type used as an index to refer to nodes.
    type I: Eq + std::hash::Hash;

    /// container type for the nodes
    type C: std::ops::Index<Self::I> + std::iter::IntoIterator<Item = Self::I>;

    /// returns a reference to the node container.
    fn nodes(&self) -> &Self::C;

    /// gets the indices of the children of a node with index `index`.
    fn children(&self, index: Self::I) -> Vec<Self::I>;

    /// gets all ancestors of a node (not very efficient...)
    fn parents(&self, i: Self::I) -> Vec<Self::I> {
        let mut res = Vec::<Self::I>::new();
        let nodes = self.nodes();
        for (idx, _) in nodes.into_iter().enumerate() {
            let children = self.children(idx);
            for child_idx in children {
                if child_idx == i {
                    res.push(idx);
                }
            }
        }
        return res;
    }
}

Это дает мне не очень удивительную ошибку компилятора:

error[E0308]: mismatched types
  --> src/lib.rs:19:42
   |
19 |             let children = self.children(idx);
   |                                          ^^^ expected associated type, found usize
   |
   = note: expected type `<Self as SimpleGraph>::I`
              found type `usize`

Одним из безобразных решений было бы добавить еще один обязательный метод indices, который возвращает список индексов и затем выполнять итерацию по этому списку, но это не очень удобно для пользователя и кажется ненужным шагом длялюбой из std::collections, который реализует std::ops::Index и std::iter::IntoIterator. Я бы лучше переписал функцию enumerate в общем виде.

Как бы я написал это простым и понятным образом?

1 Ответ

0 голосов
/ 08 октября 2019

Я нашел способ сделать это:

Объяснение

Я понял, что хотел бы, чтобы Index предоставил метод enumerate для перечисления индексов и элементов, стоящих за индексом коллекции. Таким образом, с помощью принятого ответа Использование универсальных итераторов вместо определенных типов списков Я реализовал супертрейт Index, который обеспечивает этот метод.

Шаг 1: Определите новый Enumerateструктурировать и реализовать Iterator для него

pub struct Enumerate<IndexIter, ItemIter> {
    index: IndexIter,
    item: ItemIter,
}

/// implements the [`Iterator`] trait for the new struct
impl<IndexIter, ItemIter> Iterator for Enumerate<IndexIter, ItemIter>
where
    IndexIter: Iterator,
    ItemIter: Iterator,
{
    type Item = (IndexIter::Item, ItemIter::Item);

    /// returns the next iterator
    #[inline]
    fn next(&mut self) -> Option<(IndexIter::Item, ItemIter::Item)> {
        self.index.next().map(|idx| {
            // CAUTION! We need to make sure that the index and item iterators are ordered consistently.
            // We are really just incrementing two iterators simultaneously here...
            (idx, self.item.next().unwrap())
        })
    }
}

Шаг 2: определить супертрейт для Index, который добавляет метод enumerate

/// trait for implementing over the indices of collections that implement [`std::ops::Index`].
/// 
/// It adds the enumerate function that returns an `Enumerate<IndexIter,ItemIter>` as an iterator.
pub trait SuperIndex<'a, Idx>: std::ops::Index<Idx> {
    type IndexIter: Iterator<Item = Idx>;
    type ItemIter: Iterator;

    /// enumerates over the indices and items of a collection
    fn enumerate(&'a self) -> Enumerate<Self::IndexIter, Self::ItemIter>;
}

Шаг 3: Внедрить супертрейт дляколлекции, которые я хочу использовать

Реализация для Vec

/// implement the [`SuperIndex`] trait for [`Vec<T>`]
impl<'a, T: 'a> SuperIndex<'a, usize> for Vec<T> {
    type IndexIter = std::ops::Range<usize>;
    type ItemIter = std::slice::Iter<'a, T>;

    fn enumerate(&'a self) -> Enumerate<Self::IndexIter, Self::ItemIter> {
        Enumerate {
            index: 0..self.len(),
            item: self.iter(),
        }
    }
}

Реализация для HashMap

/// implement the [`SuperIndex`] trait for [`HashMap<K, V, S>`]
impl<'a, K: 'a, V: 'a, S> SuperIndex<'a, &'a K> for std::collections::HashMap<K, V, S>
where
    K: Eq + std::hash::Hash,
    S: std::hash::BuildHasher,
{
    type IndexIter = std::collections::hash_map::Keys<'a, K, V>;
    type ItemIter = std::collections::hash_map::Values<'a, K, V>;

    fn enumerate(&'a self) -> Enumerate<Self::IndexIter, Self::ItemIter> {
        Enumerate {
            index: self.keys(),
            item: self.values(),
        }
    }
}

Обсуждение

Теперь я могуперечислять индекс и значение для любого вида коллекции, которая реализует SuperIndex и index, не обязательно должно быть usize:

for (index, item) in c.enumerate() {
    assert_eq!(&c[index], item);
}

Эта реализация делает то, что я хочу, и я не могу думатьиз любых альтернатив, но у него есть несколько незначительных ошибок: индексы

  • SuperIndex не могут быть такими же общими, как индексы для Index, например, срезы не допускаются.
  • Нам нужно явно реализовать SuperIndex для каждой коллекции.
  • InВ каждой реализации мы должны убедиться, что два итератора упорядочены последовательно.

Если что-то не так в моей реализации, пожалуйста, дайте мне знать! Кажется, работает нормально, но я понимаю только половину того, что я делаю.

...