Разделение строки на группы

Я пытаюсь "сгруппировать" строку в сегменты, я думаю, этот пример объяснил бы это более сурово [

scala> val str: String = "aaaabbcddeeeeeeffg"
... (do something)
res0: List("aaaa","bb","c","dd","eeeee","ff","g")

Я могу сделать несколько способов сделать это в императивном стиле (с помощью vars и переходить по строке, чтобы найти группы), но мне было интересно, может ли какое-либо лучшее функциональное решение  быть достигнуто? Я просматривал API Scala, но, похоже, что-то не соответствует моим потребностям.

Любая помощь будет оценена

Ответ 1

Вы можете разбить строку рекурсивно на span:

def s(x : String) : List[String] = if(x.size == 0) Nil else {
    val (l,r) = x.span(_ == x(0))
    l :: s(r) 
}

Рекурсивный хвост:

@annotation.tailrec def s(x : String, y : List[String] = Nil) : List[String] = {
    if(x.size == 0) y.reverse 
    else {
        val (l,r) = x.span(_ == x(0))
        s(r, l :: y)
    }
}

Ответ 2

Кажется, что все другие ответы очень сосредоточены на операциях по сбору. Но решение pure + regex намного проще:

str split """(?<=(\w))(?!\1)""" toList

В этом регулярном выражении я использую положительный lookbehind и негативный просмотр для захваченного char

Ответ 3

def group(s: String): List[String] = s match {
  case "" => Nil
  case s  => s.takeWhile(_==s.head) :: group(s.dropWhile(_==s.head))
}

Изменить: Рекурсивная версия хвоста:

def group(s: String, result: List[String] = Nil): List[String] = s match {
  case "" => result reverse
  case s  => group(s.dropWhile(_==s.head), s.takeWhile(_==s.head) :: result)
}

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

Ответ 4

Сделайте его одним слоем:

scala>  val str = "aaaabbcddddeeeeefff"
str: java.lang.String = aaaabbcddddeeeeefff

scala> str.groupBy(identity).map(_._2)
res: scala.collection.immutable.Iterable[String] = List(eeeee, fff, aaaa, bb, c, dddd)

UPDATE

Поскольку @Paul упоминает о порядке здесь, обновленная версия:

scala> str.groupBy(identity).toList.sortBy(_._1).map(_._2)
res: List[String] = List(aaaa, bb, c, dddd, eeeee, fff)

Ответ 5

Вы можете использовать некоторые вспомогательные функции, например:

val str = "aaaabbcddddeeeeefff"

def zame(chars:List[Char]) = chars.partition(_==chars.head)

def q(chars:List[Char]):List[List[Char]] = chars match {
    case Nil => Nil
    case rest =>
        val (thesame,others) = zame(rest)
        thesame :: q(others)
}

q(str.toList) map (_.mkString)

Это должно сделать трюк, верно? Несомненно, он может быть очищен в однострочном пространстве еще больше

Ответ 6

Функциональное * решение с использованием fold:

def group(s : String) : Seq[String] = {
  s.tail.foldLeft(Seq(s.head.toString)) { case (carry, elem) =>
    if ( carry.last(0) == elem ) {
      carry.init :+ (carry.last + elem)
    }
    else {
      carry :+ elem.toString
    }
  }
}

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

(*) Afaik все/большинство операций в библиотеке коллекций зависят от итераторов, имхо по своей сути нефункциональной концепции. Но код выглядит функционально, по крайней мере.

Ответ 7

Изменить: Придется читать более внимательно. Ниже нет функционального кода.

Иногда, небольшое изменяемое состояние помогает:

def group(s : String) = {
  var tmp = ""
  val b = Seq.newBuilder[String]

  s.foreach { c =>
    if ( tmp != "" && tmp.head != c ) {
      b += tmp
      tmp = ""
    }

    tmp += c
  }
  b += tmp

  b.result
}

Время выполнения O (n) (если сегменты имеют не более постоянной длины), а tmp.+=, вероятно, создает наибольшие издержки. Вместо строкового исполнения используйте строковый построитель в O (n).

group("aaaabbcddeeeeeeffg")
> Seq[String] = List(aaaa, bb, c, dd, eeeeee, ff, g)

Ответ 8

Начиная с Scala 2.13, List теперь поставляется с компоновщиком Option[(A,S)]):CC[A] rel="nofollow noreferrer"> unfold который можно комбинировать с Boolean):(String,String) rel="nofollow noreferrer"> String::span:

List.unfold("aaaabbaaacdeeffg") {
  case ""   => None
  case rest => Some(rest.span(_ == rest.head))
}
// List[String] = List("aaaa", "bb", "aaa", "c", "d", "ee", "ff", "g")

или, в качестве альтернативы, в сочетании с Scala 2.13 A):Option[A] rel="nofollow noreferrer"> Option#unless:

List.unfold("aaaabbaaacdeeffg") {
  rest => Option.unless(rest.isEmpty)(rest.span(_ == rest.head))
}
// List[String] = List("aaaa", "bb", "aaa", "c", "d", "ee", "ff", "g")

Подробности:

  • Развернуть (def unfold[A, S](init: S)(f: (S) ⇒ Option[(A, S)]): List[A]) основан на внутреннем состоянии (init), которое инициализируется в нашем случай с "aaaabbaaacdeeffg".
  • Для каждой итерации мы span (def span(p: (Char) ⇒ Boolean): (String, String)) это внутреннее состояние, чтобы найти префикс, содержащий тот же символ, и создать кортеж (String, String) который содержит префикс и остальная часть строки. В этом контексте span очень удачен, так как он производит именно то, что ожидает unfold: кортеж, содержащий следующий элемент списка и новое внутреннее состояние.
  • Развертывание прекращается, когда внутреннее состояние равно "" в этом случае мы создаем None как ожидается при разворачивании для выхода.

Ответ 9

Если вы хотите использовать Scala API, вы можете использовать встроенную функцию для этого:

str.groupBy(c => c).values

Или, если вы не возражаете против сортировки и в списке:

str.groupBy(c => c).values.toList.sorted