删除功能样式中的元素

时间:2013-06-07 22:44:34

标签: algorithm f#

我一直在努力寻找看起来像一个简单算法的东西,但到目前为止找不到以功能方式表达它的简洁方法。以下是问题的概述:假设我有2个数组X和Y,

X = [| 1; 2; 2; 3; 3 |]
Y = [| 5; 4; 4; 3; 2; 2 |]

我想要的是检索匹配的元素和不匹配的元素,如:

matched = [| 2; 2; 3 |]
unmatched = [| 1; 3 |], [| 4; 4; 5 |]

在伪代码中,这就是我想到的解决问题的方法:

let rec match matches x y =
    let m = find first match from x in y
    if no match, (matches, x, y)
    else
        let x' = remove m from x
        let y' = remove m from y
        let matches' = add m to matches
        match matches' x' y'

我遇到的问题是"remove m from x"部分 - 我找不到一个干净的方法来做到这一点(我有工作代码,但它很丑陋)。有没有一个很好的,惯用的功能方法来解决这个问题,要么是删除部分,还是编写算法本身的不同方式?

2 个答案:

答案 0 :(得分:4)

这可以使用正确的数据结构轻松解决,但是如果你想手动完成,这就是我在Haskell中的方法。我不知道F#足以翻译这个,但我希望它足够相似。所以,这里是(半)文字的Haskell。

overlap xs ys =

我首先对两个序列进行排序,以避免必须了解以前的值。

  go (sort xs) (sort ys)
    where

递归的两个基本情况很容易处理 - 如果任一列表为空,则结果包括重叠的元素列表中的另一个列表。

      go xs []           = ([], (xs, []))
      go [] ys           = ([], ([], ys))
然后我检查每个列表中的第一个元素。如果它们匹配,我可以确定列表在该元素上重叠,因此我将其添加到包含的元素中,并让我排除元素。我通过递归列表的尾部继续搜索列表的其余部分。

      go (x:xs) (y:ys)
        | x == y = let (  included, excluded)     = go xs ys
                   in  (x:included, excluded)

然后是有趣的部分!我基本上想知道的是,如果其中一个列表的第一个元素存在于第二个列表中 - 在这种情况下,我应该将它添加到排除列表中,然后继续搜索。

        | x < y  = let (included, (  xex,   yex)) = go xs (y:ys)
                   in  (included, (x:xex,   yex))
        | y < x  = let (included, (  xex,   yex)) = go (x:xs) ys
                   in  (included, (  xex, y:yex))

实际上就是这样。它似乎至少适用于你给出的例子。

> let (matched, unmatched) = overlap x y
> matched
[2,2,3]
> unmatched
([1,3],[4,4,5])

答案 1 :(得分:3)

您似乎在描述multiset (bag)及其操作。

如果使用适当的数据结构,操作非常容易实现:

// Assume that X, Y are initialized bags
let matches = X.IntersectWith(Y)
let x = X.Difference(Y)
let y = Y.Difference(X)

.NET框架中没有内置的Bag集合。您可以使用Power Collection库,包括Bag class,其中包含上述函数签名。

<强>更新

您可以通过弱升序列表来表示行李。以下是使用F#语法的@ kqr答案的改进版本:

let overlap xs ys =
    let rec loop (matches, ins, outs) xs ys =
        match xs, ys with
        // found a match
        | x::xs', y::ys' when x = y -> loop (x::matches, ins, outs) xs' ys'
        // `x` is smaller than every element in `ys`, put `x` into `ins`
        | x::xs', y::ys' when x < y -> loop (matches, x::ins, outs) xs' ys
        // `y` is smaller than every element in `xs`, put `y` into `outs`
        | x::xs', y::ys' -> loop (matches, ins, y::outs) xs ys'
        // copy remaining elements in `xs` to `ins`
        | x::xs', [] -> loop (matches, x::ins, outs) xs' ys
        // copy remaining elements in `ys` to `outs`
        | [], y::ys' -> loop (matches, ins, y::outs) xs ys'
        | [], [] -> (List.rev matches, List.rev ins, List.rev outs)
    loop ([], [], []) (List.sort xs) (List.sort ys)

在对List.sort的两次调用(可能是O(nlogn))之后,查找匹配与两个列表的长度之和呈线性关系。

如果你需要一个快速和脏的包模块,我会建议像这样的模块签名:

type Bag<'T> = Bag of 'T list

module Bag =
    val count : 'T -> Bag<'T> -> int
    val insert : 'T -> Bag<'T> -> Bag<'T>
    val intersect : Bag<'T> -> Bag<'T> -> Bag<'T>
    val union : Bag<'T> -> Bag<'T> -> Bag<'T>
    val difference : Bag<'T> -> Bag<'T> -> Bag<'T>