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
}
1
foxtrotuniform6969 29 abr. 2020 a las 04:30

3 respuestas

La mejor respuesta

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) }
2
Andronicus 29 abr. 2020 a las 01:39

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.

0
Allen Han 29 abr. 2020 a las 02:40

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.

0
Allen Han 29 abr. 2020 a las 01:49