Заполнение карты в цикле [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
}
Тем не менее, он также использует изменчивую карту.