处理元组流而没有可变性?

时间:2014-03-20 20:05:52

标签: f# tuples immutability

所以我想要一个接收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#中提出一个不涉及可变性的算法(使用前一个值的累加器意味着我需要一个可变变量),但我不能弄清楚。这可能吗?

4 个答案:

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