Estoy en el proceso de aprender Scala y estoy tratando de escribir algún tipo de función que compare un elemento de una lista con un elemento de otra lista en el mismo índice . Sé que tiene que haber una forma más escalatica de hacerlo que dos escribir dos for
bucles y realizar un seguimiento de la index
actual de cada uno manualmente.
Digamos que estamos comparando URL, por ejemplo. Digamos que tenemos los siguientes dos List
s que son URL divididas por el carácter /
:
val incommingUrl = List("users", "profile", "12345")
Y
val urlToCompare = List("users", "profile", ":id")
Digamos que quiero tratar cualquier elemento que comience con el carácter :
como una coincidencia, pero cualquier elemento que no comience con un :
no será una coincidencia.
¿Cuál es la mejor y la forma más escalatica de hacer esta comparación?
Viniendo de un fondo de OOP, saltaría inmediatamente a un bucle for
, pero sé que tiene que haber una buena forma de FP que me enseñe una o dos cosas sobre Scala.
EDITAR
Para completar, encontré esta pregunta obsoleta poco después publicar el mío que se relaciona con el problema.
EDITAR 2 La implementación que elegí para este caso de uso específico :
def doRoutesMatch(incommingURL: List[String], urlToCompare: List[String]): Boolean = {
// if the lengths don't match, return immediately
if (incommingURL.length != urlToCompare.length) return false
// merge the lists into a tuple
urlToCompare.zip(incommingURL)
// iterate over it
.foreach {
// get each path
case (existingPath, pathToCompare) =>
if (
// check if this is some value supplied to the url, such as `:id`
existingPath(0) != ':' &&
// if this isn't a placeholder for a value that the route needs, then check if the strings are equal
p2 != p1
)
// if neither matches, it doesn't match the existing route
return false
}
// return true if a `false` didn't get returned in the above foreach loop
true
}
3 respuestas
Puede usar zip
, que se invoca en Seq[A]
con Seq[B]
da como resultado Seq[(A, B)]
. En otras palabras, crea una secuencia con tuplas con elementos de ambas secuencias:
incommingUrl.zip(urlToCompare).map { case(incomming, pattern) => f(incomming, pattern) }
Dado que el OP tiene curiosidad por ver cómo usaríamos colecciones perezosas o un pliegue personalizado para hacer lo mismo, he incluido una respuesta separada con esas implementaciones.
La primera implementación usa colecciones perezosas. Tenga en cuenta que las colecciones diferidas tienen propiedades de caché deficientes, por lo que, en la práctica, a menudo no tiene sentido utilizar colecciones diferidas como una microoptimización. Aunque las colecciones diferidas minimizarán la cantidad de veces que recorre los datos, como ya se mencionó, la estructura de datos subyacente no tiene una buena ubicación de caché. Para comprender por qué las colecciones diferidas minimizan la cantidad de pases que realiza sobre los datos, lea el capítulo 5 de Programación funcional en Scala .
object LazyZipTest extends App{
val incomingUrl = List("users", "profile", "12345", "extra").view
val urlToCompare = List("users", "profile", ":id").view
val list1 = incomingUrl.map(Some(_))
val list2 = urlToCompare.map(Some(_))
val zipped = list1.zipAll(list2, None, None)
println(zipped)
}
La segunda implementación utiliza un pliegue personalizado para revisar las listas solo una vez. Como estamos agregando a la parte posterior de nuestra estructura de datos, queremos usar IndexedSeq, no List. Rara vez debería usar List de todos modos. De lo contrario, si va a convertir de List a IndexedSeq, en realidad está haciendo un pase adicional sobre los datos, en cuyo caso, es mejor que no se moleste y simplemente use la implementación ingenua que ya escribí en la otra respuesta.
Aquí está el pliegue personalizado.
object FoldTest extends App{
val incomingUrl = List("users", "profile", "12345", "extra").toIndexedSeq
val urlToCompare = List("users", "profile", ":id").toIndexedSeq
def onePassZip[T, U](l1: IndexedSeq[T], l2: IndexedSeq[U]): IndexedSeq[(Option[T], Option[U])] = {
val folded = l1.foldLeft((l2, IndexedSeq[(Option[T], Option[U])]())) { (acc, e) =>
acc._1 match {
case x +: xs => (xs, acc._2 :+ (Some(e), Some(x)))
case IndexedSeq() => (IndexedSeq(), acc._2 :+ (Some(e), None))
}
}
folded._2 ++ folded._1.map(x => (None, Some(x)))
}
println(onePassZip(incomingUrl, urlToCompare))
println(onePassZip(urlToCompare, incomingUrl))
}
Si tiene alguna pregunta, puedo responderla en la sección de comentarios.
Ya hay otra respuesta a la pregunta, pero estoy agregando otra ya que hay un caso de esquina para tener en cuenta. Si no conoce las longitudes de las dos listas, necesita zipAll. Dado que zipAll necesita un valor predeterminado para insertar si no existe un elemento correspondiente en la Lista, primero estoy envolviendo cada elemento en un Some y luego realizando el zipAll.
object ZipAllTest extends App {
val incomingUrl = List("users", "profile", "12345", "extra")
val urlToCompare = List("users", "profile", ":id")
val list1 = incomingUrl.map(Some(_))
val list2 = urlToCompare.map(Some(_))
val zipped = list1.zipAll(list2, None, None)
println(zipped)
}
Una cosa que podría molestarlo es que estamos haciendo varios pases a través de los datos. Si eso es algo que le preocupa, puede usar colecciones perezosas o escribir un pliegue personalizado que solo pase una vez por los datos. Sin embargo, eso probablemente sea exagerado. Si alguien quiere, puede agregar esas implementaciones alternativas en otra respuesta.
Preguntas relacionadas
Nuevas preguntas
scala
Scala es un lenguaje de programación de propósito general dirigido principalmente a la máquina virtual Java. Diseñado para expresar patrones de programación comunes de una manera concisa, elegante y segura de tipos, fusiona estilos de programación imperativos y funcionales. Sus características clave son: un sistema de tipo estático avanzado con inferencia de tipo; tipos de funciones; la coincidencia de patrones; parámetros implícitos y conversiones; sobrecarga del operador; interoperabilidad total con Java; concurrencia