[英]Split seq in F#
我應該通過元素的屬性將seq<a>
拆分為seq<seq<a>>
。 如果此屬性等於給定值,則必須在該點“拆分”。 我怎樣才能在FSharp 中做到這一點?
如果必須在該項目處拆分或不拆分,則向其傳遞一個返回布爾值的“函數”應該很好。
示例: 輸入序列: seq: {1,2,3,4,1,5,6,7,1,9}
當它等於 1 時應該在每個項目處拆分,所以結果應該是:
seq
{
seq{1,2,3,4}
seq{1,5,6,7}
seq{1,9}
}
你真正要做的就是分組——每次遇到一個值時創建一個新組。
let splitBy f input =
let i = ref 0
input
|> Seq.map (fun x ->
if f x then incr i
!i, x)
|> Seq.groupBy fst
|> Seq.map (fun (_, b) -> Seq.map snd b)
let items = seq [1;2;3;4;1;5;6;7;1;9]
items |> splitBy ((=) 1)
再一次,更短,斯蒂芬的不錯的改進:
let splitBy f input =
let i = ref 0
input
|> Seq.groupBy (fun x ->
if f x then incr i
!i)
|> Seq.map snd
不幸的是,編寫使用序列( seq<'T>
類型)的函數有點困難。 它們不能很好地處理列表上的模式匹配等功能概念。 相反,您必須使用GetEnumerator
方法和生成的IEnumerator<'T>
類型。 這通常使代碼非常必要。 在這種情況下,我會寫以下內容:
let splitUsing special (input:seq<_>) = seq {
use en = input.GetEnumerator()
let finished = ref false
let start = ref true
let rec taking () = seq {
if not (en.MoveNext()) then finished := true
elif en.Current = special then start := true
else
yield en.Current
yield! taking() }
yield taking()
while not (!finished) do
yield Seq.concat [ Seq.singleton special; taking()] }
我不推薦使用函數式風格(例如使用Seq.skip
和Seq.head
),因為這非常低效 - 它創建了一個序列鏈,從其他序列中獲取值並返回它(所以通常有 O( N^2) 復雜性)。
或者,您可以使用計算生成器來編寫此代碼以使用IEnumerator<'T>
,但這不是標准的。 如果你想玩它,你可以在這里找到它。
以下是一個不純的實現,但會懶惰地產生不可變的序列:
let unflatten f s = seq {
let buffer = ResizeArray()
let flush() = seq {
if buffer.Count > 0 then
yield Seq.readonly (buffer.ToArray())
buffer.Clear() }
for item in s do
if f item then yield! flush()
buffer.Add(item)
yield! flush() }
f
是 function 用於測試一個元素是否應該是一個分割點:
[1;2;3;4;1;5;6;7;1;9] |> unflatten (fun item -> item = 1)
可能不是最有效的解決方案,但這有效:
let takeAndSkipWhile f s = Seq.takeWhile f s, Seq.skipWhile f s
let takeAndSkipUntil f = takeAndSkipWhile (f >> not)
let rec splitOn f s =
if Seq.isEmpty s then
Seq.empty
else
let pre, post =
if f (Seq.head s) then
takeAndSkipUntil f (Seq.skip 1 s)
|> fun (a, b) ->
Seq.append [Seq.head s] a, b
else
takeAndSkipUntil f s
if Seq.isEmpty pre then
Seq.singleton post
else
Seq.append [pre] (splitOn f post)
splitOn ((=) 1) [1;2;3;4;1;5;6;7;1;9] // int list is compatible with seq<int>
splitOn 的類型是 ('a -> bool) -> seq<'a> -> seq>。 我沒有在很多輸入上測試過它,但它似乎有效。
如果您正在尋找實際上像 split 作為字符串拆分一樣工作的東西(即,謂詞返回 true 的項目不包括在內),下面是我想出的.. 試圖盡可能地發揮作用:)
let fromEnum (input : 'a IEnumerator) =
seq {
while input.MoveNext() do
yield input.Current
}
let getMore (input : 'a IEnumerator) =
if input.MoveNext() = false then None
else Some ((input |> fromEnum) |> Seq.append [input.Current])
let splitBy (f : 'a -> bool) (input : 'a seq) =
use s = input.GetEnumerator()
let rec loop (acc : 'a seq seq) =
match s |> getMore with
| None -> acc
| Some x ->[x |> Seq.takeWhile (f >> not) |> Seq.toList |> List.toSeq]
|> Seq.append acc
|> loop
loop Seq.empty |> Seq.filter (Seq.isEmpty >> not)
seq [1;2;3;4;1;5;6;7;1;9;5;5;1]
|> splitBy ( (=) 1) |> printfn "%A"
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.