所以我想要一个接收Tuple< int,int>数组的函数并返回相同的类型,但值不同。
我想要做的是一个返回这种值的函数:
f( [1,10; 2,20; 3,40; 4,70] ) = [2,10; 3,20; 4,30]
所以你可以看到,第一个数字基本没有变化(除了第一个项目没有被选中),但最后一个数字是当前数字与前一个数字的减法(20 - 10 = 10,40 - 20) = 20,......)。
我试图在F#中提出一个不涉及可变性的算法(使用前一个值的累加器意味着我需要一个可变变量),但我不能弄清楚。这可能吗?
答案 0 :(得分:8)
使用内置函数。在这种情况下,您可以使用Seq.pairwise
。该函数采用一系列输入并生成包含先前值和当前值的对序列。获得成对后,可以使用Seq.map
将对转换为结果 - 在您的情况下,获取当前值的ID并从当前值中减去先前的值:
input
|> Seq.pairwise
|> Seq.map (fun ((pid, pval), (nid, nval)) -> nid, nval-pval)
请注意,结果是序列(IEnumerable<T>
)而不是列表 - 只是因为Seq
模块包含一些(有用的)函数。您可以使用List.ofSeq
将其转换回列表。
使用显式递归。如果你的任务不适合某些内置函数所涵盖的常见模式之一,那么答案就是使用递归(一般来说) ,取代功能风格的变异。)
为了完整性,递归版本看起来像这样(这不是完美的,因为它不是尾递归所以它可能导致堆栈溢出,但它证明了这个想法):
let rec f list =
match list with
| (pid, pval)::(((nid, nval)::_) as tail) ->
(nid, nval-pval)::(f tail)
| _ -> []
这会获取一个列表并查看列表(pid, pval)
和(nid, nval)
的前两个元素。然后它根据(nid, nval-pval)
中的两个元素计算新值,然后递归处理列表的其余部分(tail
),跳过第一个元素。如果列表包含一个或多个元素(第二种情况),则不返回任何内容。
可以使用&#34;累加器&#34;来编写尾递归版本。特技。我们不是编写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 []
调用该函数来初始化累加器。
答案 1 :(得分:4)
> 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
代替_
,因为未使用该值。
答案 2 :(得分:2)
Mark和Tomas为这个具体问题提供了非常好的解决方案。你的问题有一个声明我认为值得第三个答案,但是:
(使用累加器表示前一个值意味着我需要一个可变变量)
但事实并非如此! List.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
是尾递归的。
当然,使用Seq.pairwise
的Tomas和Mark的解决方案更适合您的特定问题,并且您肯定希望在实践中使用其中一个。
答案 3 :(得分:0)
每当我们需要从另一个序列创建一个序列时,输出中的一个元素是其前辈的函数, scan
(docs)就派上用场了:
[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)]