Как мне собрать в массив?
Я хочу позвонить .map()
на массиве перечислений:
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos = bar.iter().map(|x| Foo::Value(*x)).collect::<[Foo; 3]>();
}
но компилятор жалуется:
error[E0277]: the trait bound `[Foo; 3]: std::iter::FromIterator<Foo>` is not satisfied
--> src/main.rs:8:51
|
8 | let foos = bar.iter().map(|x| Foo::Value(*x)).collect::<[Foo; 3]>();
| ^^^^^^^ a collection of type `[Foo; 3]` cannot be built from an iterator over elements of type `Foo`
|
= help: the trait `std::iter::FromIterator<Foo>` is not implemented for `[Foo; 3]`
Как мне это сделать?
11 ответов
Проблема на самом деле в collect
, не в map
,
Чтобы иметь возможность собирать результаты итерации в контейнер, этот контейнер должен реализовывать FromIterator
,
[T; n]
не реализует FromIterator
потому что он не может сделать это вообще: произвести [T; n]
Вы должны предоставить n
элементы точно, однако при использовании FromIterator
Вы не даете никаких гарантий относительно количества элементов, которые будут введены в ваш тип.
Существует также трудность, которую вы не знали бы без дополнительных данных, какой индекс массива вы должны использовать сейчас (и является ли он пустым или полным) и т. Д.... это можно решить с помощью enumerate
после map
(по существу, подпитывает индекс), но тогда у вас все еще будет проблема с решением, что делать, если недостаточно или слишком много элементов предоставлено.
Поэтому не только сейчас невозможно реализовать FromIterator
на массиве фиксированного размера; но даже в будущем это похоже на длинный выстрел.
Итак, что теперь делать? Есть несколько возможностей:
- встроить преобразование на сайте вызова:
[Value(1), Value(2), Value(3)]
возможно с помощью макроса - собрать в другой (растущий) контейнер, такой как
Vec<Foo>
- ...
Начиная с rustc 1.42.0, это работает:
use std::convert::TryInto;
xxxx.iter()
.collect::<Vec<_>>()
.as_slice()
.try_into()
.unwrap()
collect as_slice try_into + unwrap()
Iterator<T> ------> Vec<T> -------> &[T] ------------------> [T]
Вам нужно включить std::convert::TryInto
поскольку try_into
определен в типаже TryInto.
Каждую ночь с
array_map
флаг функции, который вы можете сделать:
#![feature(array_map)]
fn main() {
let bar = [1, 2, 3];
let foos = bar.map(|x| Foo::Value(x));
}
Хотя вы не можете напрямую собирать данные в массив по причинам, указанным в других ответах, это не означает, что вы не можете собирать данные в структуру, поддерживаемую массивом, например ArrayVec
:
extern crate arrayvec;
use arrayvec::ArrayVec;
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos: ArrayVec<[_; 3]> = bar.iter().map(|x| Foo::Value(*x)).collect();
let the_array = foos.into_inner()
.unwrap_or_else(|_| panic!("Array was not completely filled"));
}
Вытащить массив из ArrayVec
возвращает Result
разбираться со случаем, когда не хватало предметов для его заполнения; случай, который обсуждался в других ответах.
into_inner
есть предостережение:
Примечание. Эта функция может потребовать непропорционально больших накладных расходов для перемещения массива, ее производительность не является оптимальной.
Из-за этого вы просто хотите оставить данные там, где они есть. Вы бы все-таки избежали выделения кучи.
В этом случае вы можете использовать Vec<Foo>
:
#[derive(Debug)]
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos = bar.iter().map(|&x| Foo::Value(x)).collect::<Vec<Foo>>();
println!("{:?}", foos);
}
.collect()
создает структуры данных, которые могут иметь произвольную длину, потому что номер элемента итератора в целом не ограничен. (Ответ Шепмастера уже дает много деталей там).
Одна возможность получить данные в массив из сопоставленной цепочки без выделения Vec
или аналогично, чтобы вносить изменяемые ссылки на массив в цепочку. В вашем примере это будет выглядеть так:
#[derive(Debug, Clone, Copy)]
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let mut foos = [Foo::Nothing; 3];
bar.iter().map(|x| Foo::Value(*x))
.zip(foos.iter_mut()).for_each(|(b, df)| *df = b);
}
.zip()
заставляет итерацию проходить через оба bar
а также foos
в шаге - если foos
были недостаточно выделены, тем выше bar
s не будет отображаться вообще, и если он будет перераспределен, он сохранит свои первоначальные значения инициализации. (Таким образом, также клон и копия, они необходимы для [Nothing; 3]
инициализация).
Фактически вы можете определить Iterator
расширение trait для этого!
use std::convert::AsMut;
use std::default::Default;
trait CastExt<T, U: Default + AsMut<[T]>>: Sized + Iterator<Item = T> {
fn cast(mut self) -> U {
let mut out: U = U::default();
let arr: &mut [T] = out.as_mut();
for i in 0..arr.len() {
match self.next() {
None => panic!("Array was not filled"),
Some(v) => arr[i] = v,
}
}
assert!(self.next().is_none(), "Array was overfilled");
out
}
}
impl<T, U: Iterator<Item = T>, V: Default + AsMut<[T]>> CastExt<T, V> for U { }
fn main () {
let a: [i32; 8] = (0..8).map(|i| i * 2).cast();
println!("{:?}", a); // -> [0, 2, 4, 6, 8, 10, 12, 14]
}
Вот ссылка на игровую площадку.
Вы можете комбинировать массивыmap
метод сIterator::next
.
Пример:
fn iter_to_array<Element, const N: usize>(mut iter: impl Iterator<Item = Element>) -> [Element; N] {
// Here I use `()` to make array zero-sized -> no real use in runtime.
// `map` creates new array, which we fill by values of iterator.
let res = [(); N].map(|_| iter.next().unwrap());
// Ensure that iterator finished
assert!(matches!(iter.next(), None));
res
}
Это невозможно, потому что массивы не реализуют никаких признаков. Вы можете собирать только в типы, которые реализуют FromIterator
черта (см. список внизу документации).
Это ограничение языка, поскольку в настоящее время невозможно быть универсальным по длине массива, а длина является частью его типа. Но, даже если бы это было возможно, очень маловероятно, что FromIterator
будет реализован на массивах, потому что он должен был бы паниковать, если бы количество полученных элементов не было точно длиной массива.
Я жестко закодировал свои факториалы, так как они довольно быстро увеличиваются:
const N: usize = 7;
const FACTORIAL: usize = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800][N];
type Perm = [u8; N];
type Perms = [Perm;FACTORIAL];
Я столкнулся с этой проблемой сам - вот обходной путь.
Вы не можете использовать FromIterator
, но вы можете перебирать содержимое объекта фиксированного размера или, если все сложнее, индексы, которые разбивают все, к чему можно получить доступ. В любом случае, мутация жизнеспособна.
Например, у меня была проблема с массивом типа [[usize; 2]; 4]
:
fn main() {
// Some input that could come from another function and thus not be mutable
let pairs: [[usize; 2]; 4] = [[0, 0], [0, 1], [1, 1], [1, 0]];
// Copy mutable
let mut foo_pairs = pairs.clone();
for pair in foo_pairs.iter_mut() {
// Do some operation or other on the fixed-size contents of each
pair[0] += 1;
pair[1] -= 1;
}
// Go forth and foo the foo_pairs
}
Если это происходит внутри небольшой функции, это нормально в моей книге. В любом случае, вы должны были получить преобразованное значение идентичного типа с тем же типом, поэтому сначала копирование всего объекта, а затем его изменение - это примерно столько же усилий, сколько обращение к значению в замыкании и возврат некоторой его функции.,
Обратите внимание, что это работает только в том случае, если вы планируете вычислить что-то, что будет одного и того же типа, вплоть до размера / длины. Но это подразумевается вашим использованием массивов Rust. (В частности, вы могли бы Value()
ваш Foo
с или Nothing
они, как вам нравится, и все еще будут в пределах параметров типа для вашего массива.)