根据匹配规则按连续对筛选数组或列表

时间:2011-02-23 21:11:52

标签: algorithm list f# sequence seq

这可能是微不足道的,我确实有一个解决方案,但我对此并不满意。不知何故,(更多)更简单的表单似乎不起作用,并且它在拐角情况下变得混乱(连续的第一个或最后一个匹配对)。

为了简单起见,我们将匹配规则定义为任意两个或多个差异为2的数字。例如:

> filterTwins [1; 2; 4; 6; 8; 10; 15; 17]
val it : int list = [2; 4; 6; 8; 10; 15; 17]

我目前使用的代码是这个,只是感觉草率和超重:

let filterTwins list =
    let func item acc =
        let prevItem, resultList = acc
        match prevItem, resultList with
        | 0, [] 
            -> item, []
        | var, [] when var - 2 = item
            -> item, item::var::resultList
        | var, hd::tl when var - 2 = item && hd <> var
            -> item, item::var::resultList
        | var, _ when var - 2 = item
            -> item, item::resultList
        | _ 
            -> item, resultList

    List.foldBack func list (0, [])
    |> snd

我打算用我自己的原创练习来试验List.foldBack,大型列表和并行编程(进展顺利)但最终弄乱了“简单”部分......

通过答案指南

  • Daniel's last,113个字符*,易于理解,速度慢
  • Kvb's 2nd,106个字符*(如果我包含该功能),简单,但返回值需要工作
  • Stephen's 2nd,397个字符*,冗长且相对复杂,但速度最快
  • Abel's,155个字符*,基于Daniel的,允许重复(这不是必需品,顺便说一句)并且相对较快。

有更多答案,但我相信以上是最明显的答案。希望我接受丹尼尔的答案作为解决方案并没有伤害任何人的感受:每一个解决方案都应该是选定的答案(!)。

*使用函数名称作为一个字符进行计数

5 个答案:

答案 0 :(得分:2)

这会做你想要的吗?

let filterTwins l = 
    let rec filter l acc flag =
        match l with
        | [] -> List.rev acc
        | a :: b :: rest when b - 2 = a -> 
            filter (b::rest) (if flag then b::acc else b::a::acc) true
        | _ :: t -> filter t acc false
    filter l [] false

这非常低效,但这是使用更多内置函数的另一种方法:

let filterTwinsSimple l =
    l 
    |> Seq.pairwise 
    |> Seq.filter (fun (a, b) -> b - 2 = a)
    |> Seq.collect (fun (a, b) -> [a; b])
    |> Seq.distinct
    |> Seq.toList

也许稍好一点:

let filterTwinsSimple l =
    seq { 
        for (a, b) in Seq.pairwise l do
            if b - 2 = a then 
                yield a
                yield b 
    }
    |> Seq.distinct
    |> Seq.toList

答案 1 :(得分:1)

这个怎么样?

let filterPairs f =
  let rec filter keepHead = function
  | x::(y::_ as xs) when f x y -> x::(filter true xs)
  | x::xs -> 
      let rest = filter false xs
      if keepHead then x::rest else rest
  | _ -> []
  filter false

let test = filterPairs (fun x y -> y - x = 2) [1; 2; 4; 6; 8; 10; 15; 17]

或者如果您的所有列表项都是唯一的,您可以这样做:

let rec filterPairs f s =
  s
  |> Seq.windowed 2
  |> Seq.filter (fun [|a;b|] -> f a b)
  |> Seq.concat
  |> Seq.distinct

let test = filterPairs (fun x y -> y - x = 2) [1; 2; 4; 6; 8; 10; 15; 17]

修改

或者这是我觉得优雅的另一种选择。首先定义一个函数,用于将列表分解为满足谓词的连续项的组列表:

let rec groupConsec f = function
| [] -> []
| x::(y::_ as xs) when f x y -> 
    let (gp::gps) = groupConsec f xs 
    (x::gp)::gps
| x::xs -> [x]::(groupConsec f xs)

然后,通过将所有结果重新收集起来,丢弃任何单身人士来构建你的功能:

let filterPairs f =
  groupConsec f
  >> List.collect (function | [_] -> [] | l -> l)

let test = filterPairs (fun x y -> y - x = 2) [1; 2; 4; 6; 8; 10; 15; 17]

答案 2 :(得分:1)

以下解决方案符合您自己的精神,但我使用一个区别联盟来封装算法的各个方面并稍微控制一下疯狂:

type status =
    | Keep of int
    | Skip of int
    | Tail

let filterTwins xl = 
    (Tail, [])
    |> List.foldBack
        (fun cur (prev, acc) ->
            match prev with
            | Skip(prev) when prev - cur = 2 -> (Keep(cur), cur::prev::acc)
            | Keep(prev) when prev - cur = 2 -> (Keep(cur), cur::acc)
            | _ -> (Skip(cur), acc))
        xl
    |> snd

答案 3 :(得分:1)

这是另一个解决方案,它使用类似的判别联合策略作为我的另一个答案,但它可以懒惰地处理序列,所以你可以看到那些双胞胎(素数?)在它们出现时滚动:

type status =
    | KeepTwo of int * int
    | KeepOne of int
    | SkipOne of int
    | Head

let filterTwins xl = 
    let xl' =
        Seq.scan
            (fun prev cur ->
                match prev with
                | KeepTwo(_,prev) | KeepOne prev when cur - prev = 2 ->
                    KeepOne cur
                | SkipOne prev when cur - prev = 2 ->
                    KeepTwo(prev,cur)
                | _ ->
                    SkipOne cur)
            Head
            xl

    seq { 
        for x in xl' do
            match x with
            | KeepTwo(a,b) -> yield a; yield b
            | KeepOne b -> yield b
            | _ -> ()
    }

答案 4 :(得分:0)

为了完整起见,我将基于此主题中的友好建议,用我最终提出的内容回答这个问题。

这种方法的好处是它不需要Seq.distinct,我认为这是一种改进,因为它允许重复。但是,它仍然需要List.rev,这并不是最快的。它也不是最简洁的代码(参见问题解决方案的比较)。

let filterTwins l = 
    l 
    |> Seq.pairwise 
    |> Seq.fold (fun a (x, y) -> 
        if y - x = 2 then (if List.head a = x then y::a else y::x::a) 
        else a) [0]
    |> List.rev 
    |> List.tail