Способен ли генератор карт из онлайн-курса EPFL создать все возможные карты?

https://www.coursera.org/learn/progfun2 задание на 1-ю неделю показывает, в качестве примера, генератор карт типа Map[Int, Int]:

lazy val genMap: Gen[Map[Int,Int]] = oneOf(
  const(Map.empty[Int,Int]),
  for {
    k <- arbitrary[Int]
    v <- arbitrary[Int]
    m <- oneOf(const(Map.empty[Int,Int]), genMap)
  } yield m.updated(k, v)
)

Я новичок в Scala, но знаком с генераторами в императивных языках программирования. Мое понимание потока выполнения генератора выглядит следующим образом:

  1. arbitrary[Int] вызывается, он возвращает генератор, дающий бесконечную последовательность Ints, первое сгенерированное значение присваивается k
  2. arbitrary[Int] вызывается снова, он возвращает новый генератор, первое сгенерированное значение присваивается v
  3. Случайная карта создается рекурсивно, обновляется с помощью k->v и передается потребителю.
  4. Когда запрашивается следующее значение из генератора, выполнение возобновляется с определения m <- ..., начиная с нового случайного m и того же k->v сопоставления.
  5. Ни у const, ни у рекурсивного genMap никогда не заканчиваются значения, а это означает, что «цикл» для m никогда не заканчивается, поэтому новые значения для v и k никогда не запрашиваются у соответствующих генераторов arbitrary.

Мой вывод состоит в том, что все сгенерированные карты будут либо пустыми, либо включать отображение k->v, сгенерированное в первой итерации самого внешнего вызова, т. е. genMap никогда не сможет сгенерировать непустую карту без такого отображения.

Q1: верны ли мой анализ и мой вывод?

Q2: если да, то как я могу реализовать генератор, который после создания первой карты будет иметь ненулевой шанс создать любую возможную карту?

Q3: если я упрощу последнее определение в for-выражении до m <- genMap, это как-то изменит поведение генератора?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короче говоря, ваш анализ и вывод неверны.

Я подозреваю, что корень недоразумения заключается в интерпретации for как цикла (это не так в общем и в частности не так в этом контексте (при работе с вещами, которые более явно являются коллекциями, for достаточно близко, я думаю)).

Объясню сверху вниз.

oneOf, учитывая, что 1 или более генераторов создадут генератор, который, когда его попросят сгенерировать значение, будет зависеть от одного из заданных генераторов путем случайного выбора. Так

oneOf(
  const(Map.empty[Int, Int]),
  k: Gen[Map[Int, Int]]  // i.e. some generator for Map[Int, Int]
)

Выход может быть

someMapFromK, Map.empty, someMapFromK, someMapFromK, Map.empty, Map.empty...

В этом случае наш k

for {
  k <- arbitrary[Int]
  v <- arbitrary[Int]
  m <- oneOf(const(Map.empty[Int, Int]), genMap)  // genMap being the name the outermost generator will be bound to
} yield m.updated(k)

for — это синтаксический сахар для вызовов flatMap и map:

arbitrary[Int].flatMap { k =>
  arbitrary[Int].flatMap { v =>
    oneOf(const(Map.empty[Int, Int]), genMap).map { m =>
      m.updated(k, v)
    }
  }
}

Для чего-то вроде List, map и flatMap используется вся коллекция. Gen ленивее:

  • flatMap в основном означает создание значения и передача этого значения функции, которая приводит к Gen
  • map в основном означает генерировать значение и преобразовывать его

Если бы мы представили метод на Gen с именем sample, который дал бы нам «следующее» сгенерированное значение (для этой цели мы скажем, что для Gen[T] это приведет к T и никогда не выдаст исключение и т. д.), genMap в точности аналогичен :

trait SimpleGen[T] { def sample: T }

lazy val genMap: SimpleGen[Map[Int, Int]] = new SimpleGen[Map[Int, Int]] {
  def sample: Map[Int, Int] =
    if (scala.util.Random.nextBoolean) Map.empty
    else {
      val k = arbitrary[Int].sample
      val v = arbitrary[Int].sample
      val m =
        if (scala.util.Random.nextBoolean) Map.empty
        else genMap.sample // Since genMap is lazy, we can recurse
      m.updated(k, v)
    }
  }

Что касается третьего вопроса, в исходном определении дополнительный oneOf служит для ограничения глубины рекурсии, чтобы предотвратить взрыв стека. Для этого определения существует 1/4 шанса стать рекурсивным, в то время как замена внутреннего oneOf на genMap будет иметь 1/2 шанса стать рекурсивным. Таким образом (без учета вероятности столкновения в ks), для первого:

  • 50% шанс пустого (50% шанс 1+)
  • 37,5% вероятность размера 1 (12,5% вероятность 2+)
  • 9,375% вероятность размера 2 (3,125% вероятность 3+)
  • 2,34375 шанс размера 3 (0,78125% шанс 4+)...

Пока для второго:

  • 50% шанс пустого
  • 25% вероятность размера 1
  • 12,5% вероятность размера 2
  • 6,25% вероятность размера 3...

Технически возможность переполнения стека подразумевает, что в зависимости от того, сколько рекурсий вы можете сделать, максимальное количество k -> v пар в Map вы можете сгенерировать, поэтому почти наверняка есть Map, которые не могут быть сгенерированы.

Другие вопросы по теме