简体   繁体   English

Scala:比较两个 arrays 中相同 position 的元素

[英]Scala: Compare elements at same position in two arrays

I'm in the process of learning Scala and am trying to write some sort of function that will compare one element in an list against an element in another list at the same index .我正在学习 Scala 并尝试编写某种 function ,它将列表中的一个元素与另一个列表中相同索引的元素进行比较。 I know that there has to be a more Scalatic way to do this than two write two for loops and keep track of the current index of each manually.我知道必须有一种比两个写两个for循环并手动跟踪每个循环的当前index更 Scalatic 的方式来做到这一点。

Let's say that we're comparing URLs, for example.例如,假设我们正在比较 URL。 Say that we have the following two List s that are URLs split by the / character:假设我们有以下两个List ,它们是由/字符分割的 URL:

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

and

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

Say that I want to treat any element that begins with the : character as a match, but any element that does not begin with a : will not be a match.假设我想将任何以:字符开头的元素视为匹配项,但任何不以:开头的元素都不会成为匹配项。

What is the best and most Scalatic way to go about doing this comparison?关于进行此比较, go最佳和最可扩展的方法是什么?

Coming from a OOP background, I would immediately jump to a for loop, but I know that there has to be a good FP way to go about it that will teach me a thing or two about Scala.来自 OOP 背景,我会立即跳到for循环,但我知道必须有一个很好的 FP 方法来 go 关于它,这将教我一两件事关于 Z3012DCFF1477E1BBFEAAB81764587C。

EDIT编辑

For completion, I found this outdated question shortly after posting mine that relates to the problem.为了完成,我在发布与该问题相关的我的问题后不久发现了这个过时的问题。

EDIT 2编辑 2

The implementation that I chose for this specific use case :为这个特定用例选择的实现:

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
}

You can use zip , that invoked on Seq[A] with Seq[B] results in Seq[(A, B)] .您可以使用zip ,在Seq[A]上调用Seq[B]会导致Seq[(A, B)] In other words it creates a sequence with tuples with elements of both sequences:换句话说,它创建了一个包含两个序列元素的元组的序列:

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

There is already another answer to the question, but I am adding another one since there is one corner case to watch out for.这个问题已经有了另一个答案,但我要添加另一个答案,因为有一个角落案例需要注意。 If you don't know the lengths of the two Lists, you need zipAll.如果不知道两个Lists的长度,需要zipAll。 Since zipAll needs a default value to insert if no corresponding element exists in the List, I am first wrapping every element in a Some, and then performing the zipAll.由于如果列表中不存在相应的元素,则 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)
}

One thing that might bother you is that we are making several passes through the data.可能困扰您的一件事是我们正在对数据进行多次传递。 If that's something you are worried about, you can use lazy collections or else write a custom fold that makes only one pass over the data.如果这是您担心的事情,您可以使用惰性 collections 或者编写一个自定义折叠,只对数据进行一次传递。 That is probably overkill though.不过,这可能是矫枉过正。 If someone wants to, they can add those alternative implementations in another answer.如果有人愿意,他们可以在另一个答案中添加这些替代实现。

Since the OP is curious to see how we would use lazy collections or a custom fold to do the same thing, I have included a separate answer with those implementations.由于 OP 很想知道我们将如何使用惰性 collections 或自定义折叠来做同样的事情,因此我在这些实现中包含了一个单独的答案。

The first implementation uses lazy collections.第一个实现使用惰性 collections。 Note that lazy collections have poor cache properties so that in practice, it often does does not make sense to use lazy collections as a micro-optimization.请注意,惰性 collections 的缓存属性很差,因此在实践中,使用惰性 collections 作为微优化通常没有意义。 Although lazy collections will minimize the number of times you traverse the data, as has already been mentioned, the underlying data structure does not have good cache locality.虽然惰性 collections 会尽量减少遍历数据的次数,但正如已经提到的,底层数据结构没有很好的缓存局部性。 To understand why lazy collections minimize the number of passes you make over the data, read chapter 5 of Functional Programming in Scala .要了解为什么惰性 collections 最小化您对数据的传递次数,请阅读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)
}

The second implementation uses a custom fold to go over lists only one time.第二种实现只使用一次自定义折叠到 go 列表。 Since we are appending to the rear of our data structure, we want to use IndexedSeq, not List.由于我们要附加到数据结构的后面,所以我们想使用 IndexedSeq,而不是 List。 You should rarely be using List anyway.无论如何,您应该很少使用 List 。 Otherwise, if you are going to convert from List to IndexedSeq, you are actually making one additional pass over the data, in which case, you might as well not bother and just use the naive implementation I already wrote in the other answer.否则,如果您要从 List 转换为 IndexedSeq,您实际上是对数据进行了一次额外的传递,在这种情况下,您最好不要打扰,只需使用我已经在另一个答案中编写的幼稚实现。

Here is the custom fold.这是自定义折叠。

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

If you have any questions, I can answer them in the comments section.如果您有任何问题,我可以在评论部分回答。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM