减去 Map<'a, int> 的两个 Map

Subtract two Maps of Map<'a, int>

我有以下类型:

type Multiset<'a when 'a: comparison> = MSet of Map<'a, int>

我想为此类型声明一个减去两个 MSet 的函数。

假设我有以下两个多重集:

let f = MSet (Map.ofList [("a",1);("b",2);("c",1)])
let g = MSet (Map.ofList [("a",1);("b",3);("c",1)])

我现在尝试创建这个需要两个多重集的减法函数。

let subtract fms sms =
             match fms with 
             | MSet fs -> match sms with 
                          | MSet ss -> 
                                      let toList ms = Map.fold (fun keys key value -> keys @ [for i = 1 to value do yield key] ) [] ms 
                                      let fromList l = match l with 
                                                       | [] -> MSet(Map.ofList [])
                                                       | x::xs -> MSet(Map.ofList (x::xs |> Seq.countBy id |> Seq.toList))
                        let sfList = toList fs
                        let ssList = toList ss
                        fromList (List.filter (fun n -> not (List.contains n sfList)) ssList)

如果我 运行 :

subtract f g 

它 returns :

MSet (map [])

这不是我想要的。 g 比 f 多一个 b,所以我希望它 return:

MSet(map [("b", 1)])

我的实现不考虑同一键的多次出现。我不太确定如何解决这个问题,所以我得到了想要的功能?

我怀疑你只是颠倒了你的论点,仅此而已。试试 subtract g f.

也就是说,您的解决方案似乎比需要的复杂得多。如何通过减去第二张地图中的计数,然后删除非正数来更新第一张地图中的值?

let sub (MSet a) (MSet b) =
    let bCount key = match Map.tryFind key b with | Some c -> c | None -> 0
    let positiveCounts, _ = 
        a 
        |> Map.map (fun key value -> value - (bCount key))
        |> Map.partition (fun _ value -> value > 0)
    MSet positiveCounts

此外,您的实现中的嵌套匹配项不需要存在。如果你想匹配两个参数,你可以这样做:

match fms, sms with
| MSet fs, MSet ss -> ...

但即使 that 也是矫枉过正 - 你可以只在参数声明中包含模式,就像我上面的实现一样。

至于重复键——在这种情况下,没有理由担心:两个参数都不能有重复键(因为它们都是 Maps),并且算法永远不会产生任何.

潜在的问题,在您的 中也很明显,似乎是相同密钥的统一。这需要一个等式约束并且可以很容易地被高级函数 Seq.groupBy 影响。由于比较不是绝对必要的,我建议使用字典,但这种方法也适用于地图。

给定一个类型

type MultiSet<'T> = MultiSet of System.Collections.Generic.IDictionary<'T, int>

以及映射键、求和它们的值并验证结果的助手;

let internal mapSum f = 
    Seq.groupBy (fun (KeyValue(k, _)) -> f k)
    >> Seq.map (fun (k, kvs) -> k, Seq.sumBy (fun (KeyValue(_, v)) -> v) kvs)
    >> Seq.filter (fun (_, v) -> v > 0)
    >> dict
    >> MultiSet

您的操作变为:

let map f (MultiSet s) =
    mapSum f s

let add (MultiSet fms) (MultiSet sms) =
    Seq.append fms sms
    |> mapSum id

let subtract (MultiSet fms) (MultiSet sms) =
    Seq.map (fun (KeyValue(k, v)) ->
        System.Collections.Generic.KeyValuePair(k, -v)) sms
    |> Seq.append fms
    |> mapSum id

let f = MultiSet(dict["a", 1; "b", 2; "c", 1])
let g = MultiSet(dict["a", 1; "b", 3; "c", 1])

subtract f g
// val it : MultiSet<string> = MultiSet (seq [])
subtract g f
// val it : MultiSet<string> = MultiSet (seq [[b, 1] {Key = "b";
//                                                    Value = 1;}])