Предисловие
Это 8 часть руководств для начинающих программистов, попадающих в Scala. Другие посты находятся в этом блоге, и вы можете получить ссылки на эти и другие ресурсы на странице ссылок курса по компьютерной лингвистике, для которого я их создаю. Кроме того, вы можете найти эту и другие серии учебников на странице JCG Java Tutorials .
Это руководство о доступе к файловой системе для работы с текстовыми файлами. Предыдущий учебник показал, как построить карту, которая содержит количество слов каждого типа в данном тексте. Однако предполагалось, что текст был доступен в переменной String, и, как правило, нам интересно знать о файлах, которые существуют в файловой системе или в Интернете. Из этого туториала вы узнаете, как прочитать содержимое файла в Scala для обработки, либо создав одну строку для файла, либо используя его построчно в потоковом режиме. Попутно неизменные Карты представлены как способ включить подсчет слов без чтения всего файла в память.
Подсчет слов по содержимому файла
В качестве примера мы будем использовать полный Шерлок Холмс из проекта Гутенберг . Загрузите его, поместите в каталог, а затем запустите Scala REPL в этом каталоге. Для доступа к файлам мы будем использовать класс Source , поэтому для начала вам нужно его импортировать.
1
2
|
scala> import scala.io.Source import scala.io.Source |
Source предоставляет несколько способов взаимодействия с файлами и делает их доступными для вас в вашей программе Scala. Вероятно, вам понадобится метод fromFile .
1
2
|
scala> Source.fromFile( "pg1661.txt" ) res 3 : scala.io.BufferedSource = non-empty iterator |
Это создает BufferedSource , из которого вы можете легко получить все содержимое файла в виде строки.
1
2
3
4
5
6
7
8
9
|
scala> val holmes = Source.fromFile( "pg1661.txt" ).mkString holmes : String = "Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online at www.gutenberg.net <...many more lines...> |
При этом вы можете сделать то же самое, что показано в уроке 7, чтобы получить количество слов (за исключением того, что здесь мы будем разбивать последовательности пробелов, а не только один пробел).
1
2
3
4
5
6
7
8
|
scala> val counts = holmes.split( "\\s+" ).groupBy(x = >x).mapValues(x = >x.length) counts : scala.collection.immutable.Map[java.lang.String,Int] = Map(wood-work, -> 1 , "Pray, -> 1, herself. -> 2, stern-post -> 1, " Should -> 1 , incident -> 8 , serious -> 14 , earth-- " -> 2, sinister -> 10, comply -> 7, breaks -> 1, forgotten -> 3, precious -> 10, 'It -> 3, compliment -> 2, suite, -> 1, " DEAR -> 1 , summarise. -> 1 , "Done -> 1, fine.' -> 1, lover -> 5, of. -> 2, lead. -> 1, plentiful -> 1, 'Lone -> 4, malignant -> 1, terrible -> 14, rate -> 1, mole -> 1, assert -> 1, lights -> 2, Stevenson, -> 1, submitted -> 4, tap. -> 1, beard, -> 1, band--a -> 1, force! -> 1, snow -> 7, Produced -> 2, ask, -> 1, purchasing -> 1, Hall, -> 1, wall. -> 5, remarked -> 32, laughing -> 4, member." -> 1 , 30 , 000 -> 2 , Redistributing -> 1 , coat, -> 6 , "'One -> 2, 'band,' -> 1, relapsed -> 1, apol... scala> counts(" Holmes ") res2: Int = 197 scala> counts(" Watson") res 3 : Int = 4 |
Чтобы вам не показалось странным, что Уотсон появляется только четыре раза, имейте в виду, что мы разделяемся на пробелы, и это означает, что в предложении, подобном следующему, знак интереса — Уотсон », а не Уотсон .
Поиск этого и других показывает больше токенов, содержащих Ватсона в истории.
1
2
3
4
5
6
7
8
|
scala> counts( "Watson,\"" ) res 4 : Int = 19 scala> counts( "Watson," ) res 5 : Int = 40 scala> counts( "Watson." ) res 6 : Int = 10 |
Конечно, реальная проблема заключается в том, что токенизация на пустом месте слишком грубая. Чтобы сделать это должным образом, обычно требуется хороший ручной токенизатор (который может хранить токены, такие как, например, Mr. и Yahoo!, в то же время разделяя знаки препинания на большинство слов), или компьютерный обученный, который обучается на данных, помеченных вручную для токенов. Пример последнего см. В лексемах инструментария Apache OpenNLP , которые включают предварительно обученные модели для английского языка.
Рабочая строка за строкой
Довольно часто вам нужно построчно работать с файлом, а не читать все как одну строку, как мы делали выше. Например, вам может потребоваться обрабатывать каждую строку по-разному, поэтому просто иметь ее как одну строку не очень удобно. Или вы можете работать с большим файлом, который не может легко поместиться в память (что происходит, когда вы читаете всю строку). Вы можете получить строки в файле как Iterator [String] , в котором каждый элемент является отдельной строкой из файла, используя метод getLines .
1
2
|
scala> Source.fromFile( "pg1661.txt" ).getLines res 4 : Iterator[String] = non-empty iterator |
Этот итератор готов для вас использовать строки, но он не сразу считывает весь файл в память — вместо этого он буферизует его так, что каждая строка будет доступна для вас, когда вы об этом просите, по существу, считывая данные с диска, когда вы требовать больше строк. Вы можете думать об этом как о потоке файла в вашу программу Scala, так же как современный аудио и видео контент передается на ваш компьютер: он никогда не сохраняется, а просто передается по частям туда, где он нужен, когда это необходимо.
Конечно, итераторы имеют много общего со структурами данных последовательности, такими как списки: если у нас есть итератор, мы можем использовать foreach , for , map и т. Д. Таким образом, чтобы распечатать все строки в файле, мы можем сделать следующее.
01
02
03
04
05
06
07
08
09
10
11
12
|
scala> Source.fromFile( "pg1661.txt" ).getLines.foreach(println) Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online at www.gutenberg.net Title : The Adventures of Sherlock Holmes Author : Arthur Conan Doyle <...many more lines...> |
Это создает много выходных данных, но показывает, как вы можете легко создать собственную реализацию Scala для программы Unix cat : просто сохраните следующую строку в файле с именем cat.scala :
1
|
scala.io.Source.fromFile(args( 0 )).getLines.foreach(println) |
И затем вызовите это с именем файла, чтобы перечислить его содержимое.
1
|
$ scala cat .scala pg1661.txt |
Вернемся к REPL, это не совсем идеально, чтобы увидеть весь файл. Если вы просто хотите увидеть начало файла, используйте метод take в Итераторе перед foreach .
1
2
3
4
5
6
|
scala> Source.fromFile( "pg1661.txt" ).getLines.take( 5 ).foreach(println) Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included |
Метод take в целом весьма полезен для любой последовательности и обеспечивает дополнение метода drop, как показано в следующих примерах на простом List [Int] .
01
02
03
04
05
06
07
08
09
10
11
|
scala> val numbers = 1 to 10 toList numbers : List[Int] = List( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) scala> numbers.take( 3 ) res 12 : List[Int] = List( 1 , 2 , 3 ) scala> numbers.drop( 3 ) res 13 : List[Int] = List( 4 , 5 , 6 , 7 , 8 , 9 , 10 ) scala> numbers.take( 3 ) ::: numbers.drop( 3 ) res 14 : List[Int] = List( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) |
Подсчет слов построчно, сначала попробуйте
Теперь, когда мы увидели, как читать файл и начинать работать с ним построчно, как мы подсчитываем количество вхождений каждого слова? Вспомните из урока 7 и выше, что отправной точкой должна была быть последовательность (Array, List и т. Д.) Строк, в которой каждый элемент представляет собой слово-токен. Чтобы начать двигаться к этому, мы можем просто использовать метод toList на Iterator [String], полученный из getLines .
1
2
|
scala> val holmes = Source.fromFile( "pg1661.txt" ).getLines.toList holmes : List[String] = List(The Project Gutenberg EBook of The Adventures of Sherlock Holmes, by Sir Arthur Conan Doyle, ( # 15 in our series by Sir Arthur Conan Doyle), "" , Copyright laws are changing all over the world. Be sure to check the, copyright laws for your country before downloading or redistributing, this or any other Project Gutenberg eBook., "" , This header should be the first thing seen when viewing this Project, Gutenberg file. Please do not remove it. Do not change or edit the, header without written permission., "" , Please read the "legal small print," and other information about the, eBook and Project Gutenberg at the bottom of this file. Included is, important information about your specific rights and restrictions in, how the file may be used. You can also find ou... |
Теперь у нас есть содержимое файла в виде List [String] , и мы можем продолжать делать с ним полезные вещи. Например, мы могли бы отобразить каждую строку (строки) как последовательности строк, разделенных пробелами.
1
2
|
scala> val listOfListOfWords = Source.fromFile( "pg1661.txt" ).getLines.toList.map(x = > x.split( " " ).toList) listOfListOfWords : List[List[java.lang.String]] = List(List(Project, Gutenberg's, The, Adventures, of, Sherlock, Holmes,, by, Arthur, Conan, Doyle), List( "" ), List(This, eBook, is, for , the, use, of, anyone, anywhere, at, no, cost, and, with ), List(almost, no, restrictions, whatsoever., "" , You, may, copy, it,, give, it, away, or), List(re-use, it, under, the, terms, of, the, Project, Gutenberg, License, included), List( with , this , eBook, or, online, at, www.gutenberg.net), List( "" ), List( "" ), List(Title : , The, Adventures, of, Sherlock, Holmes), List( "" ), List(Author : , Arthur, Conan, Doyle), List( "" ), List(Posting, Date : , April, 18 ,, 2011 , [EBook, # 1661 ]), List(First, Posted : , November, 29 ,, 2002 ), List( "" ), List(Language : , English), List( "" ), List( "" ), List(***, START, OF, THIS, PRO... |
И, как мы видели в уроке 7 , когда у нас есть список списков, мы можем использовать flatten для создания одного большого списка.
1
2
|
scala> val listOfWords = listOfListOfWords.flatten listOfWords : List[java.lang.String] = List(Project, Gutenberg's, The, Adventures, of, Sherlock, Holmes,, by, Arthur, Conan, Doyle, "" , This, eBook, is, for , the, use, of, anyone, anywhere, at, no, cost, and, with , almost, no, restrictions, whatsoever., "" , You, may, copy, it,, give, it, away, or, re-use, it, under, the, terms, of, the, Project, Gutenberg, License, included, with , this , eBook, or, online, at, www.gutenberg.net, "" , "" , Title : , The, Adventures, of, Sherlock, Holmes, "" , Author : , Arthur, Conan, Doyle, "" , Posting, Date : , April, 18 ,, 2011 , [EBook, # 1661 ], First, Posted : , November, 29 ,, 2002 , "" , Language : , English, "" , "" , ***, START, OF, THIS, PROJECT, GUTENBERG, EBOOK, THE, ADVENTURES, OF, SHERLOCK, HOLMES, ***, "" , "" , "" , "" , Produced, by, an, anonymous, Project, Gut... |
Но теперь вы можете распознать, что это шаблон « карта-затем-сглаживание» , который мы видели ранее, что означает, что мы можем вместо этого использовать FlatMap .
1
2
|
scala> val flatMappedWords = Source.fromFile( "pg1661.txt" ).getLines.toList.flatMap(x = > x.split( " " )) flatMappedWords : List[java.lang.String] = List(Project, Gutenberg's, The, Adventures, of, Sherlock, Holmes,, by, Arthur, Conan, Doyle, "" , This, eBook, is, for , the, use, of, anyone, anywhere, at, no, cost, and, with , almost, no, restrictions, whatsoever., "" , You, may, copy, it,, give, it, away, or, re-use, it, under, the, terms, of, the, Project, Gutenberg, License, included, with , this , eBook, or, online, at, www.gutenberg.net, "" , "" , Title : , The, Adventures, of, Sherlock, Holmes, "" , Author : , Arthur, Conan, Doyle, "" , Posting, Date : , April, 18 ,, 2011 , [EBook, # 1661 ], First, Posted : , November, 29 ,, 2002 , "" , Language : , English, "" , "" , ***, START, OF, THIS, PROJECT, GUTENBERG, EBOOK, THE, ADVENTURES, OF, SHERLOCK, HOLMES, ***, "" , "" , "" , "" , Produced, by, an, anonymous, Project,... |
Но вас должно немного беспокоить все это: не была ли здесь идея (частично) не читать все строки сразу? Действительно, с тем, что мы сделали выше, как только мы сказали toList на Итераторе, весь файл был прочитан в память. Тем не менее, мы можем обойтись без шага toList и просто напрямую добавить итератор в FlatMap и получить новый итератор по токенам, а не по строкам.
1
2
|
scala> val flatMappedWords = Source.fromFile( "pg1661.txt" ).getLines.flatMap(x = > x.split( " " )) flatMappedWords : Iterator[java.lang.String] = non-empty iterator |
Теперь, если мы хотим посчитать слова, мы можем преобразовать это в список и выполнить groupBy трюк mapValues, который мы уже видели (вывод опущен).
1
|
scala> val counts = Source.fromFile( "pg1661.txt" ).getLines.flatMap(x = > x.split( " " )).toList.groupBy(x = >x).mapValues(x = >x.length) |
Упс — это сработало, но мы снова перенесли весь файл в память, потому что список, созданный из toList, содержит все строки для файла. Далее мы увидим, как использовать изменяемую карту, чтобы обойти это.
Подсчет слов путем потоковой передачи с помощью итератора и использования изменяемых карт
До сих пор во всех руководствах я придерживался неизменных структур данных, за исключением случаев, когда изменяемые структуры появляются из-за контекста (например, массивы, выходящие из метода toString ). Хорошо по возможности использовать неизменяемые структуры данных, но бывают случаи, когда изменяемые структуры более удобны и, возможно, более уместны.
С неизменными картами, которые мы видели в предыдущем уроке, вы не могли изменить назначение для ключа, а также не могли добавить новый ключ.
01
02
03
04
05
06
07
08
09
10
|
lettersToNumbers : scala.collection.immutable.Map[java.lang.String,Int] = Map(A -> 1 , B -> 2 , C -> 3 ) 1 scala> lettersToNumbers( "A" ) = 4 <console> : 9 : error : value update is not a member of scala.collection.immutable.Map[java.lang.String,Int] lettersToNumbers( "A" ) = 4 scala> lettersToNumbers( "D" ) = 5 <console> : 9 : error : value update is not a member of scala.collection.immutable.Map[java.lang.String,Int] lettersToNumbers( "D" ) = 5 |
Существует еще один вид Map, scala.collection.mutable.Map , который допускает такого рода поведение.
01
02
03
04
05
06
07
08
09
10
11
12
|
scala> import scala.collection.mutable import scala.collection.mutable scala> val mutableLettersToNumbers = mutable.Map( "A" -> 1 , "B" -> 2 , "C" -> 3 ) mutableLettersToNumbers : scala.collection.mutable.Map[java.lang.String,Int] = Map(C -> 3 , B -> 2 , A -> 1 ) scala> mutableLettersToNumbers( "A" ) = 4 scala> mutableLettersToNumbers( "D" ) = 5 scala> mutableLettersToNumbers res 4 : scala.collection.mutable.Map[java.lang.String,Int] = Map(C -> 3 , D -> 5 , B -> 2 , A -> 4 ) |
Он также имеет удобный способ увеличить число, связанное с ключом, используя метод + = .
1
2
3
4
|
scala> mutableLettersToNumbers( "D" ) + = 5 scala> mutableLettersToNumbers res 6 : scala.collection.mutable.Map[java.lang.String,Int] = Map(C -> 3 , D -> 10 , B -> 2 , A -> 4 ) |
Однако мы не можем использовать этот метод с ключом, который не существует.
1
2
3
|
scala> mutableLettersToNumbers( "E" ) + = 1 java.util.NoSuchElementException : key not found : E <...stacktrace...> |
К счастью, мы можем предоставить дефолт. Вот пример запуска новой карты со значением по умолчанию 0.
01
02
03
04
05
06
07
08
09
10
11
|
scala> val counts = mutable.Map[String,Int]().withDefault(x = > 0 ) counts : scala.collection.mutable.Map[String,Int] = Map() scala> counts( "Z" ) + = 1 scala> counts( "Y" ) + = 1 scala> counts( "Z" ) + = 1 scala> counts res 11 : scala.collection.mutable.Map[String,Int] = Map(Z -> 2 , Y -> 1 ) |
Примечание : когда вы начинаете с некоторыми значениями, уже имеющимися на карте, Scala может определить типы ключей и значений, но при инициализации пустой карты необходимо явно объявить типы ключей и значений.
Имея это в виду , вот как мы можем использовать flatMap и изменяемую карту для подсчета слов в тексте, не считывая весь текст в память.
1
2
3
4
|
import scala.collection.mutable val counts = mutable.Map[String, Int]().withDefault(x = > 0 ) for (token <- scala.io.Source.fromFile( "pg1661.txt" ).getLines.flatMap(x = >x.split( "\\s+" ))) counts(token) + = 1 |
Создав таким образом Карту счетчиков, мы можем преобразовать ее в неизменяемую Карту с помощью метода toMap, как только мы закончим добавление элементов.
1
2
3
|
scala> val fixedCounts = counts.toMap fixedCounts : scala.collection.immutable.Map[String,Int] = Map(wood-work, -> 1 , <...output truncated...> |
Теперь мы не можем изменять значения на fixedCounts , что имеет преимущества во многих контекстах, например, мы не можем случайно уничтожить значения или добавить нежелательные ключи, и есть (положительные) последствия для параллельной обработки.
1
2
3
4
|
scala> fixedCounts( "Holmes" ) = 0 <console> : 13 : error : value update is not a member of scala.collection.immutable.Map[String,Int] fixedCounts( "Holmes" ) = 0 ^ |
Чтение файла с URL
Оказывается, scala.io.Source может делать гораздо больше, чем просто читать из файла. Другой пример — чтение URL-адреса для доступа к файлу в Интернете с использованием метода fromURL .
1
2
3
|
for (line <- Source.fromURL(holmesUrl).getLines) println(line) |
Если вы просто собираетесь снова и снова анализировать один и тот же файл, это, вероятно, не то, что вам нужно — просто скачайте файл и используйте его локально. Однако это может быть весьма полезно в тех случаях, когда вы изучаете ссылки на страницах (например, при обработке данных Википедии или Твиттера) и вам нужно читать содержимое с URL-адресов на лету.
Используйте (вверх) источник
Последнее замечание об итераторах, которые вы получаете с Source.fromFile и Source.fromURL: вы можете проходить их только один раз! Это часть того, что делает их более эффективными — они не хранят весь этот текст в памяти. Так что не удивляйтесь, если вы получите следующее поведение.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
scala> val holmesIterator = Source.fromFile( "pg1661.txt" ).getLines holmesIterator : Iterator[String] = non-empty iterator scala> holmesIterator.foreach(println) Project Gutenberg's The Adventures of Sherlock Holmes, by Arthur Conan Doyle This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online at www.gutenberg.net <...many lines of output...> This Web site includes information about Project Gutenberg-tm, including how to make donations to the Project Gutenberg Literary Archive Foundation, how to help produce our new eBooks, and how to subscribe to our email newsletter to hear about new eBooks. scala> holmesIterator.foreach(println) <...nothing output!...> |
Итак, Итератор исчерпан! Если вы хотите снова просмотреть файл, вам нужно будет запустить новый итератор, как вы это делали в первый раз. Неплохая вещь в том, чтобы оставаться с Итераторами и не преобразовывать их в списки (и, следовательно, переносить все в память), заключается в том, что каждая операция отображения, которую мы выполняем в Итераторе, применяется только к текущему элементу, который мы просматриваем, поэтому нам никогда не нужно читать все файл в память.
Конечно, если у вас есть достаточно небольшой файл для работы, вы можете абсолютно свободно вносить его в список и работать с ним таким образом, если хотите, — это часто будет более удобным, поскольку вы можете создавать шаблоны groupBy и mapValue .
Ссылка: Первые шаги в Scala для начинающих программистов, часть 8 от нашего партнера JCG Джейсона Болдриджа в блоге Bcomposes .
Статьи по Теме :
- Scala Tutorial — Scala REPL, выражения, переменные, основные типы, простые функции, сохранение и запуск программ, комментарии
- Scala Tutorial — кортежи, списки, методы для списков и строк
- Scala Tutorial — условное исполнение с блоками if-else и соответствием
- Scala Tutorial — итерация, для выражений, yield, map, filter, count
- Scala Tutorial — регулярные выражения, сопоставление
- Scala Tutorial — регулярные выражения, сопоставления и замены с помощью API scala.util.matching
- Учебник по Scala — Карты, Наборы, groupBy, Параметры, Flatten, FlatMap
- Scala Tutorial — объекты, классы, наследование, черты, списки с несколькими связанными типами, применение
- Scala Tutorial — скриптинг, компиляция, основные методы, возвращаемые значения функций
- Scala Tutorial — SBT, scalabha, пакеты, системы сборки
- Scala Tutorial — блоки кода, стиль кодирования, замыкания, проект документации scala
- Веселье с функцией композиции в Scala
- Как Scala изменил мой взгляд на мой Java-код
- Тестирование с помощью Scala
- Вещи, которые должен знать каждый программист