一起添加2个Int列表F#

时间:2017-01-18 20:00:06

标签: matrix vector f#

我正在做家庭作业,问题是我们得到两个相同大小的int列表,然后将这些数字加在一起。示例如下。

    vecadd [1;2;3] [4;5;6];; would return [5;7;9]

我是新手,我需要保持我的代码非常简单,以便我可以从中学习。到目前为止我有这个。 (不工作)

    let rec vecadd L K =
         if L <> [] then vecadd ((L.Head+K.Head)::L) K else [];;

我基本上想要用添加的数字替换第一个列表(L)。此外,我尝试使用匹配案例以不同的方式对其进行编码。

    let rec vecadd L K =
       match L with
         |[]->[]
         |h::[]-> L
         |h::t -> vecadd ((h+K.Head)::[]) K

他们都没有工作,我希望得到任何帮助。

3 个答案:

答案 0 :(得分:7)

首先,您对修改第一个列表而不是返回新列表的想法是错误的。突变(即修改数据到位)是今天错误的首要原因(过去是goto,但现在已经被禁止了很长时间)。使每个操作产生一个新的数据而不是修改现有的数据更加安全。在某些情况下,它可能更具性能,非常违反直觉(见下文)。

第二,您尝试这样做的方式,您没有做您认为自己正在做的事情。双冒号并不代表&#34;修改第一项&#34;。这意味着&#34;在前面附上一个项目&#34;。例如:

let a = [1; 2; 3]
let b = 4 :: a    // b = [4; 1; 2; 3]
let c = 5 :: b    // c = [5; 4; 1; 2; 3]

实际建立列表的方式:从一个空列表开始并为其添加项目。您正在使用的[1; 2; 3]语法只是一种语法糖。也就是[1; 2; 3] === 1::2::3::[]

那么我问如何修改列表呢?答案是,你不是! F#列表是不可变的数据结构。一旦您创建了列表,就无法再对其进行修改。

这种不变性允许有趣的优化。再看看我上面发布的示例,包含三个列表abc的示例。你认为这三个列表占用多少内存单元?第一个列表有3个项目,第二个 - 4个,第三个 - 5个,因此占用的内存总量必须为12,对吧?错误!这三个列表占用的内存总量实际上只有5个单元。这是因为列表b不是长度为4的内存块,而是与指向列表4的指针配对的数字a。数字4被称为&#34; head&#34;列表,指针被称为&#34; tail&#34;。同样,列表c包含一个数字5(其&#34; head&#34;)和一个指向列表b的指针,它是&#34; tail&#34 ;。

如果列表不是不可变的,那么就不能像这样组织它们:如果有人修改了我的尾巴怎么办?每次都必须复制列表(谷歌&#34;防御性副本&#34;)。

因此,使用列表的唯一方法是返回一个新列表。您尝试做的事情可以这样描述:如果输入列表为空,则结果为空列表;否则,结果是前缀与头部总和之间的尾部总和。你可以用F#几乎逐字地写下来:

let rec add a b =
    match a, b with
    | [], [] -> []   // sum of two empty list is an empty list
    | a::atail, b::btail -> (a + b) :: (add atail btail)  // sum of non-empty lists is sum of their tails prepended with sum of their heads

请注意,此程序不完整:它没有指定当一个输入为空而另一个输入为空时结果应该是什么。编译器将生成关于此的警告。我将解决方案作为练习留给读者。

答案 1 :(得分:4)

您可以将这两个列表与List.map2一起映射(请参阅the docs) 它成对地遍历两个列表,您可以为它提供一个函数(List.map2的第一个参数),以应用于列表中的每对元素。这会生成新列表。

 let a = [1;2;3]
 let b = [4;5;6]

 let vecadd  = List.map2 (+)

 let result  = vecadd a b
 printfn "%A" result

如果你不想做更多的工作'自己'这样的话?

let a = [1;2;3]
let b = [4;5;6]

let vecadd l1 l2 = 
    let rec step l1 l2 acc = 
        match l1, l2 with
            | [],  [] -> acc
            | [], _ | _, [] -> failwithf "one list is bigger than the other"
            | h1 :: t1, h2 :: t2 -> step t1 t2 (List.append acc [(h1 + h2)])
    step l1 l2 []
let result  = vecadd a b
printfn "%A" result

step函数是一个递归函数,它带有两个列表和一个累加器来携带结果。

  • 在最后一场比赛中,它做了三件事
    • 总结两个名单的负责人
    • 将结果添加到累加器
    • 使用新累加器和列表尾部递归调用自身
  • 当剩余列表为空时,第一个匹配将返回累加器
  • 当其中一个列表比另一个列表长时,第二个匹配会返回错误。 当其余列表为空时,将返回累加器。

调用step l1 l2 []使用两个提供的列表和一个空累加器将其启动。

答案 2 :(得分:0)

我这样做是为了跨越两个列表(将具有相同索引的项目相乘):

let items = [1I..50_000I]
let another = [1I..50_000I]

let rec cross a b = 
    let rec cross_internal = function
        | r, [], [] -> r
        | r, [], t -> r@t
        | r, t, [] -> r@t
        | r, head::t1, head2::t2 -> cross_internal(r@[head*head2], t1, t2)
    cross_internal([], a, b)

let result = cross items another
result |> printf "%A,"

注意:不是真正的性能。每一步都有列表对象创建,这太可怕了。理想情况下,内部函数 cross_internal 必须创建一个可变列表并不断更新它。

注意 2:我的范围最初较大并使用 bigint(因此 I 中的 50_000 后缀),但随后将上面的示例代码减少到仅 50,500 个元素。