Scala: сравнивать элементы в одной позиции в двух массивах - PullRequest
1 голос
/ 29 апреля 2020

Я нахожусь в процессе обучения Scala и пытаюсь написать какую-то функцию, которая будет сравнивать один элемент в списке с элементом в другом списке с тем же индексом . Я знаю, что должен быть более Scalati c способ сделать это, чем два, написать два for цикла и отслеживать текущий index каждого вручную.

Скажем, мы сравнивая URL, например. Скажем, у нас есть следующие два List, которые являются URL, разделенными на символ /:

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

и

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

Скажите, что я хочу рассматривать любой элемент, который начинается с символа :, как совпадение, но любой элемент, который не начинается с :, не будет совпадением.

Что такое лучший и самый Scalati c способ сделать это сравнение?

Исходя из OOP в фоновом режиме, я бы сразу перепрыгнул на for l oop, но я знаю, что должен быть хороший способ FP к go об этом, который научит меня одной или двум вещам о Scala.

РЕДАКТИРОВАТЬ

Для завершения я обнаружил этот устаревший вопрос вскоре после публикации моего сообщения, связанного с проблемой.

РЕДАКТИРОВАТЬ 2 Реализация, которую я выбрал для этой спецификации c сценарий использования :

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 ]

2 голосов
/ 29 апреля 2020

Вы можете использовать zip, который вызывается на Seq[A] с Seq[B], в результате Seq[(A, B)]. Другими словами, он создает последовательность с кортежами с элементами обеих последовательностей:

incommingUrl.zip(urlToCompare).map { case(incomming, pattern) => f(incomming, pattern) }
0 голосов
/ 29 апреля 2020

Поскольку OP любопытно посмотреть, как мы будем использовать ленивые коллекции или пользовательские фолды для того же, я включил отдельный ответ в эти реализации.

Первая реализация использует ленивые коллекции. Обратите внимание, что у ленивых коллекций плохие свойства кэша, поэтому на практике часто не имеет смысла использовать ленивые коллекции в качестве микрооптимизации. Хотя отложенные коллекции будут минимизировать количество проходов данных, как уже упоминалось, базовая структура данных не имеет хорошей локализации кэша. Чтобы понять, почему отложенные коллекции минимизируют количество проходов, которые вы делаете над данными, прочитайте главу 5 Функциональное программирование в 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)
}

Во второй реализации используется пользовательское свертывание для go над списками только один раз. Поскольку мы добавляем в конец нашей структуры данных, мы хотим использовать 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))
}

Если у вас есть какие-либо вопросы, я могу ответить на них в разделе комментариев.

0 голосов
/ 29 апреля 2020

Уже есть другой ответ на вопрос, но я добавляю еще один, так как есть один угловой случай, который нужно остерегаться. Если вы не знаете длины двух списков, вам нужен 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)
}

Одна вещь, которая может вас беспокоить, заключается в том, что мы делаем несколько проходов через данные. Если это то, что вас беспокоит, вы можете использовать ленивые коллекции или написать собственную складку, которая делает только один проход по данным. Это, вероятно, излишне, хотя. Если кто-то хочет, он может добавить эти альтернативные реализации в другой ответ.

...