Синтаксис Rust-дженериков для средней функции - PullRequest
0 голосов
/ 13 января 2019

Я пытаюсь написать функцию, которая берет кусочек чисел и вычисляет среднее значение.

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

Мой код:

extern crate num;

use num::{FromPrimitive, Zero};
use std::ops::{Add, Div};

fn main() {
    let mut numbers = [10, -21, 15, 20, 18, 14, 18];
    let err = "Slice is empty.";

    println!("Mean is {:.3}", mean(&numbers).expect(err));
}

fn mean<T>(numbers: &[T]) -> Option<f64>
where
    T: Copy + Zero + Add<T, Output = T> + Div<T, Output = T> + FromPrimitive,
{
    match numbers.len() {
        0 => None,
        _ => {
            let sum = numbers.iter().sum: ();
            let length = FromPrimitive::from_usize(numbers.len()).unwrap();
            Some(sum / length)
        }
    }
}

Ошибка:

error[E0658]: type ascription is experimental (see issue #23416)
  --> src/main.rs:20:23
   |
20 |             let sum = numbers.iter().sum: ();
   |                       ^^^^^^^^^^^^^^^^^^^^^^

Есть ли способ написания обобщенной средней функции без использования экспериментальных функций?

Ответы [ 4 ]

0 голосов
/ 15 января 2019

Как насчет этого:

use std::iter::Sum;

fn main() {
    let err = "Slice is empty.";

    // Test vector of integers
    let numbers = vec![10i32, -21, 15, 20, 18, 14, 18];
    println!("Mean is {:.3}", mean(numbers.into_iter()).expect(err));

    // Test vector of floating point numbers
    let numbers = vec![10f64, -21f64, 15f64, 20f64, 18f64, 14f64, 18f64];
    println!("Mean is {:.3}", mean(numbers.into_iter()).expect(err));

    // Test empty vector
    let numbers: Vec<i32> = Vec::new();    
    println!("Mean is {:.3}", mean(numbers.into_iter()).expect(err));
}

fn mean<T, I: Iterator<Item = T>>(iter: I) -> Option<f64>
where
    T: Into<f64> + Sum<T>,
{
    let mut len = 0;
    let sum = iter
        .map(|t| {
            len += 1;
            t
        })
        .sum::<T>();

    match len {
        0 => None,
        _ => Some(sum.into() / len as f64)
    }
}

Тот же код на площадке Rust

Похоже, что имеет следующие преимущества перед ответами, опубликованными до сих пор:

  1. Гораздо проще определение родового типа.
  2. Не зависит от внешнего num ящика.
  3. Нет необходимости в трудно угадываемых чертах, таких как FromPrimitive и Zero.
  4. Нет ручных деклараций времени жизни.

Или эта версия, которая имеет следующие отличия от приведенной выше:

  1. Может принимать массивы, а не векторы.
  2. Не использует массив (или вектор).
  3. Требуется ручное объявление срока службы.
use std::iter::Sum;

fn main() {
    let err = "Slice is empty.";

    // Test aray of integers
    let numbers = [10, -21, 15, 20, 18, 14, 18];
    println!("Mean is {:.3}", mean(numbers.iter()).expect(err));

    // Test array of floating point numbers
    let numbers = [10f64, -21f64, 15f64, 20f64, 18f64, 14f64, 18f64];
    println!("Mean is {:.3}", mean(numbers.iter()).expect(err));

    // Test empty array
    let numbers: [i32; 0] = [];
    match mean(numbers.iter()) {
        Some(mean_) => println!("Mean is {:.3}", mean_),
        None => println!("Empty array"),
    }
}

fn mean<'a, T, I>(iter: I) -> Option<f64>
where
    T: Into<f64> + Sum<&'a T> + 'a,
    I: Iterator<Item = &'a T>,
{
    let mut len = 0;
    let sum = iter
        .map(|t| {
            len += 1;
            t
        })
        .sum::<T>();

    match len {
        0 => None,
        _ => Some(sum.into() / len as f64),
    }
}

Спасибо моему другу Свену за вклад кода.

0 голосов
/ 13 января 2019

Вашему типу также необходимо реализовать Sum<T>, поэтому вам также необходимо добавить границу Sum. Этого недостаточно, вам также необходимо преобразовать ваш общий тип в f64. Num crate также имеет черту ToPrimitive для этого:

fn mean<'a, T: 'a>(numbers: &'a [T]) -> Option<f64>
where
    T: ToPrimitive + Sum<&'a T>,
{
    match numbers.len() {
        0 => None,
        _ => {
            let sum = numbers.iter().sum::<T>();
            FromPrimitive::from_usize(numbers.len())
                .and_then(|length: f64| T::to_f64(&sum).and_then(|val| Some(val / length)))
        }
    }
}

Детская площадка

0 голосов
/ 13 января 2019

Другие ответы, вероятно, помогут вам в вашей реальной проблеме написания этой функции в общем виде.


Фактическая ошибка , о которой вы спрашивали, является лишь синтаксической ошибкой. Вы написали это:

let sum = numbers.iter().sum: ();

Но почти наверняка намеревался написать:

let sum = numbers.iter().sum();

Компилятор увидел :, который вы случайно включили, и думает, что вы пытаетесь использовать атрибуцию типа. Тип ascription - это синтаксис, позволяющий использовать аннотации типов, встроенные в выражение, а не только в объявлениях переменных.

То, что вы написали, очень похоже на:

let sum: () = numbers.iter().sum;

Если бы вы включили определение типа в ночной сборке rustc, ошибка изменилась бы, потому что теперь компилятор скажет вам, что sum является функцией и определенно не имеет тип ().

0 голосов
/ 13 января 2019
  • Если компилятор не может определить тип S из fn sum<S>(self) -> S, вам нужно либо написать let foo: Bar = baz.sum();, либо let foo = baz.sum::<Bar>();

  • Если вы уверены, что T всегда будет неким типом числового примитива, вы должны собрать принадлежащий тип из sum() с let sum: T = numbers.iter().cloned().sum(); и добавить core::iter::Sum, связанный с T. В противном случае вы можете работать со ссылками.

  • Вы можете сделать свою функцию немного более универсальной, возвращая Option<T>, но если вы действительно хотите вернуть Option<f64>, вы должны привести T к f64, используя черту ToPrimitive. Как это .

...