Разделение сложного шаблона строки (без регулярного выражения) в функциональном подходе

Я пытаюсь разбить строку без регулярного выражения в более идиоматическом функциональном подходе.

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]

Насколько я понимаю, это небольшая вариация проблемы балансировки скобок. Однако в данном случае ; означало бы своего рода продолжение.

У меня два вопроса в этом случае

1) Откуда появилась лишняя запись /пробел после [ts1:tssub2|ts1:tssub2], тоже после

[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])

также]. Любые подсказки, как это сделать?


person Robert Knox    schedule 11.04.2018    source источник
comment
обнаружен неисправимый цикл, разрешающий импорт. Что это за parsed в начале и какое отношение он имеет к val parsed ближе к концу?   -  person Andrey Tyukin    schedule 11.04.2018
comment
Извините, это моя ошибка, я не включил класс case и испортил импорт. Обновил вопрос   -  person Robert Knox    schedule 13.04.2018
comment
второстепенная деталь: я бы все равно сделал отступ внутри функции импорта на два пробела. Scala поддерживает локальный импорт, нет причин выравнивать их по началу строки.   -  person Andrey Tyukin    schedule 13.04.2018
comment
Согласен, имейте дурную привычку не делать отступы при импорте, как вы указали. Я думаю, это из-за обширного фона Java.   -  person Robert Knox    schedule 13.04.2018


Ответы (1)


Вопрос 1

Дополнительный пустой строковый блок появляется потому, что каждый раз выбирается самый предыдущий случай.

case ']'  if depth == 1

Он добавляет пустой блок и уменьшает глубину. Тогда у нас есть

case '|'  if depth == 0

который также добавляет еще один пустой блок, вталкивая предыдущий пустой блок в получившийся вектор.


Прежде чем ответить на второй вопрос, я хотел бы предложить другой подход к реализации этого парсера, который несколько более идиоматичен. Моя главная критика текущего — это использование промежуточного объекта (Parsed) для переноса состояния и его копирование в каждом случае. На самом деле, нам это не нужно: более частым подходом является использование рекурсивной функции, особенно когда речь идет о depth.

Таким образом, без существенного изменения обработки ваших case, ее можно представить следующим образом:

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]

И это очень похоже на то, что вы искали.

person Antot    schedule 11.04.2018
comment
Спасибо за отличное объяснение! Я понял вашу точку зрения на использование промежуточного объекта (Parsed). Я думаю, было бы полезно для вашей точки зрения, если бы в ответе можно было также сослаться на несколько примеров. - person Robert Knox; 13.04.2018