Как сгладить вложенные для понимания, которые используют ввод / вывод? - PullRequest
2 голосов
/ 07 сентября 2011

У меня возникают проблемы при объединении вложенного For Generator в один For Generator.

Я создал MapSerializer , чтобы сохранить и загрузить Карты.

Список MapSerializer.scala :

import java.io.{ObjectInputStream, ObjectOutputStream}

object MapSerializer {
  def loadMap(in: ObjectInputStream): Map[String, IndexedSeq[Int]] =
    (for (_ <- 1 to in.readInt()) yield {
      val key = in.readUTF()
      for (_ <- 1 to in.readInt()) yield {
        val value = in.readInt()
        (key, value)
      }
    }).flatten.groupBy(_ _1).mapValues(_ map(_ _2))

  def saveMap(out: ObjectOutputStream, map: Map[String, Seq[Int]]) {
    out.writeInt(map size)
    for ((key, values) <- map) {
      out.writeUTF(key)
      out.writeInt(values size)
      values.foreach(out.writeInt(_))
    }
  }
}

Изменение loadMap для назначения ключа в генераторе приводит к его отказу:

def loadMap(in: ObjectInputStream): Map[String, IndexedSeq[Int]] =
  (for (_ <- 1 to in.readInt();
        key = in.readUTF()) yield {
    for (_ <- 1 to in.readInt()) yield {
      val value = in.readInt()
      (key, value)
    }
  }).flatten.groupBy(_ _1).mapValues(_ map(_ _2))

Вот трассировка стека, которую я получаю:

java.io.UTFDataFormatException
    at java.io.ObjectInputStream$BlockDataInputStream.readWholeUTFSpan(ObjectInputStream.java)
    at java.io.ObjectInputStream$BlockDataInputStream.readOpUTFSpan(ObjectInputStream.java)
    at java.io.ObjectInputStream$BlockDataInputStream.readWholeUTFSpan(ObjectInputStream.java)
    at java.io.ObjectInputStream$BlockDataInputStream.readUTFBody(ObjectInputStream.java)
    at java.io.ObjectInputStream$BlockDataInputStream.readUTF(ObjectInputStream.java:2819)
    at java.io.ObjectInputStream.readUTF(ObjectInputStream.java:1050)
    at MapSerializer$$anonfun$loadMap$1.apply(MapSerializer.scala:8)
    at MapSerializer$$anonfun$loadMap$1.apply(MapSerializer.scala:7)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:194)
    at scala.collection.immutable.Range.foreach(Range.scala:76)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:194)
    at scala.collection.immutable.Range.map(Range.scala:43)
    at MapSerializer$.loadMap(MapSerializer.scala:7)

Я хотел бы свести загрузочный код к одному для понимания, но я получаю ошибки, которые указывают на то, что он выполняется в другом порядке или повторяется, я не ожидаю, что он будет повторяться.

Почему перемещение назначенного ключа в генератор приводит к его отказу?

Могу ли я объединить это в один генератор? Если это так, что бы это был за генератор?

Ответы [ 2 ]

6 голосов
/ 07 сентября 2011

Спасибо за автономную компиляцию кода в вашем вопросе.Я не думаю, что вы хотите сгладить петли, поскольку структура не плоская.Затем вам нужно использовать groupBy для восстановления структуры.Также, если у вас есть «ноль -> Seq ()» в качестве элемента карты, он будет потерян.Использование этой простой карты позволяет избежать groupBy и сохранить элементы, сопоставленные с пустыми последовательностями:

def loadMap(in: ObjectInputStream): Map[String, IndexedSeq[Int]] = {
  val size = in.readInt
  (1 to size).map{ _ =>
    val key = in.readUTF
    val nval = in.readInt
    key -> (1 to nval).map(_ => in.readInt)
  }(collection.breakOut)
}

Я использую breakOut для генерации правильного типа, так как в противном случае я думаю, что компиляторы жалуются на универсальные Map инеизменное Map несоответствие.Вы также можете использовать Map() ++ (...).

Примечание. Я пришел к этому решению, запутавшись в цикле for и начав переписывать с использованием flatMap и map:

val tuples = (1 to size).flatMap{ _ =>
  val key = in.readUTF
  println("key " + key)
  val nval = in.readInt
  (1 to nval).map(_ => key -> in.readInt)
}

Я думаю, что вцикл for, что-то происходит, когда вы не используете какой-либо генератор.Я думаю, что это будет эквивалентно:

val tuples = for {
  _ <- 1 to size
  key = in.readUTF
  nval = in.readInt
  _ <- 1 to nval
  value = in.readInt
} yield { key -> value }

Но это не тот случай, поэтому я думаю, что что-то упустил в переводе.

Редактировать: выяснил, что не так с одним циклом for.Короткая история: перевод определений внутри циклов for заставил оператор key = in.readUTF вызываться последовательно перед выполнением внутреннего цикла.Чтобы обойти это, используйте view и force:

val tuples = (for {
  _ <- (1 to size).view
  key = in.readUTF
  nval = in.readInt
  _ <- 1 to nval
  value = in.readInt
} yield { key -> value }).force

Проблема может быть более четко продемонстрирована с помощью этого фрагмента кода:

val iter = Iterator.from(1)
val tuple = for {
  _ <- 1 to 3
  outer = iter.next
  _ <- 1 to 3
  inner = iter.next
} yield (outer, inner)

Возвращает Vector((1,4), (1,5), (1,6), (2,7), (2,8), (2,9), (3,10), (3,11), (3,12))который показывает, что все внешние значения оцениваются перед внутренними значениями.Это связано с тем, что он более или менее переводится в нечто вроде:

for { 
  (i, outer) <- for (i <- (1 to 3)) yield (i, iter.next)
  _ <- 1 to 3
 inner = iter.next
} yield (outer, inner)

Это сначала вычисляет все внешние iter.next.Возвращаясь к исходному сценарию использования, все значения in.readUTF будут вызываться последовательно до in.readInt.

1 голос
/ 07 сентября 2011

Вот сжатая версия ответа @ huynhjl, которую я в конечном итоге развернул:

def loadMap(in: ObjectInputStream): Map[String, IndexedSeq[Int]] =
  ((1 to in.readInt()) map { _ =>
    in.readUTF() -> ((1 to in.readInt()) map { _ => in.readInt()) }
  })(collection.breakOut)

Преимущество этой версии в том, что прямых назначений нет.

...