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)]