我一直在努力寻找看起来像一个简单算法的东西,但到目前为止找不到以功能方式表达它的简洁方法。以下是问题的概述:假设我有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"
部分 - 我找不到一个干净的方法来做到这一点(我有工作代码,但它很丑陋)。有没有一个很好的,惯用的功能方法来解决这个问题,要么是删除部分,还是编写算法本身的不同方式?
答案 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>