我在F#中使用一个F#。当我从磁盘读取它时,它是一个字符串序列。每次观察的长度通常是4-5字符串:第一个字符串是标题,然后是2-4个氨基酸字符串,然后是一个空格字符串。例如:
let filePath = @"/Users/XXX/sample_database.fasta"
let fileContents = File.ReadLines(filePath)
fileContents |> Seq.iter(fun x -> printfn "%s" x)产量:

我正在寻找一种方法,使用F#中的OOB高阶函数将每个观察分割成自己的集合。我不想使用任何可变变量或for..each语法。我原以为Seq.chunkBySize可以工作->,但大小不同。有Seq.chunkByCharacter吗?
发布于 2021-08-01 15:25:09
对于这种情况,可变变量是完全可以的,前提是它们的可变性不会泄漏到更广泛的上下文中。你为什么不想用呢?
但是,如果你真的想要成为核心的“功能”,那么通常的功能方式是通过fold来做类似的事情。
let splitEm lines =
let step (blocks, currentBlock) s =
match s with
| "" -> (List.rev currentBlock :: blocks), []
| _ -> blocks, s :: currentBlock
let (blocks, lastBlock) = Array.fold step ([], []) lines
List.rev (lastBlock :: blocks)用法:
> splitEm [| "foo"; "bar"; "baz"; ""; "1"; "2"; ""; "4"; "5"; "6"; "7"; ""; "8" |]
[["foo"; "bar"; "baz"]; ["1"; "2"]; ["4"; "5"; "6"; "7"]; ["8"]]备注1:您可能需要解决一些边缘情况,这取决于您的数据和您想要的行为。例如,如果在末尾有一个空行,那么最后会有一个空块。
Note 2:您可能会注意到,这非常类似于具有可变变量的命令式算法:我甚至在谈论诸如“附加到块列表”和“使当前块为空”之类的内容。这不是巧合。在这个纯功能版本中,“变异”是通过使用不同的参数再次调用相同的函数来完成的,而在等效的命令式版本中,您只需要将这些参数转换为可变的内存单元。同样的事情,不同的观点。通常,任何命令式迭代都可以以这种方式转换为fold。
作为比较,这里有一个机械的翻译上面的命令突变为基础的风格:
let splitEm lines =
let mutable blocks = []
let mutable currentBlock = []
for s in lines do
match s with
| "" -> blocks <- List.rev currentBlock :: blocks; currentBlock <- []
| _ -> currentBlock <- s :: currentBlock
List.rev (currentBlock :: blocks)发布于 2021-08-06 21:17:04
为了说明关于包含的可变性的费奥多氏点,这里有一个可以变的例子,尽管它仍然是合理的。外部功能层是一个序列表达式,这是Seq.scan在F#源中演示的一种常见模式。
let chooseFoldSplit
folding (state : 'State)
(source : seq<'T>) : seq<'U[]> = seq {
let sref, zs = ref state, ResizeArray()
use ie = source.GetEnumerator()
while ie.MoveNext() do
let newState, uopt = folding !sref ie.Current
if newState <> !sref then
yield zs.ToArray()
zs.Clear()
sref := newState
match uopt with
| None -> ()
| Some u -> zs.Add u
if zs.Count > 0 then
yield zs.ToArray() }
// val chooseFoldSplit :
// folding:('State -> 'T -> 'State * 'U option) ->
// state:'State -> source:seq<'T> -> seq<'U []> when 'State : equalityref单元(等效于可变变量)是可变的,并且有一个可变的数据结构;它是System.Collection.Generic.List<'T>的别名,允许以O(1)的代价追加。
折叠函数的签名'State -> 'T -> 'State * 'U option让人想起fold的文件夹,只是当它的状态发生变化时会导致结果序列被分割。它还生成了一个选项,该选项表示当前组的下一个成员(或否)。
如果不将其转换为持久数组,它就会工作得很好,只要您懒洋洋地迭代结果序列,而且只需要精确地迭代一次。因此,我们需要将ResizeArray的内容与外部世界隔离开来。
对于用例来说,最简单的折叠方法是否定布尔值,但是您可以利用它来执行更复杂的任务,比如对记录编号:
[| "foo"; "1"; "2"; ""; "bar"; "4"; "5"; "6"; "7"; ""; "baz"; "8"; "" |]
|> chooseFoldSplit (fun b t ->
if t = "" then not b, None else b, Some t ) false
|> Seq.map (fun a ->
if a.Length > 1 then
{ Description = a.[0]; Sequence = String.concat "" a.[1..] }
else failwith "Format error" )
// val it : seq<FastaEntry> =
// seq [{Description = "foo";
// Sequence = "12";}; {Description = "bar";
// Sequence = "4567";}; {Description = "baz";
// Sequence = "8";}]发布于 2021-08-01 16:44:09
我进行了递归:
type FastaEntry = {Description:String; Sequence:String}
let generateFastaEntry (chunk:String seq) =
match chunk |> Seq.length with
| 0 -> None
| _ ->
let description = chunk |> Seq.head
let sequence = chunk |> Seq.tail |> Seq.reduce (fun acc x -> acc + x)
Some {Description=description; Sequence=sequence}
let rec chunk acc contents =
let index = contents |> Seq.tryFindIndex(fun x -> String.IsNullOrEmpty(x))
match index with
| None ->
let fastaEntry = generateFastaEntry contents
match fastaEntry with
| Some x -> Seq.append acc [x]
| None -> acc
| Some x ->
let currentChunk = contents |> Seq.take x
let fastaEntry = generateFastaEntry currentChunk
match fastaEntry with
| None -> acc
| Some y ->
let updatedAcc =
match Seq.isEmpty acc with
| true -> seq {y}
| false -> Seq.append acc (seq {y})
let remaining = contents |> Seq.skip (x+1)
chunk updatedAcc remaininghttps://stackoverflow.com/questions/68611484
复制相似问题