F# 处理不可变的元组流?
所以我想要一个函数,它接收一个元组数组并返回相同的类型,但值不同 我想做的是一个返回此类值的函数:F# 处理不可变的元组流?,f#,tuples,immutability,F#,Tuples,Immutability,所以我想要一个函数,它接收一个元组数组并返回相同的类型,但值不同 我想做的是一个返回此类值的函数: f( [1,10; 2,20; 3,40; 4,70] ) = [2,10; 3,20; 4,30] 正如您所看到的,第一个数字基本上没有变化(除了第一个项目没有被选中),但是最后一个数字是当前数字与前一个数字的减法(20-10=10,40-20=20,…) 我曾尝试在F#中提出一种算法,它不涉及易变性(对前面的值使用累加器意味着我需要一个易变变量),但我无法理解。这是可能的吗?使用内置函数。在
f( [1,10; 2,20; 3,40; 4,70] ) = [2,10; 3,20; 4,30]
正如您所看到的,第一个数字基本上没有变化(除了第一个项目没有被选中),但是最后一个数字是当前数字与前一个数字的减法(20-10=10,40-20=20,…)
我曾尝试在F#中提出一种算法,它不涉及易变性(对前面的值使用累加器意味着我需要一个易变变量),但我无法理解。这是可能的吗?使用内置函数。在这种情况下,您可以使用
顺序成对
。该函数接受一系列输入,并生成一系列包含前一个值和当前值的对。有了这些对之后,您可以使用Seq.map
将这些对转换为结果-在您的情况下,获取当前值的ID并从当前值中减去以前的值:
input
|> Seq.pairwise
|> Seq.map (fun ((pid, pval), (nid, nval)) -> nid, nval-pval)
请注意,结果是一个序列(IEnumerable
)而不是一个列表-这仅仅是因为Seq
模块包含了更多(有用的)函数。您可以使用list.ofSeq
将其转换回列表
使用显式递归。如果您的任务不符合某些内置函数所涵盖的常见模式之一,那么答案将是使用递归(一般来说,递归取代函数样式中的变异)
为了完整性起见,递归版本看起来是这样的(这不是完美的,因为它不是尾部递归的,所以可能会导致堆栈溢出,但它演示了这个想法):
这将获取一个列表并查看列表的前两个元素(pid,pval)
和(nid,nval)
。然后它根据(nid,nval pval)
中的两个元素计算新值,然后递归处理列表的其余部分(tail
),跳过第一个元素。如果列表包含一个或更少的元素(第二种情况),则不返回任何内容
尾部递归版本可以使用“累加器”技巧编写。我们不编写newValue::(recursiveCall…
而是将新生成的值累积到一个作为参数保存的列表中,然后将其反转:
let rec f list acc =
match list with
| (pid, pval)::(((nid, nval)::_) as tail) ->
f tail ((nid, nval-pval)::acc)
| _ -> List.rev acc
现在您只需要使用f input[]
调用函数来初始化累加器
> let values = [(1, 10); (2, 20); (3, 40); (4, 70)];;
val values : (int * int) list = [(1, 10); (2, 20); (3, 40); (4, 70)]
> values
|> Seq.pairwise
|> Seq.map (fun ((x1, y1), (x2, y2)) -> (x2, y2 - y1))
|> Seq.toList;;
val it : (int * int) list = [(2, 10); (3, 20); (4, 30)]
Seq.pairwise
将序列中的每个元素作为一对提供给您,但第一个元素除外,它仅作为第二个元素的前一个元素可用
例如:
> values |> Seq.pairwise |> Seq.toList;;
val it : ((int * int) * (int * int)) list =
[((1, 10), (2, 20)); ((2, 20), (3, 40)); ((3, 40), (4, 70))]
其次,Seq.map
使用所需的算法映射这些对中的每一对
请注意,这使用了延迟求值——我只在末尾使用了Seq.ToList
,以使输出更具可读性
顺便说一句,您也可以这样编写映射函数:
Seq.map (fun ((_, y1), (x2, y2)) -> (x2, y2 - y1))
请注意,由于未使用该值,因此将取代
x1的替换为。Mark和Tomas为特定问题提供了非常好的解决方案。你的问题有一个陈述,我想应该有第三个答案,不过:
(对前一个值使用累加器意味着我需要一个可变变量)
但事实并非如此<代码>列表。fold
的存在正是为了帮助您以功能性方式使用累加器处理列表。下面是它的外观:
let f xs = List.fold (fun (y, ys) (d, x) -> x, (d, x-y) :: ys)
(snd (List.head xs), [])
(List.tail xs)
|> snd |> List.rev
这里的累加器是参数(y,ys)
到第一行的fun…
。我们可以看到累加器如何更新到->
的右侧:我们累加列表x
的上一个元素,以及我们正在构建的新列表(d,x-y)::xs
。我们将以相反的顺序获得该列表,因此我们最终使用list.rev
将其反转
顺便提一下,List.fold
是尾部递归的
当然,Tomas和Mark使用Seq.pairwise
的解决方案对于您的特定问题来说要整洁得多,而且您肯定希望在实践中使用其中一种解决方案。每当我们需要从另一个序列创建一个序列时,其中输出中的一个元素是其前辈的函数,扫描
()方便快捷:
[1,10; 2,20; 3,40; 4,70]
|> List.scan (fun ((accA, accB), prevB) (elA, elB) -> ((elA, elB-prevB), elB)) ((0, 0), 0)
|> Seq.skip 2
|> Seq.map fst
收益率:
[(2, 10); (3, 20); (4, 30)]
如果你提出了一个非尾部递归算法,编译器能给你一个警告吗?@knocte不幸的是,它没有给你一个警告(我认为没有办法做到这一点)。对于F#PowerTools;-)来说,这将是一个不错的补充(). 我添加了一个尾部递归版本。这是一个讽刺的答案吗?我是说,这是一个不可能解决的问题吗?(就像都灵悖论中无法确定另一个程序是否进入了无限循环?:)@knocte这不是一个讽刺的答案(如果听起来像的话,很抱歉!)检测一个函数是否是尾部递归并不难,而且会是一个非常好的功能!(眨眼“;-)”意味着你可以为这个项目做出贡献!)我知道我可以贡献(请参阅fsharp/fsharp;)中我的所有贡献),但最棒的事情是将它放在编译器中,这样您就可以在fsharpbinding或您使用的任何东西中获得反馈,而无需安装任何东西。这不起作用,我认为您混合了一些东西。理解这是如何工作的最好方法是让元组是DateTimeint而不是intint(当您使用它进行测试时,代码不会编译),我编辑了答案。(我错误地认为当你写1*10
时,你指的是实际的乘法,即10
,而不是成对的1,10
)
[(2, 10); (3, 20); (4, 30)]