Почему цикл for с yield накапливается в карте, а не в списке?

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

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    

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


person VSh    schedule 10.07.2019    source источник


Ответы (3)


Это связано с тем, как синтаксис понимания for трансформируется компилятором в серию вызовов методов. map, flatMap и withFilter нацелены на перестановки for понятий. Это очень мощная и общая функция, поскольку она позволяет синтаксису работать с произвольными типами. Есть еще кое-что, например, неявное CanBuildFrom, но, по сути, сопоставление Map с Iterable[Tuple[A, B]] дает Map[A, B]. Подпись фактически перегружена для Map, чтобы обеспечить такое поведение

В частности, учитывая исходный код ниже

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

Перевод примерно такой

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

См. эту скрипту

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

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)) по очевидным причинам, поэтому выбирайте тщательно и обдуманно.

person Aluan Haddad    schedule 10.07.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)
person Mario Galic    schedule 10.07.2019

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

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

В этом примере из вопроса цикл for начинает перебирать карту, но возвращает список. Это происходит потому, что yield не возвращает тип, который можно преобразовать в карту. Но его можно преобразовать в список, поэтому 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 элемента.

person VSh    schedule 16.07.2019