Используйте моноиды для вычисления частотной карты слов

Я изучаю книгу функционального программирования в Scala. глава 10, упражнение 20, для реализации следующего метода:

def frequencyMap(strings: IndexedSeq[String]): Map[String, Int]

честно говоря, у меня нет решения, поэтому я проверил ответ из GIT:

 def mapMergeMonoid[K, V](V: Monoid[V]): Monoid[Map[K, V]] =
new Monoid[Map[K, V]] {
  def zero = Map()
  def op(a: Map[K, V], b: Map[K, V]) =
    a.map {
      case (k, v) => (k, V.op(v, b.get(k) getOrElse V.zero))
    }
}

 def bag[A](as: IndexedSeq[A]): Map[A, Int] =
   foldMapV(as, mapMergeMonoid[A, Int](intAddition))((a: A) => Map(a -> 1))
 def frequencyMap(strings: IndexedSeq[String]): Map[String, Int] = bag(strings)

Но когда я попытался запустить тест, я получил неправильный ответ:

frequencyMap(Vector("a rose", "is a", "rose is", "a rose"))

ответ напечатан:

Map(a rose -> 1)

ожидаемый результат:

Map(a -> 3, rose -> 3, is -> 2)

Я не могу понять, где не так с реализацией. Может ли кто-нибудь объяснить это для меня? Благодарю.

Отредактируйте после правильного ответа и обновите с правильной реализацией

 def mapMergeMonoid[K, V](V: Monoid[V]): Monoid[Map[K, V]] =
new Monoid[Map[K, V]] {
  def zero = Map()
  def op(a: Map[K, V], b: Map[K, V]) = {
    //        (a.map {
    //          case (k, v) => (k, V.op(v, b.get(k) getOrElse V.zero))
    //
    val c = for {
      (ka, va) <- a
      (kb, vb) <- b
      if (ka == kb)
    } yield (ka -> V.op(va, vb))

    a ++ b ++ c
  }
}

def bag[A](as: IndexedSeq[A]): Map[A, Int] =
    foldMapV(as, mapMergeMonoid[A, Int](intAddition))((a: A) => Map(a -> 1))
def frequencyMap(strings: IndexedSeq[String]): Map[String, Int] = bag(strings.map(_.split("\\s+")).flatten)

1 ответ

Решение

У меня нет книги, но, следуя приведенному здесь коду, я могу указать на несколько вещей, о которых вы должны подумать (основываясь на некоторых println() в сеансе REPL):

  • Как указал SRI, у вас есть список фраз, а не слов. Функции на github ничего не делают для разделения групп слов для вас, поэтому в лучшем случае ваш текущий входной вектор может получить:

    Map(a rose -> 2, is a -> 1, rose is -> 1)
    

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

    Vector("a rose", "is a", "rose is", "a rose") map( _.split( " " ).toSeq ) flatten
    
  • mapMergeMonoid Функция появляется только для добавления значений k и v вместе, когда клавиши (k) совпадают. Значение несортированного списка приведет к большому количеству Map(string -> 1),

    Вы можете отсортировать Вектор слов, внеся следующие изменения в Вектор:

    (Vector("a rose", "is a", "rose is", "a rose") map( _.split( " " ).toSeq ) flatten) sortWith(_.compareTo(_) < 0) 
    
  • В то время как foldMapV вычленяет все фразы или слова из вектора в Map[String, Int], он только возвращает наиболее левое желаемое слияние для отсортированного IndexedSeq. С отсортированным вектором слов, как я указал, я получил результат Map(a -> 3) от вашей реализации frequencyMap, Я был бы склонен использовать что-то кроме foldMapV хотя или изменить его, чтобы сделать frequencyMap Работа. Бит, который мне кажется отсутствующим, - это скопление результатов функции, которую он применяет в одной большой карте. Я также попробовал бы более "стандартную" головную / хвостовую рекурсию, чем вызовы splitAt() (поскольку я не уверен, что foldMapV обрабатывал is а также rose отлично).

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