首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在F#中分裂变长字符串的Seq

在F#中分裂变长字符串的Seq
EN

Stack Overflow用户
提问于 2021-08-01 14:41:45
回答 4查看 172关注 0票数 0

我在F#中使用一个F#。当我从磁盘读取它时,它是一个字符串序列。每次观察的长度通常是4-5字符串:第一个字符串是标题,然后是2-4个氨基酸字符串,然后是一个空格字符串。例如:

代码语言:javascript
复制
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吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2021-08-01 15:25:09

对于这种情况,可变变量是完全可以的,前提是它们的可变性不会泄漏到更广泛的上下文中。你为什么不想用呢?

但是,如果你真的想要成为核心的“功能”,那么通常的功能方式是通过fold来做类似的事情。

  • 您的折叠状态将是一对“积攒至今”和“当前块”。
  • 在每个步骤中,如果得到一个非空字符串,则将其附加到“当前块”。
  • 如果您得到一个空字符串,这意味着当前块已经结束,所以您将当前块附加到“到目前为止”的列表中,并使当前块为空。
  • 这样,在折叠结束时,你将得到一对“除了最后一个积木”和“最后一块”,你可以把它们粘合在一起。
  • 另外,还有一个优化细节:因为我要做很多“将一个东西附加到一个列表”,所以我想为此使用一个链接列表,因为它有固定时间的附加。但问题是,它只是恒定的时间预先,而不是附加,这意味着,我将结束所有的列表颠倒。但没关系,我会在最后再把它们颠倒过来。列表反转是一种线性操作,这意味着我的整件事仍然是线性的。
代码语言:javascript
复制
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)

用法:

代码语言:javascript
复制
> 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

作为比较,这里有一个机械的翻译上面的命令突变为基础的风格:

代码语言:javascript
复制
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)
票数 2
EN

Stack Overflow用户

发布于 2021-08-06 21:17:04

为了说明关于包含的可变性的费奥多氏点,这里有一个可以变的例子,尽管它仍然是合理的。外部功能层是一个序列表达式,这是Seq.scanF#源中演示的一种常见模式。

代码语言:javascript
复制
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 : equality

ref单元(等效于可变变量)是可变的,并且有一个可变的数据结构;它是System.Collection.Generic.List<'T>的别名,允许以O(1)的代价追加。

折叠函数的签名'State -> 'T -> 'State * 'U option让人想起fold的文件夹,只是当它的状态发生变化时会导致结果序列被分割。它还生成了一个选项,该选项表示当前组的下一个成员(或否)。

如果不将其转换为持久数组,它就会工作得很好,只要您懒洋洋地迭代结果序列,而且只需要精确地迭代一次。因此,我们需要将ResizeArray的内容与外部世界隔离开来。

对于用例来说,最简单的折叠方法是否定布尔值,但是您可以利用它来执行更复杂的任务,比如对记录编号:

代码语言:javascript
复制
[| "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";}]
票数 1
EN

Stack Overflow用户

发布于 2021-08-01 16:44:09

我进行了递归:

代码语言:javascript
复制
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 remaining
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68611484

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档