【问题标题】:Scala: Compare elements at same position in two arraysScala:比较两个数组中相同位置的元素
【发布时间】:2020-08-13 00:10:02
【问题描述】:

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

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

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

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

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

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

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

编辑

为了完成,我在发布与该问题相关的我的帖子后不久找到了this outdated question

编辑 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
}

【问题讨论】:

  • 嗨,我只是想让你知道你的实现是有效的,但我更愿意使用 forall 而不是 foreach 来编写检查。这将是一种更 Scala 的方式,无需使用显式的 return 语句。此外,您必须小心,因为如果字符串是空字符串,调用 existingPath(0) 可能会引发异常。 Scala 处理异常的方式是使用 Try monad。

标签: scala functional-programming scala-collections


【解决方案1】:

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

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

【讨论】:

  • 哇,真快!谢谢! 在我更改搜索词之后你就给出了答案哈哈。我知道zipped 已被弃用,这导致我有两个后续问题:1.(t, p).zipped.foreach((p1, p2) => ...)incommingUrl.zip(urlToCompare).map 有什么区别? 2.如何使用xs.lazyZip(ys)zipped的替代品)?
  • 另一方面,您的答案中的pattern 是什么?是regex 模式吗?
  • @foxtrotuniform6969 zipped 创建 Tuple2Zipped。延迟压缩具有相同的结果,但它是按需评估的。 incomming, pattern 构成一个迭代器,其中incommingincommingUrl 的元素,patternurlToCompare 的对应元素
【解决方案2】:

这个问题已经有了另一个答案,但我要添加另一个答案,因为有一个角落案例需要注意。如果不知道两个Lists的长度,需要zipAll。由于 zipAll 需要在 List 中不存在对应元素的情况下插入默认值,因此我首先将每个元素包装在 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)
}

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

【讨论】:

  • 感谢您的回答!在这个特定的用例中,比较每个列表的lengths 可能是验证 URL 是否匹配的第一步。显然,如果这两个列表的长度不同,您不希望某些路由器实现继续比较它们。但是,考虑到用例是一个路由器,其中 实际 处理尚未开始,您能幽默一下并使用惰性集合或自定义折叠添加这样的实现吗?我希望这个问题能帮助以后遇到不同情况的其他人。
  • 既然你很好奇,我将把另外两个解决方案放在另一个解决方案中。
【解决方案3】:

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

如果您有任何问题,我可以在 cmets 部分回答。

【讨论】:

  • 感谢您提供此示例!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多