Arrays F#扫描缓冲区,查找以\c\F开头且后面没有逗号的最后一部分

Arrays F#扫描缓冲区,查找以\c\F开头且后面没有逗号的最后一部分,arrays,csv,f#,Arrays,Csv,F#,试图找到一个优雅的F#解决方案。我正在从一个文件中读取1000字节到一个缓冲区中,“buff”。那部分很简单 现在,我想扫描缓冲区,查找两个字符组合的最后一次出现: 回车符('\r')或不后跟逗号的换行符('\f') 当我找到它时,我需要找到下一个CR或LF(或缓冲区的结尾),并将中间的内容作为字符串打印 上下文:该文件是一个CSV文件,我希望最后一行在第一列中有一些非空值。优雅在旁观者眼中,但一种方法是实现匹配器类型。匹配器是给定输入字符串和位置的函数,它要么成功返回具有更新位置的新匹配器状态

试图找到一个优雅的F#解决方案。我正在从一个文件中读取1000字节到一个缓冲区中,“buff”。那部分很简单

现在,我想扫描缓冲区,查找两个字符组合的最后一次出现:

回车符('\r')或不后跟逗号的换行符('\f')

当我找到它时,我需要找到下一个CR或LF(或缓冲区的结尾),并将中间的内容作为字符串打印


上下文:该文件是一个CSV文件,我希望最后一行在第一列中有一些非空值。

优雅在旁观者眼中,但一种方法是实现匹配器类型。匹配器是给定输入字符串和位置的函数,它要么成功返回具有更新位置的新匹配器状态,要么失败

// A matcher state holds a string and the position
[<Struct>]
type MatcherState =
  {
    Input : string
    Pos   : int
  }

  static member New i p : MatcherState = { Input = i       ; Pos = p         }

  member x.Reposition p : MatcherState = { Input = x.Input ; Pos = p         }
  member x.AdvanceBy i  : MatcherState = { Input = x.Input ; Pos = x.Pos + i }

  member x.Current      = x.Input.[x.Pos]
  member x.InRange      = x.Pos >= 0 && x.Pos < x.Input.Length
  member x.Eos          = x.Pos >= x.Input.Length

// A Matcher is a function that given a MatcherState
//  returns Some MatcherState with a new position if successful
//  otherwise returns None
type Matcher  = MatcherState -> MatcherState option
注意:我在这里处理新行,后跟空格+逗号

线端匹配类似:

// Matches a line end
let mlineEnd =
  fun ms ->
    match ms with
    // Good cases, new line or EOS
    | Cr (Ln ms)
    | Ln ms
    | Eos ms      -> mgood ms
    // All other cases bad
    | _           -> mbad
最后,我们向后扫描寻找线的起点,如果我们发现它,则从该位置向前扫描,直到找到线的终点

match scanBackward testCase testCase.Length mlineStart with
| None          -> printfn "No matching line start found"
| Some startPos ->
  // Scan forwards from line start until we find a line end
  match scanForward testCase startPos mlineEnd with
  | None        -> printfn "Line start found @%d, but no matching line end found" startPos
  | Some endPos ->
    let line = testCase.Substring (startPos, endPos - startPos)
    printfn "Line found: %s" line
Matcher实际上是一个简单的解析器,但它不生成任何值,并且支持向前和向后扫描。我选择的方法不是最有效的。如果效率很重要,那么可以通过应用例如FParsec使用的解析器组合器技术来提高效率

希望这很有趣。我相信有人可以想出一个更短的正则表达式解决方案,但这有什么乐趣呢

完整示例如下(未提供质量保证,请将其用作灵感)

//匹配器状态保存字符串和位置
[]
类型匹配状态=
{
输入:字符串
位置:int
}
静态成员New ip:MatcherState={Input=i;Pos=p}
成员x.重新定位p:MatcherState={Input=x.Input;Pos=p}
成员x.AdvanceBy i:MatcherState={Input=x.Input;Pos=x.Pos+i}
成员x.当前=x.输入。[x.Pos]
成员x.InRange=x.Pos>=0和&x.Pos=x.Input.Length
//Matcher是给定MatcherState的函数
//如果成功,则返回带有新位置的某些MatcherState
//否则返回无
键入Matcher=MatcherState->MatcherState选项
让mgood ms=一些ms
设mbad=无
//匹配EOS
让我告诉你:Matcher=
趣味ms->
如果Eos女士那么
mgood ms
其他的
mbad
//匹配特定字符
让mch:Matcher=
趣味ms->
如果不是InRange女士,那么
mbad
elif ms.Current=ch然后
姆古德
让rec循环pos=
如果位置mgood-ms
//其他情况都很糟糕
|硕士
//匹配行尾
让我来看看=
趣味ms->
匹配ms
//好案例,新品或EOS
|Cr(Ln-ms)
|Ln-ms
|Eos ms->mgood ms
//其他情况都很糟糕
|硕士
//向后或向前扫描以查找匹配项
让扫描步骤输入位置(m:匹配器)=
让rec循环ms=
将m ms与
|一些彩信->
如果步骤<0,则
一些彩信
其他的
Pos女士
|无->
如果步骤=0,则
没有一个
elif steps>0&&ms.Pos>=ms.Input.Length然后
没有一个
elif步骤<0和ms位置<0
没有一个
其他的
循环打印fn“未找到匹配的行开始”
|一些startPos->
//从线路起点向前扫描,直到找到线路终点
将scanForward testCase startPos MLinend与匹配
|None->printfn“找到行开始@%d,但找不到匹配的行结束”startPos
|一些endPos->
let line=testCase.Substring(startPos,endPos-startPos)
printfn“找到行:%s”行
0

首先,如果您正在读取CSV文件,那么最好使用。这为您提供了一个很好的CSV文件类型访问,它有几个选项,您可以用来处理混乱的CSV文件(例如,如果您需要跳过几行)。或者,F#数据库也有,它允许您使用非类型化API读取文件

也就是说,如果您真的想自己实现解析,那么下面的示例应该说明惯用方法。我不确定我是否完全理解您的问题,但可以说我们:

let input = "start \r body \r, comma"
let buff = input.ToCharArray()
我相信您希望找到介于
\r
\r,
之间的区域。您可以使用一个递归函数来实现这一点,该函数记住范围的结束和开始,并在迭代字符串时递减开始范围。您可以使用模式匹配来检测所需的情况:

let rec findRange startLoc endLoc = 
  if startLoc < 0 then failwith "reached beginning"
  match buff.[startLoc], buff.[startLoc+1] with
  | ('\r' | '\f'), ',' -> findRange (startLoc - 1) startLoc
  | ('\r' | '\f'), _ -> startLoc, endLoc
  | _, _ -> findRange (startLoc - 1) endLoc

也许正则表达式是最好的工具。我在哪里可以找到FSharp.Data?Nuget告诉我:安装程序包:找不到与指定搜索条件和程序包名称“FSharp.Data”匹配的程序包。在第1行中,字符:1+安装包FSharp.Data+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~信息:ObjectNotFound:(Microsoft.Power….InstallPackage:InstallPackage)[安装包],异常+完全限定的错误ID:NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.cmdlet.InstallPackages解决了这一问题。得到了FSharp.Data。我想使用基本的解析器(因为csv格式是未知的),但我无法加载整个内容(有时会加载很多兆字节)。我能找到行数,然后只要求前n行和后n行吗?
let input = "start \r body \r, comma"
let buff = input.ToCharArray()
let rec findRange startLoc endLoc = 
  if startLoc < 0 then failwith "reached beginning"
  match buff.[startLoc], buff.[startLoc+1] with
  | ('\r' | '\f'), ',' -> findRange (startLoc - 1) startLoc
  | ('\r' | '\f'), _ -> startLoc, endLoc
  | _, _ -> findRange (startLoc - 1) endLoc
let s, e = findRange (buff.Length-2) (buff.Length-1)
input.Substring(s + 1, e - s - 1)