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

时间:2010-05-31 17:50:13

标签: collections f# functional-programming

我在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]

2 个答案:

答案 0 :(得分:5)

我认为在这种情况下使用列表和递归的简单实现会更简单。 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 |]  

这使用了 slices (例如a.[0 .. l]),它为您提供了数组的一部分 - 您可以使用这些来获取两个数组都包含元素的部分以及更长时间的剩余部分阵列。

答案 1 :(得分:2)

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

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

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

您可以使用List.concat连接两个(或更多)列表,但效率很低。在你的例子中,我没有看到使用列表的重点 - 所有这些转换回&amp;将要付出昂贵的代价。对于数组,您可以使用Array.append,这是更好的。

顺便说一句,目前还不清楚代码中mapi的目的是什么。它与map完全相同,除了索引参数,但你没有使用它,你的映射是身份函数,所以它实际上是一个无操作。它有什么关系?