Заполнение карты в цикле [Scala]

Боюсь, это еще один нубский вопрос.

Что я хочу сделать, это использовать Map чтобы посчитать, как часто слово появляется в поэме…m, а затем вывести результаты на консоль. Я перешел к следующему коду, который, как мне кажется, работает (хотя, вероятно, не совсем идиоматический):

val poe_m="""Once upon a midnight dreary, while I pondered weak and weary,
            |Over many a quaint and curious volume of forgotten lore,
            |While I nodded, nearly napping, suddenly there came a tapping,
            |As of some one gently rapping, rapping at my chamber door.
            |`'Tis some visitor,' I muttered, `tapping at my chamber door -
            |Only this, and nothing more.'"""

val separators=Array(' ',',','.','-','\n','\'','`')
var words=new collection.immutable.HashMap[String,Int]
for(word<-poe_m.stripMargin.split(separators) if(!word.isEmpty))  
    words=words+(word.toLowerCase -> (words.getOrElse(word.toLowerCase,0)+1))

words.foreach(entry=>println("Word : "+entry._1+" count : "+entry._2))

Насколько я понимаю, в Scala неизменяемые структуры данных предпочтительнее изменяемых и val предпочтительнее varпоэтому я сталкиваюсь с дилеммой words должен быть var (позволяя использовать новый экземпляр карты для каждой итерации), если результаты должны храниться в неизменяемом Map поворачивая words в val подразумевает использование изменяемого Map,

Может ли кто-нибудь рассказать мне о правильном способе решения этой экзистенциальной проблемы?

5 ответов

Решение

В этом случае вы можете использовать groupBy а также mapValues:

val tokens = poe_m.stripMargin.split(separators).filterNot(_.isEmpty)
val words = tokens.groupBy(w => w).mapValues(_.size)

В более общем смысле это работа для фолда:

 val words = tokens.foldLeft(Map.empty[String, Int]) {
   case (m, t) => m.updated(t, m.getOrElse(t, 0) + 1)
 }

В Википедии есть несколько хороших поясняющих примеров.

Я тоже новичок со Scala, так что, возможно, есть лучшие способы сделать это. Я придумал следующее:

poe_m.stripMargin.split(separators)
     .filter(x => !x.isEmpty)
     .groupBy(x => x).foreach {
        case(w,ws) => println(w + " " + ws.size)
     }

Применяя последовательные функции, вы избавляетесь от необходимости использовать переменные и переменные

Что ж, в функциональном программировании предпочтительнее использовать некоторые неизменяемые объекты и использовать функции для их обновления (например, хвостовая рекурсивная функция, возвращающая обновленную карту). Однако, если вы не имеете дело с большими нагрузками, вы должны предпочесть изменяемую карту использованию var, не потому, что она более мощная (даже если я думаю, что это должно быть), а потому, что ее проще использовать.

Наконец, ответ Трэвиса Брауна - это решение вашей конкретной проблемы, мое - скорее личная философия.

Кредит лежит в другом месте (в частности, Трэвис и Дэниел) за то, что следует, но был более простой лайнер, который должен был выбраться.

val words = poe_m split "\\W+" groupBy identity mapValues {_.size}

Есть упрощение в том, что вам не понадобится stripMargin, потому что регулярное выражение, как предлагает Дэниел, также избавляется от символов поля.

Вы можете сохранить фильтрацию _.isEmpty для защиты от крайнего случая для пустой строки, которая выдает ("" -> 1), если хотите.

Вот как это делается в очень хорошей книге Мартина Одерски "Программирование в Scala: подробное пошаговое руководство, 2-е издание":

def countWords(text: String) = {
  val counts = mutable.Map.empty[String, Int]
  for (rawWord <- text.split("[ ,!.]+")) {
    val word = rawWord.toLowerCase
    val oldCount = 
      if (counts.contains(word)) counts(word)
      else 0
    counts += (word -> (oldCount + 1))
  }
  counts
}

Тем не менее, он также использует изменчивую карту.

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