对 F# 中 List.mapi 的行为感到困惑

发布于 2024-09-03 06:55:04 字数 1143 浏览 3 评论 0原文

我正在 F# 中构建一些方程,在处理我的多项式类时,我使用 List.mapi 发现了一些奇怪的行为

基本上,每个多项式都有一个数组,因此 3*x^2 + 5*x + 6 将是数组中的 [|6, 5, 3|],因此,在添加多项式时,如果一个数组比另一个数组长,那么我只需将额外的元素附加到结果中,这就是我遇到问题的地方。

稍后我想将其概括为不总是使用 float,但这将是在我进行更多工作之后。

所以,问题是我期望 List.mapi 返回一个 List 而不是单个元素,但是,为了将列表放在一起,我必须将 [ ] 围绕我对 mapi 的使用,我很好奇为什么会出现这种情况。

这比我预期的更复杂,我认为我应该能够告诉它从某个索引开始创建一个新的 List ,但我找不到任何函数。

type Polynomial() =
    let mutable coefficients:float [] = Array.empty
    member self.Coefficients with get() = coefficients
    static member (+) (v1:Polynomial, v2:Polynomial) =
        let ret = List.map2(fun c p -> c + p) (List.ofArray v1.Coefficients) (List.ofArray v2.Coefficients)
        let a = List.mapi(fun i x -> x)
        match v1.Coefficients.Length - v2.Coefficients.Length with
            | x when x < 0 ->
                ret :: [((List.ofArray v1.Coefficients) |> a)]
            | x when x > 0 ->
                ret :: [((List.ofArray v2.Coefficients) |> a)]
            | _ -> [ret]

I am building some equations in F#, and when working on my polynomial class I found some odd behavior using List.mapi

Basically, each polynomial has an array, so 3*x^2 + 5*x + 6 would be [|6, 5, 3|] in the array, so, when adding polynomials, if one array is longer than the other, then I just need to append the extra elements to the result, and that is where I ran into a problem.

Later I want to generalize it to not always use a float, but that will be after I get more working.

So, the problem is that I expected List.mapi to return a List not individual elements, but, in order to put the lists together I had to put [] around my use of mapi, and I am curious why that is the case.

This is more complicated than I expected, I thought I should be able to just tell it to make a new List starting at a certain index, but I can't find any function for that.

type Polynomial() =
    let mutable coefficients:float [] = Array.empty
    member self.Coefficients with get() = coefficients
    static member (+) (v1:Polynomial, v2:Polynomial) =
        let ret = List.map2(fun c p -> c + p) (List.ofArray v1.Coefficients) (List.ofArray v2.Coefficients)
        let a = List.mapi(fun i x -> x)
        match v1.Coefficients.Length - v2.Coefficients.Length with
            | x when x < 0 ->
                ret :: [((List.ofArray v1.Coefficients) |> a)]
            | x when x > 0 ->
                ret :: [((List.ofArray v2.Coefficients) |> a)]
            | _ -> [ret]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

晒暮凉 2024-09-10 06:55:04

我认为在这种情况下,使用列表和递归的直接实现会更简单。 Polynomial 类的替代实现可能大致如下所示:

// The type is immutable and takes initial list as constructor argument
type Polynomial(coeffs:float list) = 
  // Local recursive function implementing the addition using lists
  let rec add l1 l2 = 
    match l1, l2 with
    | x::xs, y::ys -> (x+y) :: (add xs ys)
    | rest, [] | [], rest -> rest

  member self.Coefficients = coeffs

  static member (+) (v1:Polynomial, v2:Polynomial) = 
    // Add lists using local function
    let newList = add v1.Coefficients v2.Coefficients
    // Wrap result into new polynomial
    Polynomial(newList)

值得注意的是,您实际上并不需要类中的可变字段,因为 + 运算符创建并返回该类型的新实例,因此该类型是完全不可变的(正如您在 F# 中通常希望的那样)。

add 函数的好处是,在处理完两个列表中可用的所有元素后,您可以简单地将非空列表的尾部作为其余元素返回。


如果您想使用数组实现相同的功能,那么最好使用简单的 for 循环(因为数组原则上是命令式的,通常的命令式模式通常是处理问题的最佳选择)与他们)。但是,我不认为有任何特别的理由选择数组(也许是性能,但这必须在开发过程中稍后进行评估)。

正如 Pavel 指出的,:: 运算符将单个元素附加到列表的前面(请参阅上面的 add 函数,它演示了这一点)。您可以使用连接列表的 @ 或使用 Array.concat (连接数组序列)来编写您想要的内容。

使用高阶函数和数组的实现也是可能的 - 我能想到的最佳版本如下所示:

let add (a1:_[]) (a2:_[]) =
  // Add parts where both arrays have elements
  let l = min a1.Length a2.Length
  let both = Array.map2 (+) a1.[0 .. l-1] a2.[0 .. l-1]  
  // Take the rest of the longer array
  let rest = 
    if a1.Length > a2.Length 
    then a1.[l .. a1.Length - 1] 
    else a2.[l .. a2.Length - 1]
  // Concatenate them
  Array.concat [ both; rest ]

add [| 6; 5; 3 |] [| 7 |]  

这使用切片(例如a.[0 .. l]< /code>) 为您提供数组的一部分 - 您可以使用它们来获取两个数组都有元素的部分以及较长数组的剩余部分。

I think that a straightforward implementation using lists and recursion would be simpler in this case. An alternative implementation of the Polynomial class might look roughly like this:

// The type is immutable and takes initial list as constructor argument
type Polynomial(coeffs:float list) = 
  // Local recursive function implementing the addition using lists
  let rec add l1 l2 = 
    match l1, l2 with
    | x::xs, y::ys -> (x+y) :: (add xs ys)
    | rest, [] | [], rest -> rest

  member self.Coefficients = coeffs

  static member (+) (v1:Polynomial, v2:Polynomial) = 
    // Add lists using local function
    let newList = add v1.Coefficients v2.Coefficients
    // Wrap result into new polynomial
    Polynomial(newList)

It is worth noting that you don't really need mutable field in the class, since the + operator creates and returns a new instance of the type, so the type is fully immutable (as you'd usually want in F#).

The nice thing in the add function is that after processing all elements that are available in both lists, you can simply return the tail of the non-empty list as the rest.


If you wanted to implement the same functionality using arrays, then it may be better to use a simple for loop (since arrays are, in principle, imperative, the usual imperative patterns are usually the best option for dealing with them). However, I don't think there is any particular reason for preferring arrays (maybe performance, but that would have to be evaluated later during the development).

As Pavel points out, :: operator appends a single element to the front of a list (see the add function above, which demonstrates that). You could write what you wanted using @ which concatenates lists, or using Array.concat (which concatenates a sequence of arrays).

An implementation using higher-order functions and arrays is also possible - the best version I can come up with would look like this:

let add (a1:_[]) (a2:_[]) =
  // Add parts where both arrays have elements
  let l = min a1.Length a2.Length
  let both = Array.map2 (+) a1.[0 .. l-1] a2.[0 .. l-1]  
  // Take the rest of the longer array
  let rest = 
    if a1.Length > a2.Length 
    then a1.[l .. a1.Length - 1] 
    else a2.[l .. a2.Length - 1]
  // Concatenate them
  Array.concat [ both; rest ]

add [| 6; 5; 3 |] [| 7 |]  

This uses slices (e.g. a.[0 .. l]) which give you a part of an array - you can use these to take the parts where both arrays have elements and the remaining part of the longer array.

风苍溪 2024-09-10 06:55:04

我认为您误解了运算符 :: 的作用。它不用于连接两个列表。它用于将单个元素添加到列表中。因此,它的类型是:

'a -> 'a list -> 'a list

在您的情况下,您将 ret 作为第一个参数,而 ret 本身就是一个浮点列表。因此,它期望第二个参数的类型为 float list list - 这就是为什么你需要向第二个参数添加额外的 [] 来使其编译 - 并且这也将是您的运算符 + 的结果类型,这可能不是您想要的。

您可以使用List.concat连接两个(或更多)列表,但这效率很低。在您的示例中,我根本看不到使用列表的意义 - 所有这些转换回来 &未来将是昂贵的。对于数组,可以使用Array.append,效果更好。

顺便说一句,您根本不清楚代码中 mapi 的用途。它与 map 完全相同,除了索引参数之外,但您没有使用它,并且您的映射是恒等函数,因此它实际上是一个无操作。是关于什么的?

I think you're misunderstanding what operator :: does. It's not used to concatenate two lists. It's used to prepend a single element to the list. Consequently, it's type is:

'a -> 'a list -> 'a list

In your case, you're giving ret as a first argument, and ret is itself a float list. Consequently, it expects the second argument to be of type float list list - hence why you need to add an extra [] to the second argument to make it to compile - and that will also be the result type of your operator +, which is probably not what you want.

You can use List.concat to concatenate two (or more) lists, but that is inefficient. In your example, I don't see the point of using lists at all - all this converting back & forth is going to be costly. For arrays, you can use Array.append, which is better.

By the way, it's not clear what is the purpose of mapi in your code at all. It's exactly the same as map, except for the index argument, but you're not using it, and your mapping is the identity function, so it's effectively a no-op. What's it about?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文