【问题标题】:Splitting complex String Pattern (without regex) in a functional approach以功能方法拆分复杂的字符串模式(没有正则表达式)
【发布时间】:2018-09-21 18:07:30
【问题描述】:

我正在尝试以更惯用的功能方法拆分没有正则表达式的字符串。

case class Parsed(blocks: Vector[String], block: String, depth: Int)

def processChar(parsed: Parsed, c: Char): Parsed = {
  import parsed._
  c match {

    case '|'  if depth == 0
                =>  parsed.copy(block = "", blocks = blocks :+ block ,
                                  depth = depth)                          
    case '['  => parsed.copy(block = block + c,
                                  depth = depth + 1)
    case ']'  if depth == 1
                => parsed.copy( block = "", blocks = blocks :+ (block + c),
                                depth = depth - 1)
    case ']'  => parsed.copy(block = block + c,
                                  depth = depth - 1)
    case _    => parsed.copy(block = block + c)
  }
}

val s = "Str|[ts1:tssub2|ts1:tssub2]|BLANK|[INT1|X.X.X.X|INT2|BLANK |BLANK | |X.X.X.X|[INT3|s1]]|[INT3|INT4|INT5|INT6|INT7|INT8|INT9|INT10|INT11|INT12|INT13|INT14|INT15]|BLANK |BLANK |[s2|s3|s4|INT16|INT17];[s5|s6|s7|INT18|INT19]|[[s8|s9|s10|INT20|INT21]|ts3:tssub3| | ];[[s11|s12|s13|INT21|INT22]|INT23:INT24|BLANK |BLANK ]|BLANK |BLANK |[s14|s15]"
val parsed = s.foldLeft(Parsed(Vector(), "", 0))(processChar)
parsed.blocks.size //20 
parsed.blocks foreach println

我希望得到以下结果parsed.blocks.size 应该是 12)。

Str
[ts1:tssub2|ts1:tssub2]
BLANK|
[INT1|X.X.X.X|INT2|BLANK |BLANK | |X.X.X.X|[INT3|s1]]
[INT3|INT4|INT5|INT6|INT7|INT8|INT9|INT10|INT11|INT12|INT13|INT14|INT15]
BLANK 
BLANK 
[s2|s3|s4|INT16|INT17];[s5|s6|s7|INT18|INT19]
[[s8|s9|s10|INT20|INT21]|ts3:tssub3| | ];[[s11|s12|s13|INT21|INT22]|INT23:INT24|BLANK |BLANK ]
BLANK 
BLANK 
[s14|s15]

但是我得到的结果是parsed.blocks.size是20)

Str
[ts1:tssub2|ts1:tssub2]

BLANK
[INT1|X.X.X.X|INT2|BLANK|BLANK||X.X.X.X|[INT3|s1]]

[INT3|INT4|INT5|INT6|INT7|INT8|INT9|INT10|INT11|INT12|INT13|INT14|INT15]

BLANK
BLANK
[s2|s3|s4|INT16|INT17]
;[s5|s6|s7|INT18|INT19]

[[s8|s9|s10|INT20|INT21]|ts1:tssub2||]
;[[s11|s12|s13|INT21|INT22]|INT23:INT24|BLANK|BLANK]

BLANK
BLANK
[s14|s15]

据我了解,这是parenthesis balancing problem 的细微变化。但是在这种情况下,; 将意味着某种延续。

在这种情况下我有两个问题

1) [ts1:tssub2|ts1:tssub2] 之后的额外条目 /space 是如何来的,也是在之后

[INT1|X.X.X.X|INT2|BLANK|BLANK||X.X.X.X|[INT3|s1]] , [INT3|INT4|INT5|INT6|INT7|INT8|INT9|INT10|INT11|INT12|INT13|INT14|INT15];[[s11|s12|s13|INT21|INT22]|INT23:INT24|BLANK|BLANK]

在我的结果中也是如此?

2) 此刻[s2|s3|s4|INT16|INT17];[s5|s6|s7|INT18|INT19]

作为两个不同的条目进入。但是,这应该合并为 [s2|s3|s4|INT16|INT17];[s5|s6|s7|INT18|INT19]

单个条目[也是如此

[[s8|s9|s10|INT20|INT21]|ts1:tssub2||]

;[[s11|s12|s13|INT21|INT22]|INT23:INT24|BLANK|BLANK])

也是]。任何线索如何做到这一点?

【问题讨论】:

  • “遇到不可恢复的循环解析导入”。开头的parsed 是什么,跟结尾的val parsed 有什么关系?
  • 对不起,我没有包含案例类并弄乱了导入,这是我的错。更新了问题
  • 次要细节:我仍然会将 import inside 函数缩进两个空格。 Scala 支持本地导入,没有理由将它们与行首对齐。
  • 同意,有一个坏习惯,即不要像您指出的那样缩进导入。我猜这是由于广泛的 Java 背景。

标签: scala parsing string-parsing idioms purely-functional


【解决方案1】:

问题 1

出现额外的空字符串块是因为每次都是前面的情况

case ']'  if depth == 1

它添加一个空块并减少深度。然后我们有

case '|'  if depth == 0

它还添加了另一个空块,将前一个空块推入结果向量中。


在回答第二个问题之前,我想提出另一种实现此解析器的方法,这种方法稍微更惯用一些。我对当前的主要批评是使用中间对象 (Parsed) 来包装状态并在每种情况下复制它。事实上,我们不需要它:更常见的方法是使用递归函数,尤其是当涉及到 depth 时。

因此,在不显着修改您的cases 的处理的情况下,它可以表示如下:

def parse(blocks: Seq[String],
          currentBlock: String,
          remaining: String,
          depth: Int): Seq[String] =
  if (remaining.isEmpty) {
    blocks
  } else {
    val curChar = remaining.head
    curChar match {
      case '|' if depth == 0 =>
        parse(blocks :+ currentBlock, "", remaining.tail, depth)
      case '[' =>
        parse(blocks, currentBlock + curChar, remaining.tail, depth + 1)
      case ']' =>
        if (depth == 1)
          parse(blocks :+ (currentBlock + curChar), "", remaining.tail, depth - 1)
        else
          parse(blocks, currentBlock + curChar, remaining.tail, depth - 1)
      case _ =>
        parse(blocks, currentBlock + curChar, remaining.tail, depth)
    }
  }

它产生与原始解决方案完全相同的输出。

要解决空块的问题,我们需要更改case '|'

case '|' if depth == 0 =>
  val updatedBlocks = if (currentBlock.isEmpty) blocks
                      else blocks :+ currentBlock
  parse(updatedBlocks, "", remaining.tail, depth)

如果当前块包含空字符串,我们将跳过它。


问题 2

要合并; char 之间的两个块,我们需要带回一个已解析的块并将其返回到currentBlock 引用中。这代表了另一种情况:

case ';' =>
  parse(blocks.init, blocks.last + curChar, remaining.tail, depth)

现在,之后

val result = parse(Seq(), "", s, 0)
result.foreach(println)

输出是

Str
[ts1:tssub2|ts1:tssub2]
BLANK
[INT1|X.X.X.X|INT2|BLANK |BLANK | |X.X.X.X|[INT3|s1]]
[INT3|INT4|INT5|INT6|INT7|INT8|INT9|INT10|INT11|INT12|INT13|INT14|INT15]
BLANK 
BLANK 
[s2|s3|s4|INT16|INT17];[s5|s6|s7|INT18|INT19]
[[s8|s9|s10|INT20|INT21]|ts3:tssub3| | ];[[s11|s12|s13|INT21|INT22]|INT23:INT24|BLANK |BLANK ]
BLANK 
BLANK 
[s14|s15]

它看起来与您要查找的内容非常相似。

【讨论】:

  • 感谢您的精彩解释!关于中间对象(已解析)的使用,我明白了你的观点。我想,如果答案中也可以引用一些示例,这对您的观点会有所帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-26
  • 2021-10-25
  • 2017-02-07
  • 1970-01-01
相关资源
最近更新 更多