Scala: Почему SortedMap mapValues ​​возвращает карту, а не SortedMap?

Я новичок в Scala. Я использую SortedMap в своем коде, и я хотел использовать mapValues ​​для создания новой карты с некоторым преобразованием значений.

Вместо того, чтобы возвращать новый SortedMap, функция mapValues ​​возвращает новую карту, которую затем мне нужно преобразовать в SortedMap.

Например

val my_map = SortedMap(1 -> "one", 0 -> "zero", 2 -> "two")
val new_map = my_map.mapValues(name => name.toUpperCase)
// returns scala.collection.immutable.Map[Int,java.lang.String] = Map(0 -> ZERO, 1 -> ONE, 2 -> TWO)
val sorted_new_map = SortedMap(new_map.toArray:_ *)

Это выглядит неэффективно - последнее преобразование, вероятно, снова сортирует ключи или, по крайней мере, проверяет их сортировку.

Я мог бы использовать обычную функцию карты, которая работает как на клавишах, так и на значениях, и намеренно не меняет ключи в моей функции преобразования. Это также выглядит неэффективным, поскольку реализация Map, вероятно, предполагает, что преобразование может изменить порядок ключей (например, в случае: my_map.map(tup => (-tup._1, tup._2)) - поэтому он, вероятно, также "повторно сортирует" их.

Кто-нибудь знаком с внутренними реализациями Map и SortedMap и может сказать, правильно ли мои предположения? Может ли компилятор автоматически распознать, что ключи не были переупорядочены? Есть ли общая причина, почему mapValues ​​не должен возвращать SortedMap? Есть ли лучший способ преобразовать значения карты, не теряя порядок ключей?

Спасибо

Ответ 1

Вы наткнулись на сложную функцию реализации Scala Map. Улов, который вам не хватает, состоит в том, что mapValues фактически не возвращает новый Map: он возвращает view для Map. Другими словами, он обертывает вашу исходную карту таким образом, что всякий раз, когда вы обращаетесь к значению, он вычисляет .toUpperCase, прежде чем возвращать вам значение.

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

Итак, почему SortedMap не возвращает a SortedMap? Потому что он фактически возвращает Map -wrapper. Основополагающий Map, а затем тот, который завернут, по-прежнему является SortedMap, поэтому, если вы должны были перебирать, он все равно будет отсортирован в порядке. Мы с тобой это знаем, но у контролера нет. Кажется, что они могли написать его таким образом, что он все еще сохраняет черту SortedMap, но они этого не сделали.

В коде вы можете видеть, что он не возвращает SortedMap, но что итерационное поведение все еще будет сортироваться:

// from MapLike
override def mapValues[C](f: B => C): Map[A, C] = new DefaultMap[A, C] {
  def iterator = for ((k, v) <- self.iterator) yield (k, f(v))
  ...

Решение вашей проблемы такое же, как решение проблемы с просмотром: используйте .map{ case (k,v) => (k,f(v)) }, как вы упомянули в своем вопросе.


Если вы действительно хотите использовать этот метод удобства, вы можете делать то, что я делаю, и написать вам, лучше, версию mapValues:

class EnrichedWithMapVals[T, U, Repr <: GenTraversable[(T, U)]](self: GenTraversableLike[(T, U), Repr]) {
  /**
   * In a collection of pairs, map a function over the second item of each
   * pair.  Ensures that the map is computed at call-time, and not returned
   * as a view as 'Map.mapValues' would do.
   *
   * @param f   function to map over the second item of each pair
   * @return a collection of pairs
   */
  def mapVals[R, That](f: U => R)(implicit bf: CanBuildFrom[Repr, (T, R), That]) = {
    val b = bf(self.asInstanceOf[Repr])
    b.sizeHint(self.size)
    for ((k, v) <- self) b += k -> f(v)
    b.result
  }
}
implicit def enrichWithMapVals[T, U, Repr <: GenTraversable[(T, U)]](self: GenTraversableLike[(T, U), Repr]): EnrichedWithMapVals[T, U, Repr] =
  new EnrichedWithMapVals(self)

Теперь, когда вы вызываете mapVals на SortedMap, вы возвращаете не-просмотр SortedMap:

scala> val m3 = m1.mapVals(_ + 1)
m3: SortedMap[String,Int] = Map(aardvark -> 2, cow -> 6, dog -> 10)

Он фактически работает с любым набором пар, а не только с реализациями Map:

scala> List(('a,1),('b,2),('c,3)).mapVals(_+1)
res8: List[(Symbol, Int)] = List(('a,2), ('b,3), ('c,4))