2011-03-09 9 views
11

que estoy tratando de 'grupo' una cadena en segmentos, supongo que este ejemplo lo explicaría más sucintamentecadena de la división en grupos

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

puedo thnk de algunas maneras de hacer esto en un estilo imperativo (con vars y pasando por la cadena para encontrar grupos) pero me preguntaba si se podría lograr alguna solución funcional mejor ? He estado buscando a través de la API de Scala, pero no parece haber algo que se ajuste a mis necesidades.

Cualquier ayuda se agradece

+0

¡Sería útil si menciona (y etiqueta) los idiomas en los que desea trabajar! –

+0

la publicación está etiquetada? Puede que haya tardado un tiempo en aparecer en los servidores de SO o en algo – djhworld

+0

¿Espera hacer coincidir 'aaabbccddeeffffffhhhhhiiiiijjjj' etc. también? ¿O solo esos 7 caracteres? – OscarRyz

Respuesta

20

Puede dividir la cadena de forma recursiva con el palmo:

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

cola recursiva:

@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) 
    } 
} 
+0

¿Podría expresarse condicionalmente el condicional como una coincidencia? –

+0

@Paul - No, no lo creo. El partido tomaría más espacio, y un control contra el tamaño es bastante claro. @Thomas - Vea mi comentario a la respuesta de Martin Ring. Me gusta esta respuesta, pero quiero señalar los inconvenientes de este estilo de método. –

+0

@Paul Puede implementarlo como Martin lo ha hecho. De hecho, hice esto, pero no fue más legible. –

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

Edición: cola versión recursiva:

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) 
} 

puede ser utilizado como el otro debido a que el segundo parámetro tiene un valor predeterminado y por lo tanto no tiene que ser suministrado.

+0

@Rex Kerr ¿por qué sería 'O (n^2)'? Creo que hay 2 operaciones de comparación para cada personaje de la cuerda que es menos que óptima (mejor solución es usar 'span' como Thomas sugirió), ¿pero eso todavía es' O (n) 'o no? ¿Me estoy perdiendo de algo? –

+0

@Rex Diría que consumes todos los personajes una vez (de acuerdo, en realidad dos veces en esta implementación). Esto sería O (n). (Sí, podrías hacerlo recursivo por la cola. Este es un ejercicio, no es un problema.) –

+0

@Martin, @Thomas - Si cada personaje es diferente, generas 'n' cadenas de longitud promedio' n/2', para ' O (n^2) 'trabajo total. –

1

Se podía utilizar algunas funciones auxiliares como esto:

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) 

Esto debe hacer el truco, ¿verdad? No hay duda de que se puede limpiar en una sola línea aún más

+0

La partición no hace lo que se quiere, creo.Dado aaabbbbbaa devolverá aaaaa bbbbb –

+0

Partición divide la lista en dos, por lo que dividir "aaaabbbbaaa" .toList como este producirá "aaaa", "bbbbaaaaa" y luego la misma función se aplica para el resto de la lista de forma recursiva . Creo que hace lo que necesita – Anne

+0

La partición no divide la lista en un punto. Recopila todos los elementos que coinciden o no coinciden con el predicado 'scala>" aaabbbbaaa "partición (_ == 'a') res0: (String, String) = (aaaaaa, bbbb) scala>' –

4

Que sea de una sola línea:

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) 

ACTUALIZACIÓN:

Como se mencionó @ Pablo sobre el orden que aquí se actualiza la versión:

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

Creo que está claro por el ejemplo del OP que importa el orden de los segmentos. –

+0

@Paul La orden no fue mencionada por todos modos. Actualicé el código –

+0

, esto daría como resultado 'List (bb, aaa)' for '" aabba "' ... ¿no? No sé si eso es lo que quería djhworld. –

0

Editar: tiene que leer con más cuidado. A continuación no hay un código funcional.

A veces, un poco de estado mutable ayuda a:

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 
} 

Runtime O (n) probablemente (si los segmentos tienen como máximo constante de longitud) y tmp.+= crea la mayor parte de arriba. Utilice un generador de cadenas en lugar de estricto tiempo de ejecución en O (n).

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

Si hubo algún método en 'collection.mutable.Seq' que en realidad * modificó * la secuencia, puede usar una lista de doble enlace en tmp y estar en O (n) hora y memoria. – Raphael

16

Parece que todas las otras respuestas están muy concentradas en las operaciones de cobro.Pero pura cuerda + solución de expresiones regulares es mucho más simple:

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

En esta expresión regular que utilizan de búsqueda hacia atrás positiva y búsqueda negativa hacia delante para el carbón capturado

+2

+1 - ¡muy bonito! Siempre me olvido del increíble poder de las expresiones regulares. –

+7

Y la asombrosa capacidad de lectura. ¿Puedes decir que lo entiendes sin un estudio cuidadoso? –

+0

¿Es esto "funcional"? @Paul Eso es lo que los comentarios son para – Raphael

1

A * solución funcional utilizando 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 
    } 
    } 
} 

Hay un gran costo oculto en todas las operaciones de secuencia realizadas en cadenas (a través de la conversión implícita). Supongo que la complejidad real depende en gran medida del tipo de Seq en que las cadenas se convierten.

(*) Todas las operaciones de Afaik en la biblioteca de colecciones dependen de iteradores, un concepto intrínsecamente no funcional. Pero el código parece funcional, al menos.

Cuestiones relacionadas