Почему цикл for с yield накапливается в карте, а не в списке? - PullRequest
1 голос
/ 11 июля 2019

У меня есть следующий код:

val dummy = Map(1 -> Map(2 -> 3.0,
                         4 -> 5.0),
                6 -> Map(7 -> 8.0))

val thisIsList = for (x <- dummy; y <- x._2.keys) yield s"(${x._1}, ${y})"
println(thisIsList)  // List((1, 2), (1, 4), (6, 7))

val thisIsMap = for (x <- dummy; y <- x._2.keys) yield new Tuple2(x._1, y)
println(thisIsMap)   // Map(1 -> 4, 6 -> 7) - this is not what I want    

Я ожидал бы, что второе выражение выдаст список кортежей, но вместо этого он возвращает Map.Я нашел объяснение здесь scala: выдайте последовательность кортежей вместо map о том, почему возвращается карта, но я все еще пытаюсь найти элегантный способ вернуть список кортежей в этом случае.

Ответы [ 3 ]

7 голосов
/ 11 июля 2019

Это связано с тем, как синтаксис понимания for преобразуется компилятором в серию вызовов методов.map, flatMap и withFilter предназначены для перестановок for пониманий.Это очень мощный и общий способ, поскольку он позволяет синтаксису работать с произвольными типами.Есть еще кое-что, например, неявное CanBuildFrom, но, по сути, отображение Map в Iterable[Tuple[A, B]] дает Map[A, B].scala.collection.IterableOnce[(K2,V2)]):CC[K2,V2]" rel="nofollow noreferrer"> Сигнатура фактически перегружена для Map, чтобы обеспечить такое поведение

Перевод выглядит примерно так

val thisIsMap1 = dummy.flatMap { x =>
  x._2.keys.map { y =>
    (x._1, y)
  }
}

Смотрите это fiddle

Чтобы получить требуемый список, мы можем написать

val thisIsMap = (for (x <- dummy; y <- x._2.keys) yield (x._1, y)).toList

Однако, если мы рассмотрим то, что мы узнали о for пониманиях, мы можем написать его более элегантно как

val thisIsMap = for (x <- dummy.toList; y <- x._2.keys) yield (x._1, y)

В вышеизложенном мы использовали само поведение, которое запутало исходный код, сделав вывод, что понимание for над List приведет к List.

Однако , обратите внимание на разницу между преобразованием источника в List и преобразованием полученной карты в List после понимания.

Если мыпозвонив toList по источнику (dummy), мы получим List((1,2), (1,4), (6,7)), тогда как, если мы вызовем его по результату, мы получим List((1,4), (6,7)), по понятным причинам, поэтому выбирайте осторожно и осознанно.

2 голосов
/ 11 июля 2019

Попробуйте

dummy
  .view
  .mapValues(_.keys.toList)
  .flatMap { case (key: Int, values: List[Int]) => values.map((key, _)) }
  .toList

, который выводит

res0: List[(Int, Int)] = List((1,2), (1,4), (6,7)
0 голосов
/ 16 июля 2019

После проработки ответов опубликует сводку TLDR на свой вопрос здесь.

Ожидается, что тип структуры данных, возвращаемой обратно циклом понимания для , будет таким же, как и тип, который цикл для начинает перебирать. То есть если он начинает перебирать Map - ожидайте, что Map будет конечным результатом.

val thisIsList = for (x <- dummy; y <- x._2.keys) yield s"(${x._1}, ${y})"
println(thisIsList)  // List((1, 2), (1, 4), (6, 7))

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

val thisIsMap = for (x <- dummy; y <- x._2.keys) yield new Tuple2(x._1, y)
println(thisIsMap)   // Map(1 -> 4, 6 -> 7) - this is not what I want 

В этом примере, однако, все происходит так, как должно, но поскольку полученная карта не может иметь дублирующихся ключей, кортеж (1,2) перезаписывается кортежем (1,4). То есть карта содержит только 2 элемента.

...