2013-03-18 11 views
8

Próbuję kodować w najprostszy sposób program do liczenia wystąpień słów w pliku w języku Scala. Do tej pory mam ten kawałek kodu:Początkujący Scala - najprostszy sposób zliczania słów w pliku

import scala.io.Codec.string2codec 
import scala.io.Source 
import scala.reflect.io.File 

object WordCounter { 
    val SrcDestination: String = ".." + File.separator + "file.txt" 
    val Word = "\\b([A-Za-z\\-])+\\b".r 

    def main(args: Array[String]): Unit = { 

     val counter = Source.fromFile(SrcDestination)("UTF-8") 
       .getLines 
       .map(l => Word.findAllIn(l.toLowerCase()).toSeq) 
       .toStream 
       .groupBy(identity) 
       .mapValues(_.length) 

     println(counter) 
    } 
} 

Nie przejmuj się wyrażeniem wyrażeń regularnych. Chciałbym wiedzieć, jak wyodrębnić pojedyncze słowa z kolejności pobierane w tym wierszu:

map(l => Word.findAllIn(l.toLowerCase()).toSeq) 

w celu uzyskania każdego occurency słowo policzone. Obecnie otrzymuję mapę z sekwencjami zliczanych słów.

Odpowiedz

27

Możesz zamienić wiersze pliku na słowa, dzieląc je za pomocą wyrażeń "\\W+" (flatmap jest leniwy, więc nie musi załadować całego pliku do pamięci). Aby policzyć wystąpień można złożyć ponad Map[String, Int] aktualizowania go z każdego słowa (znacznie bardziej wydajne pamięci i czasu niż przy użyciu groupBy)

scala.io.Source.fromFile("file.txt") 
    .getLines 
    .flatMap(_.split("\\W+")) 
    .foldLeft(Map.empty[String, Int]){ 
    (count, word) => count + (word -> (count.getOrElse(word, 0) + 1)) 
    } 
+1

Jestem bardzo nowe do Scala, więc mam trochę problemów ze zrozumieniem anonimową funkcję przejść do foldLeft. Skąd wiadomo, aby odwrócić kolejność krotki, którą przekazujesz (count, word)? –

+3

Liczba jest faktycznie mapą [String, Int], a słowo jest łańcuchem. –

+3

Tak, teraz rozumiem, pierwszym elementem krotki jest akumulator, a drugim elementem jest element foldLeft, który się iteruje. –

1

nie jestem w 100% pewien, o co prosisz, ale myślę, że widzę problem. Spróbuj użyć flatMap zamiast map:

flatMap(l => Word.findAllIn(l.toLowerCase()).toSeq) 

To będzie łączyć wszystkich sekwencji ze sobą tak, że groupBy odbywa się na poszczególnych słów zamiast na poziomie linii.


Uwaga na temat swojej Regex

wiem, że nie mówi się martwić o swój Regex, ale oto kilka zmian można zrobić, aby uczynić go trochę bardziej czytelne. Oto, co masz teraz:

val Word = "\\b([A-Za-z\\-])+\\b".r 

Po pierwsze, można użyć potrójnych cudzysłowów Scala więc nie trzeba uciekać się swoimi backslashy:

val Word = """\b([A-Za-z\-])+\b""".r 

drugie, jeśli umieścić - w zaczynają swojej klasy postaci, wtedy nie trzeba go uniknąć:

val Word = """\b([-A-Za-z])+\b""".r 
1

Oto co zrobiłem. To rozwali plik. Hashmap to dobry sposób na wysoką wydajność i przewyższa wszelkie rodzaje. Istnieje również bardziej zwięzła funkcja sortowania i wycinania, która również można obejrzeć.

import java.io.FileNotFoundException 

/**. 
* Cohesive static method object for file handling. 
*/ 
object WordCountFileHandler { 

    val FILE_FORMAT = "utf-8" 

    /** 
    * Take input from file. Split on spaces. 
    * @param fileLocationAndName string location of file 
    * @return option of string iterator 
    */ 
    def apply (fileLocationAndName: String) : Option[Iterator[String]] = { 
    apply (fileLocationAndName, " ") 
    } 

    /** 
    * Split on separator parameter. 
    * Speculative generality :P 
    * @param fileLocationAndName string location of file 
    * @param wordSeperator split on this string 
    * @return 
    */ 
    def apply (fileLocationAndName: String, wordSeperator: String): Option[Iterator[String]] = { 
    try{ 
     val words = scala.io.Source.fromFile(fileLocationAndName).getLines() //scala io.Source is a bit hackey. No need to close file. 

     //Get rid of anything funky... need the double space removal for files like the README.md... 
     val wordList = words.reduceLeft(_ + wordSeperator + _).replaceAll("[^a-zA-Z\\s]", "").replaceAll(" ", "").split(wordSeperator) 
     //wordList.foreach(println(_)) 
     wordList.length match { 
     case 0 => return None 
     case _ => return Some(wordList.toIterator) 
     } 
    } catch { 
     case _:FileNotFoundException => println("file not found: " + fileLocationAndName); return None 
     case e:Exception => println("Unknown exception occurred during file handling: \n\n" + e.getStackTrace); return None 
    } 
    } 
} 

import collection.mutable 

/** 
* Static method object. 
* Takes a processed map and spits out the needed info 
* While a small performance hit is made in not doing this during the word list analysis, 
* this does demonstrate cohesion and open/closed much better. 
* author: jason goodwin 
*/ 
object WordMapAnalyzer { 

    /** 
    * get input size 
    * @param input 
    * @return 
    */ 
    def getNumberOfWords(input: mutable.Map[String, Int]): Int = { 
    input.size 
    } 

    /** 
    * Should be fairly logarithmic given merge sort performance is generally about O(6nlog2n + 6n). 
    * See below for more performant method. 
    * @param input 
    * @return 
    */ 

    def getTopCWordsDeclarative(input: mutable.HashMap[String, Int], c: Int): Map[String, Int] = { 
    val sortedInput = input.toList.sortWith(_._2 > _._2) 
    sortedInput.take(c).toMap 
    } 

    /** 
    * Imperative style is used here for much better performance relative to the above. 
    * Growth can be reasoned at linear growth on random input. 
    * Probably upper bounded around O(3n + nc) in worst case (ie a sorted input from small to high). 
    * @param input 
    * @param c 
    * @return 
    */ 
    def getTopCWordsImperative(input: mutable.Map[String, Int], c: Int): mutable.Map[String, Int] = { 
    var bottomElement: (String, Int) = ("", 0) 
    val topList = mutable.HashMap[String, Int]() 

    for (x <- input) { 
     if (x._2 >= bottomElement._2 && topList.size == c){ 
     topList -= (bottomElement._1) 
     topList +=((x._1, x._2)) 
     bottomElement = topList.toList.minBy(_._2) 
     } else if (topList.size < c){ 
     topList +=((x._1, x._2)) 
     bottomElement = topList.toList.minBy(_._2) 
     } 
    } 
    //println("Size: " + topList.size) 

    topList.asInstanceOf[mutable.Map[String, Int]] 
    } 
} 

object WordMapCountCalculator { 

    /** 
    * Take a list and return a map keyed by words with a count as the value. 
    * @param wordList List[String] to be analysed 
    * @return HashMap[String, Int] with word as key and count as pair. 
    * */ 

    def apply (wordList: Iterator[String]): mutable.Map[String, Int] = { 
    wordList.foldLeft(new mutable.HashMap[String, Int])((word, count) => { 
     word get(count) match{ 
     case Some(x) => word += (count -> (x+1)) //if in map already, increment count 
     case None => word += (count -> 1)   //otherwise, set to 1 
     } 
    }).asInstanceOf[mutable.Map[String, Int]] 
} 
12

Myślę, że po to nieco łatwiejsze do zrozumienia:

Source.fromFile("file.txt"). 
    getLines(). 
    flatMap(_.split("\\W+")). 
    toList. 
    groupBy((word: String) => word). 
    mapValues(_.length) 
+4

FWIW, myślę, że mógłbyś zastąpić '(word: String) => word' z' identity'. – metasim

+3

Zachowałoby to całą zawartość pliku w pamięci, podczas gdy zaakceptowane odpowiedzi nie. – benroth

Powiązane problemy