F#:成对减少/聚合序列或列表

时间:2011-10-19 13:26:58

标签: .net f# functional-programming

我对函数式编程相当新,并且在列表处理任务方面存在一些问题。我有一系列记录如下:

type TestRec = {
    Id : string
    Amount : int }

现在我要删除列表中彼此构建一对的所有项目。例如,如果有两个Amount 7 -7Amount = 7 项,则应从列表中删除项。如果第三个元素包含let removeDoubles items = items |> Seq.groupBy (fun i -> Math.Abs(i.Amount)) |> Seq.map snd |> Seq.filter (fun i -> Seq.length i = 1) ,则应保留在列表中。

我希望你们能理解我想要做的事情。这是我到目前为止提出的(但它还没有正常工作):

Math.Abs

修改 确定两个元素是否相互匹配的函数可能比上面描述的更复杂(Amount)。我认为这是{{1}}值的一个很好的例子,但它可以是任何谓词函数。

编辑2: 为了澄清更多,我想对可能的相关问题给出更真实的描述。您可以想象一下发票的计算,其中列表包含所有发票头寸。现在,您要删除所有具有相同“商品编号”,“货币”且价格评估为零的发票头寸。

也许这个例子有助于解释我的问题。我只是觉得解决这个问题可能有一个更“功能性的方法”,就是在列表上运行两个循环并删除像我在命令式语言中那样的元素。

3 个答案:

答案 0 :(得分:1)

为了抽象出对立的想法,我定义了两个函数。一个用于关系相等(相关),另一个用于定义取消(相反)。

该功能首先将相关对象分组在一起,然后将它们分成相对的数组。然后根据所需的取消数量对这些得到的阵列进行切片。最后,所有内容都汇总在一起。

type TestRec = {
    Id : string;
    Amount : int;
}

let removeDoubles items related opposite =
    items
    |> Seq.groupBy related
    |> Seq.map (fun (key, values) -> 
        let t, f = values |> Seq.toArray |> Array.partition opposite
        if t.Length > f.Length then
            t.[.. t.Length - f.Length - 1]
        else
            f.[.. f.Length - t.Length - 1]
        )
    |> Seq.concat

let items = [
    {Id="first";Amount=7}; 
    {Id="seconds";Amount=7};
    {Id="we";Amount=4}; 
    {Id="negative";Amount= -7}
    ]

let test = removeDoubles 
               items 
               (fun x -> abs x.Amount) 
               (fun x -> x.Amount > 0)

printf "%A" test

System.Console.ReadLine() |> ignore

输出

seq [{Id = "first";
      Amount = 7;}; {Id = "we";
                     Amount = 4;}]

答案 1 :(得分:0)

(根据您的评论更新)

如果你想删除连续的否定对,你可以这样做:

let removePairs f items =
  let rec loop acc = function
    | a :: b :: t when f a b -> loop acc t
    | h :: t -> loop (h::acc) t
    | [] -> List.rev acc
  loop [] (List.ofSeq items)

items |> removePairs (fun {Amount=amtA} {Amount=amtB} -> amtA + amtB = 0)

答案 2 :(得分:0)

另一种选择,可能不如其他提案那么有用,但应该有效:

type R = {
    Id : int
    Amount : int
    }
let mkR id amount = {Id = id; Amount = amount}    

open System.Collections.Generic
let removePairs s : seq<R> = 
    // stores mapping: key -> corresponding nodes in result list
    let seen = Dictionary<_, LinkedList<LinkedListNode<R>>>() 
    // accumulates result
    let list = LinkedList<R>() 

    // check if paired element was already met
    // if yes - remove its first appearance from the result list
    let tryEliminate ({Amount = a} as el) = 
        // paired element will have different sign
        let key = -a
        match seen.TryGetValue key with
        | true, existing ->
            list.Remove(existing.First.Value) // Remove(LinkedListNode) works in O(1)
            existing.RemoveFirst()
            if existing.Count = 0 then seen.Remove key |> ignore
            true
        | _ -> 
            false

    let append ({Amount = a} as el) =
        let newNode = list.AddLast(el)
        let l = 
            match seen.TryGetValue a with
            | true, existing -> existing
            | false, _ ->
                let nodes = LinkedList()
                seen.Add(a, nodes)
                nodes
        l.AddLast(newNode) |> ignore

    for el in s do
        if not (tryEliminate el) then append el

    list :> _

let check source expected = 
    let result = 
        source
        |> List.mapi (fun i x -> {Id = i; Amount = x})
        |> removePairs 
        |> List.ofSeq
    if (result <> expected) then failwithf "Expected: %A, actual %A" expected result

check [1;1;-1;2] [mkR 1 1; mkR 3 2]
check [1;1;-1;-1] []
check [7;7;4] [mkR 0 7; mkR 1 7; mkR 2 4]