2017-09-24 35 views
4

給定以下輸入序列,我想生成所需的輸出。 我知道,如果所有的窗口都是固定長度的,Seq.window幾乎可以得到所需的結果。然而,在這種情況下,它們不是固定的,我希望在遇到「a」時開始一個新的序列。 這是可能的標準集合庫嗎?F#如何根據謂詞而不是固定長度來窗口序列

let inputSequence = 
     ["a"; "b"; "c"; 
     "a"; "b"; "c"; "d"; 
     "a"; "b"; 
     "a"; "d"; "f"; 
     "a"; "x"; "y"; "z"] 

let desiredResult = 
    [["a"; "b"; "c";] 
    ["a"; "b"; "c"; "d";] 
    ["a"; "b"; ] 
    ["a"; "d"; "f";] 
    ["a"; "x"; "y"; "z"]] 
+1

要做到這一點,最好的辦法可能是使用'List.fold' –

回答

4

下面是一個使用可變的狀態,但是是非常簡潔的方式:

let mutable i = 0 
[ for x in inputSequence do 
    if x = "a" then i <- i + 1 
    yield i, x ] 
|> List.groupBy fst 
|> List.map snd 
|> List.map (List.map snd) 
+0

該技術採用屈服具有可以輕鬆更改邏輯的優點,並且確實可以輕鬆地編寫幾乎任何類型的配方。 –

1

不幸的是,儘管FP遺產F#缺乏一些常見的列表操作功能。基於謂詞的分割/分割是一個。你可以使用遞歸來實現這一點,所以摺疊。不過這裏是如果你只是想應用庫函數:

let inputSequence = 
     ["a"; "b"; "c"; 
     "a"; "b"; "c"; "d"; 
     "a"; "b"; 
     "a"; "d"; "f"; 
     "a"; "x"; "y"; "z"] 

let aIdx = 
    inputSequence 
     |> List.mapi (fun i x -> i, x) //find the index of a elements 
     |> List.filter (fun x -> snd x = "a") 
     |> List.map fst //extract it into a list 

[List.length inputSequence] 
    |> List.append aIdx //We will need the last "a" index, and the end of the list 
    |> List.pairwise //begin and end index 
    |> List.map (fun (x,y) -> inputSequence.[x .. (y - 1)]) 

//val it : string list list = 
[["a"; "b"; "c"]; ["a"; "b"; "c"; "d"]; ["a"; "b"]; ["a"; "d"; "f"]; 
["a"; "x"; "y"; "z"]] 
3

正如在其他答覆中提到,你可以很容易實現這個使用遞歸或使用倍。

let chunkAt start list = 
    let rec loop chunk chunks list = 
    match list with 
    | [] -> List.rev ((List.rev chunk)::chunks) 
    | x::xs when x = start && List.isEmpty chunk -> loop [x] chunks xs 
    | x::xs when x = start -> loop [x] ((List.rev chunk)::chunks) xs 
    | x::xs -> loop (x::chunk) chunks xs 
    loop [] [] list 

然後您就可以使用您的輸入順序運行:

chunkAt "a" inputSequence 
爲了讓遞歸版本更加有用,您可以在列表中包含特定值定義一個函數 chunkAt創建一個新塊

雖然沒有標準庫函數可以做到這一點,但您可以使用data series manipulation library Deedle,它實現了相當豐富的分塊函數。要做到這一點使用Deedle,你可以把你的序列到由序號索引收錄了一系列然後用:

let s = Series.ofValues inputSequence 
let chunked = s |> Series.chunkWhile (fun _ k2 -> s.[k2] <> "a") 

如果你希望把數據傳回一個列表,你可以使用返回系列Values財產:

chunked.Values |> Seq.map (fun s -> s.Values) 
0

這個答案有幾乎相同的機制作爲一個親通過@TheQuickBrownFox vided,但它並沒有使用可變:

inputSequence 
|> List.scan (fun i x -> if x = "a" then i + 1 else i) 0 
|> List.tail 
|> List.zip inputSequence 
|> List.groupBy snd 
|> List.map (snd >> List.map fst) 

如果你想使用一個庫,除了一個由@Tomas建議,F#+提供了一些基本的拆分功能,允許撰寫像這樣的功能:

let splitEvery x = 
    List.split (seq [[x]]) >> Seq.map (List.cons x) >> Seq.tail >> Seq.toList 

there is a proposal包括這些類型的功能在F#核心,實在值得閱讀的討論。

0

這裏是一個短:

let folder (str: string) ((xs, xss): list<string> * list<list<string>>) = 
    if str = "a" then ([], ((str :: xs) :: xss)) 
    else (str :: xs, xss) 

List.foldBack folder inputSequence ([], []) 
|> snd 

// [["a"; "b"; "c"]; ["a"; "b"; "c"; "d"]; ["a"; "b"]; ["a"; "d"; "f"]; ["a"; "x"; "y"; "z"]] 

這滿足於問題的規範:I would like to start a new sequence whenever "a" is encountered,因爲之前,首先任何初始字符串「a」將被忽略。例如,對於

let inputSequence = 
     ["r"; "s"; 
     "a"; "b"; "c"; 
     "a"; "b"; "c"; "d"; 
     "a"; "b"; 
     "a"; "d"; "f"; 
     "a"; "x"; "y"; "z"] 

得到與上述相同的結果。

如果人們需要第一個「一」以下之前捕捉到初始字符串可以使用:

match inputSequence |> List.tryFindIndex (fun x -> x = "a") with 
| None -> [inputSequence] 
| Some i -> (List.take i inputSequence) :: 
      (List.foldBack folder (List.skip i inputSequence) ([], []) |> snd) 

// [["r"; "s"]; ["a"; "b"; "c"]; ["a"; "b"; "c"; "d"]; ["a"; "b"]; 
    ["a"; "d"; "f"]; ["a"; "x"; "y"; "z"]] 
+0

太短了。你假設謂詞將成爲列表的第一個元素 - 否則,你將失去它前面的元素 – kaefer

+0

它符合規範。 – Soldalma