【问题标题】:Adding 2 Int Lists Together F#将 2 个 Int 列表一起添加 F#
【发布时间】:2017-01-18 20:00:05
【问题描述】:

我正在做作业,问题是我们得到 2 个相同大小的 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

他们都没有工作,如果我能得到任何帮助,我将不胜感激。

【问题讨论】:

  • 为什么你不想返回一个新列表?
  • 使用 Seq.zip 并返回一个新集合对我来说似乎是最惯用的方法
  • @FyodorSoikin 作业没有指定我是否应该返回新列表。通常我会采取安全的方式而不是,但在这种情况下,我想这并不重要。很抱歉造成混乱。
  • “安全方式”和“修改现有列表”是不相容的想法。

标签: matrix vector f#


【解决方案1】:

首先,您关于修改第一个列表而不是返回新列表的想法是错误的。突变(即修改数据)是当今出现错误的第一大原因(以前是goto,但现在已经被禁止了很长时间)。让每个操作都产生一个新的数据而不是修改现有的数据要安全得多。在某些情况下,它的性能可能会更高,这完全违反直觉(见下文)。

第二,你试图这样做的方式,你没有做你认为你正在做的事情。双冒号并不意味着“修改第一项”。它的意思是“在前面附加一个项目”。例如:

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 称为列表的“头”,指针称为其“尾”。同样,列表c 包含一个数字5(它的“头”)和一个指向列表b 的指针,它是它的“尾”。

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

因此,处理列表的唯一方法是返回一个新列表。您尝试做的事情可以这样描述:如果输入列表为空,则结果为空列表;否则,结果是尾部总和加上正面总和。你可以用 F# 几乎一字不差地写下来:

let rec add a b =
    match a, b with
    | [], [] -> []   // sum of two empty lists 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

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

【讨论】:

  • 这不是尾递归吧?它需要将所有(a+b)s 保留在内存(堆栈)中,然后才能构建最终列表。如果这是正确的,那么我们不能添加具有 5m 元素的列表
  • 是的,这是正确的,这个例子不是尾递归的。
【解决方案2】:

您可以将两个列表与 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 [] 以两个提供的列表和一个空累加器开始。

【讨论】:

  • 非常有用且简单,但我正在寻找更像匹配语句或递归函数的东西。我需要坚持我们在课堂上学到的东西。
  • 你可以自己提供一个map2函数let map2 f xs ys=let rec aux acc=function x::xs,y::ys-&gt;aux(f x y::acc)(xs,ys)|_-&gt;List.rev acc in aux[](xs,ys)
【解决方案3】:

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

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(因此在 50_000 中使用了 I 后缀),但随后将上面的示例代码减少到只有 50,500 个元素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-20
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 2013-08-27
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    相关资源
    最近更新 更多