【问题标题】:Rust generic linspace functionRust 通用 linspace 函数
【发布时间】:2022-11-03 02:20:29
【问题描述】:

我正在尝试实现一个通用函数 linspace:

pub fn linspace<T> (x0: T, xend: T, n: usize) -> Vec<T>
    where
        T: Sub<Output = T>
        + Add<Output = T>
        + Div<Output = T>
        + Clone
{

    let dx = (xend - x0) / ((n - 1) as T);

    let mut x = vec![x0; n];

    for i in 1..n {
        x[i] = x[i - 1] + dx;
    }

    x
}

到目前为止,我发现T 必须实现SubAddDivClone,但现在我遇到了n as T 语句的问题。

non-primitive cast: `usize` as `T`
let dx = (xend - x0) / ((n - 1) as T);
   |                   ^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object

我知道 num crate,但我试图在没有外部 crate 的情况下实现这一点。有解决方法吗? 谢谢!

【问题讨论】:

  • 要从usize 转换为T,请查看From 之类的标准转换特征(或更可能是TryFrom,因为从usize 转换为大多数其他整数类型是错误的)
  • 谢谢,@kmdreko 的回答。我在特征绑定中添加了From&lt;usize&gt;,并使用T::from(n - 1) 实现了转换(以避免错误处理,因为它需要TryFrom),但现在我收到错误:the trait 'From&lt;usize&gt;' is not implemented for 'f64'。有什么建议,请。

标签: generics rust


【解决方案1】:

除非您将此作为学习练习,否则我建议您需要 num_traits 板条箱的边界,它具有类似 Float 的特征,在这里会很有用:

use num_traits::Float;

pub fn linspace<T: Float + TryFrom<usize>>(x0: T, xend: T, n: usize) -> Vec<T> {
    let dx = (xend - x0) / (n - 1).try_into().unwrap_or_else(|_| panic!());
    let mut x = vec![x0; n];
    for i in 1..n {
        x[i] = x[i - 1] + dx;
    }
    x
}

然而现在我得到了错误:'f64'没有实现特征'From'。

它没有被实现,因为有usize 值不能精确地表示为f64。错误是让你决定如何处理这些。如果遇到这样的值,我的代码会出现恐慌。

另外,我相信浮点加法会累积错误,因此基于乘法的计算可能是一个更好的主意:

pub fn linspace<T: Float + TryFrom<usize>>(x0: T, xend: T, n: usize) -> Vec<T> {
    let to_float = |i: usize| i.try_into().unwrap_or_else(|_| panic!());
    let dx = (xend - x0) / to_float(n - 1);
    (0..n).map(|i| x0 + to_float(i) * dx).collect()
}

Playground

【讨论】:

    【解决方案2】:

    如果您想坚持标准库特征,则需要使用TryInto,并处理请求的转换可能失败的事实,因为数字类型可能小于输入。此外,不幸的是,对于不一定适合f32(或f64)的数字,甚至没有任何错误的TryInto&lt;f32&gt;。因此,我们必须使项目数u16 可以转换为任何浮点类型。

    use core::ops::Add;
    use core::ops::Div;
    use core::ops::Sub;
    use std::fmt::Debug;
    
    pub fn linspace<T>(x0: T, xend: T, n: u16) -> Vec<T>
    where
        T: Sub<Output = T> + Add<Output = T> + Div<Output = T> + Clone + Debug,
        u16: TryInto<T> + TryInto<usize>,
        <u16 as TryInto<T>>::Error: Debug,
    {
        let segments: T = (n - 1)
            .try_into()
            .expect("requested number of elements did not fit into T");
        let n_size: usize = n.try_into()
            .expect("requested number of elements exceeds usize");
            
        let dx = (xend - x0.clone()) / segments;
    
        let mut x = vec![x0; n_size];
    
        for i in 1..n_size {
            x[i] = x[i - 1].clone() + dx.clone();
        }
    
        x
    }
    

    如果给定一个太大的n,它会恐慌,例如如果Tu8n1000

    (顺便说一句,重复添加dx 通常不是最好的方法,因为如果T 是浮点类型,它会累积错误;最后一个元素不一定等于xend。)

    【讨论】:

    • 谢谢@Kevin,我已经在here 中尝试过你的实现,但它没有编译。我错过了什么? (顺便说一句,感谢旁注,它非常有用!)。
    • @SergioCavaleiroCosta 哎呀,我错过了没有TryInto&lt;f64&gt; for usize。我发布了一个固定但更有限的版本。也许num_traits 确实是一个更好的方法。
    【解决方案3】:

    这更整洁:

    use num_traits::Float;
    
    fn linspace<T: Float + std::convert::From<u16>>(l: T, h: T, n: usize) -> 
        Vec<T> {
        let size: T = (n as u16 - 1).try_into()
            .expect("requested number of elements exceeds usize");
        let dx = (h - l) / size;
    
        (1..=n).scan(-dx, |a, _| { *a = *a + dx; Some(*a) }).collect()
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-24
      • 2022-01-05
      • 1970-01-01
      • 2018-01-14
      • 2016-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多