Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву
Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Получение строк из входного потокаСодержание книги
Поиск на нашем сайте Давайте посмотрим на действие ввода-вывода getContents, упроща- ющее обработку входного потока за счёт того, что оно позволяет рассматривать весь поток как обычную строку. Действие getContents читает всё содержимое стандартного потока ввода вплоть до обна- ружения символа конца файла. Его тип: getContents:: IO String. Са- мое приятное в этом действии то, что ввод-вывод в его исполнении является ленивым. Это означает, что выполнение foo <- getContents не приводит к загрузке в память всего содержимого потока и связы- ванию его с именем foo. Нет, действие getContents для этого слиш- ком лениво.Оно скажет: «Да, да, я прочту входные данные с тер- минала как-нибудь потом, когда это действительно понадобится!». В примере capslocker.hs для чтения ввода строка за строкой и пе- чати их в верхнем регистре использовалась функция forever. Если мы перейдём на getContents, то она возьмёт на себя все заботы о деталях ввода-вывода– о том, когда и какую часть входных дан- ных нужно прочитать. Поскольку наша программа просто берёт входные данные, преобразует их и выводит результат, пользуясь
getContents, её можно написать короче: import Data.Char
main = do
contents <- getContents putStr $ map toUpper contents Мы выполняем действие getContents и даём имя contents стро- ке, которую она прочтёт. Затем проходим функцией toUpper по всем символам этой строки и выводим результат на терминал. Имейте в виду: поскольку строки являются списками, а списки ленивы, как и действие getContents, программа не будет пытаться прочесть и со- хранить в памяти всё содержимое входного потока. Вместо этого
она будет читать данные порциями, переводить каждую порцию в верхний регистр и печатать результат.
Давайте проверим: $./capslocker < haiku.txt Я МАЛЕНЬКИЙ ЧАЙНИК
ОХ УЖ ЭТОТ ОБЕД В САМОЛЁТЕ ОН СТОЛЬ МАЛ И НЕВКУСЕН Работает. А что если мы просто запустим capslocker и будем пе- чатать строки вручную (для выхода из программы нужно нажать Ctrl + D)?
$./capslocker хей хо ХЕЙ ХО
идём ИДЁМ Чудесно! Как видите, программа печатает строки в верхнем ре- гистре по мере ввода строк. Когда результат действия getContents связывается с идентификатором сontents, он представляется в па- мяти не в виде настоящей строки, но в виде обещания, что рано или поздно он вернёт строку. Также есть обещание применить фун- кцию toUpper ко всем символам строки сontents. Когда выполняет- ся функция putStr, она говорит предыдущему обещанию: «Эй, мне нужна строка в верхнем регистре!». Поскольку никакой строки ещё нет, она говорит идентификатору сontents: «Аллё, а не считать ли строку с терминала?». Вот тогда функция getContents в самом деле считывает с терминала и передаёт строку коду, который её запра- шивал, чтобы сделать что-нибудь осязаемое. Затем этот код при- меняет функцию toUpper к символам строки и отдаёт результат в функцию putStr, которая его печатает. После чего функция putStr говорит, «Ау, мне нужна следующая строка, шевелись!» – и так про- должается до тех пор, пока не закончатся строки на входе, что мы обозначаем символом конца файла. Теперь давайте напишем программу, которая будет принимать некоторый вход и печатать только те строки, длина которых мень- ше 15 символов. Смотрим:
main = do contents <- getContents putStr $ shortLinesOnly contents
shortLinesOnly:: String -> String
shortLinesOnly = unlines. filter (\line -> length line < 15). lines Фрагмент программы, ответственный за ввод-вывод, сделан настолько малым, насколько это вообще возможно. Так как пред- полагается, что наша программа печатает результат, основываясь на входных данных, её можно реализовать согласно следующей логике: читаем содержимое входного потока, запускаем на этом содержимом некоторую функцию, печатаем результат работы этой функции. Функция shortLinesOnly принимает строку – например, такую: "коротко\nдлииииииииииинно\nкоротко". В этом примере в строке на самом деле три строки входных данных: две короткие и одна (посе- редине) длинная. В результате применения функции lines получа- ем список ["коротко", "длииииииииииинно", "коротко"]. Затем список строк фильтруется, и остаются только строки, длина которых мень- ше 15 символов: ["коротко", "коротко"]. Наконец, функция unlines соединяет элементы списка в одну строку, разделяя их символом перевода строки: "коротко\nкоротко".
Попробуем проверить, что получилось. Сохраните этот текст в файле shortlines.txt: Я короткая И я А я длиииииииинная!!!
А уж я-то какая длиннющая!!!!!!! Коротенькая Длиииииииииииииииииииииинная Короткая Сохраните программу в файле shortlinesonly.hs и скомпилируй- те её:
$ ghc shortlinesonly.hs
[1 of 1] Compiling Main (shortlinesonly.hs, shortlinesonly.o) Linking shortlinesonly...
Чтобы её протестировать, перенаправим содержимое файла
shortlines.txt на её поток ввода: $./shortlinesonly < shortlines.txt Я короткая
И я Коротенькая Короткая Видно, что на терминал выведены только короткие строки.
Преобразование входного потока
Подобная последовательность действий – считывание строки из потока ввода, преобразование её функцией и вывод результата – на- столько часто встречается, что существует функция, которая дела- ет эту задачу ещё легче; она называется interact. Функция interact принимает функцию типа String –> String как параметр и возвраща- ет действие ввода-вывода, которое примет некоторый вход, запус- тит заданную функцию и распечатает результат. Давайте изменим нашу программу так, чтобы воспользоваться этой функцией: main = interact shortLinesOnly
shortLinesOnly:: String -> String
shortLinesOnly = unlines. filter (\line -> length line < 15). lines Этой программой можно пользоваться, либо перенаправляя файл в поток ввода, либо вводя данные непосредственно с кла- виатуры, строка за строкой. Результат будет одинаковым, однако при вводе с клавиатуры входные данные будут чередоваться с вы- ходными. Давайте напишем программу, которая постоянно считывает строку и затем говорит нам, является ли введённая строка палинд- ромом. Можно было бы использовать функцию getLine, чтобы она считывала строку, затем говорить пользователю, является ли она палиндромом, и снова запускать функцию main. Но легче делать это с помощью функции interact. Когда вы её используете, всегда ду- майте, как преобразовать некий вход в желаемый выход. В нашем случае мы хотим заменить строку на входе на "палиндром" или "не палиндром".
respondPalindromes:: String -> String respondPalindromes = unlines. map (\xs -> if isPal xs then "палиндром" else "не палиндром"). lines
isPal xs = xs == reverse xs Всё вполне очевидно. Вначале преобразуем строку, например
"слон\nпотоп\nчто-нибудь" в список строк
["слон", "потоп", "что-нибудь"] Затем применяем анонимную функцию к элементам списка и по- лучаем:
["не палиндром", "палиндром", "не палиндром"] Соединяем список обратно в строку функцией unlines. Теперь мы можем определить главное действие ввода-вывода:
main = interact respondPalindromes Протестируем:
$./palindromes ха-ха не палиндром арозаупаланалапуазора палиндром печенька
не палиндром Хоть мы и написали программу, которая преобразует одну боль- шую составную строку в другую составную строку, она работает так, как будто мы обрабатываем строку за строкой. Это потому что язык Haskell ленив – он хочет распечатать первую строку результата, но не может, поскольку пока не имеет первой строки ввода. Как толь- ко мы введем первую строку на вход, он напечатает первую строку на выходе. Мы выходим из программы по символу конца файла.
Также можно запустить нашу программу, перенаправив в неё со- держимое файла. Например, у нас есть файл words.txt: кенгуру радар ротор мадам
Вот что мы получим, если перенаправим его на вход нашей про- граммы: $./palindromes < words.txt не палиндром
палиндром палиндром палиндром Ещё раз: результат аналогичен тому, как если бы мы запускали программу и вводили слова вручную. Здесь мы не видим входных строк, потому что вход берётся из файла, а не со стандартного ввода. К этому моменту, вероятно, вы уже усвоили, как работает лени- вый ввод-вывод и как его можно использовать с пользой для себя. Вы можете рассуждать о том, каким должен быть выход для дан- ного входа, и писать функцию для преобразования входа в выход. В ленивом вводе-выводе ничего не считывается со входа до тех пор, пока это не станет абсолютно необходимым для того, что мы собираемся напечатать.
Чтение и запись файлов До сих пор мы работали с вводом-выводом, печатая на терминале и считывая с него. Ну а как читать и записывать файлы? В неко- тором смысле мы уже работали с файлами. Чтение с терминала можно представить как чтение из специального файла. То же вер- но и для печати на терминале – это почти что запись в файл. Два файла – stdin и stdout – обозначают, соответственно, стандартный ввод и вывод. Принимая это во внимание, мы увидим, что запись и чтение из файлов очень похожи на запись в стандартный вывод и чтение со стандартного входа. Для начала напишем очень простую программу, которая откры- вает файл с именем girlfriend.txt и печатает его на терминале. В этом
файле записаны слова лучшего хита Аврил Лавин, «Girlfriend». Вот содержимое girlfriend.txt: Эй! Ты! Эй! Ты! Мне не нравится твоя подружка! Однозначно! Однозначно!
Думаю, тебе нужна другая! Программа:
handle <– openFile "girlfriend.txt" ReadMode contents <– hGetContents handle
putStr contents hClose handle Скомпилировав и запустив её, получаем ожидаемый результат:
Эй! Ты! Эй! Ты! Мне не нравится твоя подружка! Однозначно! Однозначно!
Думаю, тебе нужна другая! Посмотрим, что у нас тут? Первая строка – это просто четыре восклицания: они привлекают наше внимание. Во второй строке Аврил сообщает вам, что ей не нравится ваша подружка. Третья строка подчёркивает, что неприятие это категорическое. Ну а чет- вёртая предписывает подружиться с кем-нибудь получше. А теперь пройдемся по каждой строке кода. Наша программа – это несколько действий ввода-вывода, «склеенных» с помощью бло- ка do. В первой строке блока do мы использовали новую функцию, openFile. Вот её сигнатура: openFile:: FilePath –> IOMode –> IO Handle. Если попробовать это прочитать, получится следующее: «Функция openFile принимает путь к файлу и режим открытия файла (IOMode) и возвращает действие ввода-вывода, которое откроет файл, полу- чит дескриптор файла и заключит его в результат».
Тип FilePath – это просто синоним для типа String; он опре- делён так: type FilePath = String
Тип IOMode определён так: data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
Этот тип содержит перечисление режимов открытия файла, так же как наш тип содержал перечисление дней недели. Очень просто! Обратите внимание, что этот тип – IOMode; не путайте его с IO Mode. Тип IO Mode может быть типом действия ввода-вывода, ко- торое возвращает результат типа Mode, но тип IOMode – это просто перечисление. В конце концов функция вернёт действие ввода-вывода, кото- рое откроет указанный файл в указанном режиме. Если мы привя-
На следующей строке мы видим функцию hGetContents. Она прини- мает значение типа Handle; таким образом, она знает, с каким файлом работать, и возвращает значение типа IO String – действие ввода- вывода, которое вернёт содержимое файла в результате. Функция похожа на функцию getContents. Единственное отличие – функция getContents читает со стандартного входа (то есть с терминала), в то время как функция hGetContents принимает дескриптор файла, из которого будет происходить чтение. Во всех остальных смыслах они работают одинаково. Так же как и getContents, наша функция hGetContents не пытается прочитать весь файл целиком и сохра- нить его в памяти, но читает его по мере необходимости. Это очень удобно, поскольку мы можем считать, что идентификатор contents хранит всё содержимое файла, но на самом деле содержимого фай-
ла в памяти нет. Так что даже чтение из очень больших файлов не отожрёт всю память, но будет считывать только то, что нужно, и тогда, когда нужно. Обратите внимание на разницу между дескриптором, который используется для идентификации файла, и его содержимым. В на- шей программе они привязываются к именам handle и contents. Дескриптор – это нечто, с помощью чего мы знаем, что есть наш файл. Если представить всю файловую систему в виде очень боль- шой книги, а каждый файл в виде главы, то дескриптор будет чем-то вроде закладки, которая показывает нам, где мы в данный момент читаем (или пишем), в то время как идентификатор contents будет содержать саму главу. С помощью вызова putStr contents мы распечатываем содержи- мое на стандартном выводе, а затем выполняем функцию hClose, ко- торая принимает дескриптор и возвращает действие ввода-вывода, закрывающее файл. После открытия файла с помощью функции openFile вы должны закрывать файлы самостоятельно!
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Последнее изменение этой страницы: 2017-02-17; просмотров: 294; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 216.73.217.21 (0.013 с.) |