Scala:比较两个数组中相同位置的元素

Scala: Compare elements at same position in two arrays

我正在学习 Scala,正在尝试编写某种函数,将列表中的一个元素与另一个列表中的元素进行比较在同一索引.我知道必须有比两个写两个 for 循环并手动跟踪每个循环的当前 index 更 Scalic 的方法来做到这一点。

例如,假设我们正在比较 URL。假设我们有以下两个 Lists,它们是由 / 字符分割的 URL:

val incomingUrl = List("users", "profile", "12345")

val urlToCompare = List("users", "profile", ":id")

假设我想将任何以 : 字符开头的元素视为匹配项,但任何不以 : 字符开头的元素都不是匹配项。

进行此比较的最佳 和最 Scalatic 方法是什么?

来自 OOP 背景,我会立即跳到 for 循环,但我知道必须有一个好的 FP 方法来解决它,这将教会我一两件关于 Scala 的事情.

编辑

为了完成,我发现 在发布与该问题相关的我的帖子后不久。

编辑 2

我为这个特定用例选择的实现

def doRoutesMatch(incomingURL: List[String], urlToCompare: List[String]): Boolean = {
    // if the lengths don't match, return immediately
    if (incomingURL.length != urlToCompare.length) return false

    // merge the lists into a tuple
    urlToCompare.zip(incomingURL)
      // 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
}

您可以使用 zip,在 Seq[A] 上调用 Seq[B] 结果是 Seq[(A, B)]。换句话说,它创建了一个包含两个序列元素的元组的序列:

incomingUrl.zip(urlToCompare).map { case(incoming, pattern) => f(incoming, pattern) }

这个问题已经有另一个答案,但我要添加另一个答案,因为有一个极端情况需要注意。如果你不知道两个List的长度,你需要zipAll。如果 List 中不存在相应的元素,zipAll 需要插入一个默认值,因此我首先将每个元素包装在一个 Some 中,然后执行 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)
}

可能困扰您的一件事是我们要对数据进行多次传递。如果这是您担心的事情,您可以使用惰性集合或编写一个只传递一次数据的自定义折叠。不过,这可能有点矫枉过正。如果有人愿意,他们可以在另一个答案中添加这些替代实现。

由于 OP 很想知道我们如何使用惰性集合或自定义折叠来做同样的事情,我在这些实现中包含了一个单独的答案。

第一个实现使用惰性集合。请注意,惰性集合的缓存属性很差,因此在实践中,将惰性集合用作微优化通常没有意义。尽管惰性集合会最大限度地减少遍历数据的次数,但正如已经提到的那样,底层数据结构没有良好的缓存局部性。要了解为什么惰性集合会最大限度地减少您对数据进行的传递次数,请阅读 Scala 中的函数式编程.

的第 5 章
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)
}

第二种实现使用自定义折叠仅遍历列表一次。由于我们要追加到数据结构的尾部,所以我们要使用 IndexedSeq,而不是 List。无论如何,您应该很少使用 List。否则,如果您要从 List 转换为 IndexedSeq,您实际上是在对数据进行一次额外的传递,在这种情况下,您最好不要打扰,只需使用我已经在另一个答案中写过的简单实现。

这是自定义折叠。

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

大家有什么问题,我可以在评论区一一解答。