Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pol_Grem_-_ANSI_Common_Lisp_High_tech_-_2012.pdf
Скачиваний:
28
Добавлен:
12.03.2016
Размер:
4.85 Mб
Скачать

134

Глава 7. Ввод и вывод

Чтобы­ избе­жать­ подоб­ных­ случа­ев,­ не стоит­ приме­нять­ read напря­мую­. Лучший­ способ:­ пользо­ва­тель­ский­ ввод чита­ет­ся­ с помо­щью­ read-line и далее­ обра­ба­ты­ва­ет­ся­ с помо­щью­ read-from-string.° Эта функция­ при­ нима­ет­ строку­ и возвра­ща­ет­ первый­ объект,­ прочи­тан­ный­ из нее:

> (read-from-string "a b c") A

2

Поми­мо­ прочи­тан­но­го­ объек­та­ она возвра­ща­ет­ пози­цию­ в строке,­ на ко­ торой­ завер­ши­лось­ чтение­.

В общем­ случае­ read-from-string может­ при­нимать­ два необя­за­тель­ных­ аргу­мен­та­ и три аргу­мен­та­ по ключу­. Два необя­за­тель­ных ­– те же, что и третий­ и четвер­тый­ аргу­мен­ты­ в read-line (вызы­­вать ли ошибку­ при дости­же­нии­ конца­ строки­ и, если­ нет, что возвра­щать­ в этом случае)­ . А аргу­мен­ты­ по ключу­ :start и :end огра­ни­чи­ва­ют­ часть строки,­ в кото­­ рой будет­ выпол­нять­ся­ чтение­.

Рассмот­рен­ные­ в этом разде­ле­ функции­ опре­де­ле­ны­ с помо­щью­ более­ прими­тив­ной­ read-char, считы­ваю­щей­ одиноч­ный­ знак. Она при­нима­ет­ те же четы­ре­ необя­за­тель­ных­ аргу­мен­та,­ что и read, и read-line. Common Lisp также­ опре­де­ля­ет­ функцию­ peek-char, кото­рая­ похо­жа­ на read-char, но не удаля­ет­ прочи­тан­ный­ знак из пото­ка­.

7.3. Вывод

Имеют­ся­ три простей­шие­ функции­ выво­да:­ prin1, princ и terpri. Всем им можно­ сооб­щить­ выход­ной­ поток;­ по умолча­нию­ это *standard-output*­ .

Разни­ца­ между­ prin1 и princ, грубо­ гово­ря,­ в том, что prin1 гене­ри­ру­ет­ вывод­ для программ,­ а princ – для людей­. Так, напри­мер,­ prin1 печа­та­ет­ двойные­ кавыч­ки­ вокруг­ строк, а princ – нет:

>(prin1 "Hello") "Hello"

"Hello"

>(princ "Hello") Hello

"Hello"

Обе функции­ возвра­ща­ют­ свой первый­ аргу­мент,­ кото­рый,­ кстати,­ сов­ пада­ет­ с тем, кото­рый­ отобра­жа­ет­ prin1. Функция­ terpri печа­та­ет­ толь­ ко новую­ строку­.

Зная об этих функци­ях,­ вам легче­ будет­ понять­ пове­де­ние­ более­ общей­ format. С помо­щью­ функции format может­ быть осуще­ст­в­лен­ практи­че­­ ски любой­ вывод­. Она прини­ма­ет­ поток­ (а также­ t или nil), строку­ фор­ мати­ро­ва­ния­ и ноль или более­ аргу­мен­тов­. Строка­ форма­ти­ро­ва­ния­ мо­ жет содер­жать­ дирек­ти­вы­ форма­ти­ро­ва­ния­, кото­рые­ начи­на­ют­ся­ со знака­ ~ (тиль­да). На их место­ будут­ выве­де­ны­ представ­ле­ния­ аргу­мен­­ тов, пере­дан­ных­ format.

7.3. Вывод

135

Пере­да­вая­ t в каче­ст­ве­ перво­го­ аргу­мен­та,­ мы направ­ля­ем­ вывод­ в *stan­ dard-output*. Если­ первый­ аргу­мент ­– nil, то format возвра­тит­ строку­ вме­ сто того,­ чтобы­ ее напе­ча­тать­. Ради­ кратко­сти­ будем­ при­водить­ только­ такие­ приме­ры­. Функцию­ format можно­ рассмат­ри­вать­ одно­вре­мен­но­ и как неве­ро­ят­но­ мощный,­ и как жутко­ сложный­ инст­ру­мент­. Она по­ нима­ет­ огром­ное­ коли­че­ст­во­ дирек­тив,­ но только­ часть из них вам при­ дется­ исполь­зо­вать­. Две наибо­лее­ распро­стра­нен­ные­ дирек­ти­вы:­ ~A и ~%. (Между­ ~a и ~A нет разли­чия,­ одна­ко­ приня­то­ исполь­зо­вать­ вторую­ ди­ ректи­ву,­ пото­му­ что в строке­ форма­ти­ро­ва­ния­ она более­ замет­на­.) ~A – это место­ для вставки­ значе­ния,­ кото­рое­ будет­ напе­ча­та­но­ с помо­щью­ princ. ~% соот­вет­ст­ву­ет­ новой­ строке­.

> (format nil "Dear ~A,~% Our records indicate…" "Mr. Malatesta")

"Dear Mr. Malatesta, Our records indicate…"

В этом приме­ре­ format возвра­ща­ет­ один аргу­мент ­– строку,­ содер­жа­щую­ символ­ пере­но­са­ строки­.

Дирек­ти­ва­ ~S похо­жа­ на ~A, одна­ко­ она выво­дит­ объек­ты­ так же, как prin1, а не как princ:

> (format t "~S ~A" "z" "z") "z" z

NIL

Дирек­ти­вы­ форма­ти­ро­ва­ния­ сами­ могут­ прини­мать­ аргу­мен­ты­. ~F ис­ пользу­ет­ся­ для печа­ти­ выров­нен­ных­ справа­ чисел­ с плаваю­щей­ запя­­ той1 и может­ прини­мать­ пять аргу­мен­тов:­

1.Суммар­ное­ коли­че­ст­во­ выво­ди­мых­ симво­лов­. По умолча­нию­ это дли­ на числа­.

2.Коли­че­ст­во­ выво­ди­мых­ симво­лов­ после­ точки­. По умолча­нию­ выво­­ дятся­ все.

3.Смеще­ние­ точки­ влево­ (смеще­ние­ на один знак соот­вет­ст­ву­ет­ умно­­ жению­ на 10). По умолча­нию­ отсут­ст­ву­ет­.

4.Символ,­ кото­рый­ будет­ выве­ден­ вместо­ числа,­ если­ оно не умеща­ет­­ ся в коли­че­ст­во­ симво­лов,­ разре­шен­ное­ первым­ аргу­мен­том­. Если­ ниче­го­ не зада­но,­ то число,­ превы­шаю­щее­ допус­ти­мый­ лимит,­ будет­ напе­ча­та­но­ как есть.

5.Символ,­ печа­тае­мый­ перед­ левой­ цифрой­. По умолча­нию ­– пробел­.

1В англоя­зыч­ной­ лите­ра­ту­ре­ приня­то­ исполь­зо­вать­ точку­ в каче­ст­ве­ разде­­ лите­ля­ в деся­тич­ных­ дробях­. В русском­ языке­ приня­то­ исполь­зо­вать­ запя­­ тую, а такие­ числа­ назы­вать­ «числа­ми­ с плаваю­щей­ запя­той»­ вместо­ «flo­ ating­ point numbers» (числа­ с плаваю­щей­ точкой). В Common Lisp для запи­­ си деся­тич­ных­ дробей­ всегда­ исполь­зу­ет­ся­ точка­. – Прим. перев­.

136

Глава 7. Ввод и вывод

Вот при­мер, в кото­ром­ исполь­зу­ют­ся­ все пять аргу­мен­тов:­

> (format nil "~10,2,0,’*,’ F" 26.21875)

"26.22"

Исход­ное­ чис­ло округ­ля­ет­ся­ до двух знаков­ после­ точки­ (сама­ точка­ смеща­ет­ся­ на 0 поло­же­ний­ влево,­ то есть не смеща­ет­ся),­ для печа­ти­ чис­ ла выде­ля­ет­ся­ простран­ст­во­ в 10 симво­лов,­ на месте­ неза­пол­нен­ных­ сле­ ва полей­ печа­та­ют­ся­ пробе­лы­. Обра­ти­те­ внима­ние,­ что символ­ звездоч­­ ки пере­да­ет­ся­ как ’*, а не как обычно­ #\*. Посколь­ку­ задан­ное­ число­ вписы­ва­ет­ся­ в предло­жен­ное­ простран­ст­во­ 10 симво­лов,­ четвер­тый­ ар­ гумент­ не исполь­зу­ет­ся­.

Все эти аргу­мен­ты­ не обяза­тель­ны­ для исполь­зо­ва­ния­. Чтобы­ приме­­ нить значе­ние­ по умолча­нию,­ доста­точ­но­ просто­ пропус­тить­ соот­вет­ст­­ вующий­ аргу­мент­. При жела­нии­ напе­ча­тать­ число,­ округ­лен­ное­ до двух знаков­ после­ точки,­ доста­точ­но­ напи­сать:­

> (format nil "~,2,,,F" 26.21875) "26.22"

Такая­ после­до­ва­тель­ность­ запя­тых­ может­ быть опуще­на,­ поэто­му­ бо­ лее распро­стра­не­на­ следую­щая­ запись:­

> (format nil "~,2F" 26.21875) "26.22"

Преду­пре­ж­де­ние­: Округ­ляя­ числа,­ format не гаран­ти­ру­ет­ округ­ле­ние­ в большую­ или меньшую­ сторо­ну­. Поэто­му­ (format nil "~,1F" 1.25) мо­ жет выдать­ либо­ "1.2", либо­ "1.3". Таким­ обра­зом,­ если­ вам нужно­ округ­­ ление­ в конкрет­ную­ сторо­ну­ (напри­мер,­ при конвер­та­ции­ валют),­ перед­ печа­тью­ округ­ляй­те­ выво­ди­мое­ число­ явным­ обра­зом­.

7.4. Пример: замена строк

В этом разде­ле­ приве­ден­ при­мер исполь­зо­ва­ния­ ввода­-выво­да ­– простая­ програм­ма­ для заме­ны­ строк в тексто­вых­ файлах­. Мы созда­дим­ функ­ цию, кото­рая­ сможет­ заме­нить­ каждую­ строку­ old в файле­ на строку­ new. Простей­ший­ способ­ сделать­ это – сверять­ каждый­ символ­ с первым­ симво­лом­ old. Если­ они не совпа­да­ют,­ то мы можем­ просто­ напе­ча­тать­ символ­ из файла­. Если­ они совпа­да­ют,­ то сверя­ем­ следую­щий­ символ­ из файла­ со вторым­ симво­лом­ old, и т. д. Если­ найде­но­ совпа­де­ние,­ то печа­та­ем­ на вы­ход строку­ new.°

Что проис­хо­дит,­ когда­ мы наты­ка­ем­ся­ на несов­па­де­ние­ в процес­се­ свер­ ки симво­лов?­ Напри­мер,­ предпо­ло­жим,­ что мы ищем шаблон­ "abac", а входной­ файл содер­жит­ "ababac". Совпа­де­ние­ будет­ наблю­дать­ся­ вплоть до четвер­то­го­ симво­ла:­ в файле­ это b, а в шабло­не­ c. Дойдя­ до четвер­то­го­ симво­ла,­ мы пони­ма­ем,­ что можем­ напе­ча­тать­ первый­ символ­ a. Одна­ко­ неко­то­рые­ пройден­ные­ симво­лы­ нам все еще нужны,­ пото­му­ что третий­ прочтен­ный­ символ­ a совпа­да­ет­ с первым­ симво­лом­ шабло­на­. Таким­

7.4. Пример: замена строк

137

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

Очередь­ для времен­но­го­ хране­ния­ входной­ инфор­ма­ции­ назы­ва­ет­ся­ бу­ фером­. В данном­ случае­ необ­хо­ди­мый­ размер­ буфе­ра­ нам зара­нее­ неиз­­ вестен,­ и мы восполь­зу­ем­ся­ структу­рой­ данных­ под назва­ние­м­ кольце­­ вой буфер­. Кольце­вой­ буфер­ строит­ся­ на осно­ве­ векто­ра­. Способ­ его ис­ пользо­ва­ния­ напо­ми­на­ет­ кольцо:­ этот вектор­ после­до­ва­тель­но­ напол­ня­­ ется­вновьпосту­паю­щи­ми­симво­ла­ми,­акогда­онзапол­нит­ся­полно­стью,­ процесс­ начи­на­ет­ся­ с нача­ла,­ поверх­ уже суще­ст­вую­щих­ элемен­тов­. Ес­ ли нам зара­нее­ извест­но,­ что не пона­до­бит­ся­ хранить­ более­ n элемен­­ тов, векто­ра­ длиной­ n будет­ доста­точ­но,­ и пере­за­пись­ элемен­тов­ с нача­­ ла не приве­дет­ к поте­ре­ данных­.

На рис. 7.1 пока­зан­ код, реали­зую­щий­ опера­ции­ с кольце­вым­ буфе­ром­. Структу­ра­ buf имеет­ пять полей:­ вектор­ для хране­ния­ объек­тов­ и четы­­ ре индек­са­. Два из них, start и end, необ­хо­ди­мы­ для любых­ опера­ций­ с кольце­вым­ буфе­ром:­ start указы­­вает­ на началь­ное­ значе­ние­ в буфе­ре­ и будет­ увели­чи­вать­ся­ при изъя­тии­ элемен­та­ из буфе­ра;­ end указы­­вает­ на послед­нее­ значе­ние­ в буфе­ре­ и будет­ увели­чи­вать­ся­ при добав­ле­нии­ ново­го­ элемен­та­.

Два других­ индек­са,­ used и new, потре­бу­ют­ся­ для исполь­зо­ва­ния­ буфе­ра­ в нашем­ прило­же­нии­. Они могут­ прини­мать­ значе­ние­ между­ start и end, причем­ всегда­ будет­ соблю­дать­ся­ следую­щее­ соот­но­ше­ние:­

start used new end

Пару­ used и new можно­ считать­ анало­гом­ start и end для теку­ще­го­ сов­ паде­ния­. Когда­ мы начи­на­ем­ отсле­жи­вать­ совпа­де­ние,­ used будет­ ра­ вен start, а new – end. Для каждо­го­ после­до­ва­тель­но­го­ совпа­де­ния­ пары­ симво­лов­ used будет­ увели­чи­вать­ся­. Когда­ used достиг­нет­ new, это будет­ озна­чать,­ что мы счита­ли­ из буфе­ра­ все элемен­ты,­ зане­сен­­ные туда­ до нача­ла­ провер­ки­ данно­го­ совпа­де­ния­. Нам не нужно­ исполь­зо­вать­ боль­ ше симво­лов,­ чем было­ в буфе­ре­ перед­ нача­лом­ нахо­ж­де­ния­ теку­ще­го­ совпа­де­ния,­ иначе­ мы бы исполь­зо­ва­ли­ неко­то­рые­ симво­лы­ по несколь­­ ку раз. Поэто­му­ нам требу­ет­ся­ другой­ индекс,­ new, кото­рый­ исход­но­ ра­ вен end, но не увели­чи­ва­ет­ся­ при добав­ле­нии­ новых­ симво­лов­ во время­ провер­ки­ совпа­де­ния­.

Функция­ bref возвра­ща­ет­ элемент,­ храня­щий­ся­ в буфе­ре­ по задан­но­му­ индек­су­. Исполь­зуя­ оста­ток­ от деле­ния­ задан­но­го­ индек­са­ на длину­ векто­ра,­ мы можем­ сделать­ вид, что наш буфер­ имеет­ неог­ра­ни­чен­­ный размер­. Вызов­ (new-buf n) созда­ет­ новый­ буфер,­ способ­ный­ хранить­ до n элемен­тов­.

Чтобы­ помес­тить­ в буфер­ новое­ значе­ние,­ будем­ при­менять­ buf-insert. Эта функция­ увели­чи­ва­ет­ индекс­ end и кладет­ на это место­ задан­ный­ элемент­. Обрат­ной­ функци­ей­ явля­ет­ся­ buf-pop, кото­рая­ возвра­ща­ет­ пер­ вый элемент­ буфе­ра­ и увели­чи­ва­ет­ индекс­ start. Эти две функции­ нуж­ ны для любо­го­ кольце­во­го­ буфе­ра­.

138 Глава 7. Ввод и вывод

(defstruct buf

vec (start -1) (used -1) (new -1) (end -1))

(defun bref (buf n) (svref (buf-vec buf)

(mod n (length (buf-vec buf)))))

(defun (setf bref) (val buf n) (setf (svref (buf-vec buf)

(mod n (length (buf-vec buf))))

val))

(defun new-buf (len)

(make-buf :vec (make-array len)))

(defun buf-insert (x b)

(setf (bref b (incf (buf-end b))) x))

(defun buf-pop (b) (prog1

(bref b (incf (buf-start b))) (setf (buf-used b) (buf-start b)

(buf-new b) (buf-end b))))

(defun buf-next (b)

(when (< (buf-used b) (buf-new b)) (bref b (incf (buf-used b)))))

(defun buf-reset (b)

(setf (buf-used b) (buf-start b) (buf-new b) (buf-end b)))

(defun buf-clear (b)

(setf (buf-start b) -1 (buf-used b) -1 (buf-new b) -1 (buf-end b) -1))

(defun buf-flush (b str)

(do ((i (1+ (buf-used b)) (1+ i))) ((> i (buf-end b)))

(princ (bref b i) str)))

Рис. 7.1. Опера­ции­ с кольце­вым­ буфе­ром­

Следую­щие­ две функции­ напи­са­ны­ специ­аль­но­ для наше­го­ прило­же­­ ния: buf-next чита­ет­ значе­ние­ из буфе­ра­ без его извле­че­ния,­ buf-reset сбрасы­ва­ет­ used и new до исход­ных­ значе­ний­ start и end. Если­ все значе­­ ния до new прочи­та­ны,­ buf-next возвра­тит­ nil. Посколь­ку­ мы соби­ра­ем­ся­ хранить­ в буфе­ре­ исклю­чи­тель­но­ симво­лы,­ отли­чить­ nil как особое­ значе­ние­ не будет­ пробле­мой­.

Нако­нец,­ buf-flush выво­дит­ содер­жи­мое­ буфе­ра,­ запи­сы­вая­ все доступ­­ ные элемен­ты­ в задан­ный­ поток,­ а buf-clear очища­ет­ буфер,­ сбрасы­вая­ все индек­сы­ до -1.

7.4. Пример: замена строк

139

Функции­ из кода­ на рис. 7.1 исполь­зу­ют­ся­ в коде­ на рис. 7.2, предос­тав­­ ляющем­ средст­ва­ для заме­ны­ строк. Функция­ file-subst прини­ма­ет­ че­ тыре­ аргу­мен­та:­ иско­мую­ строку,­ ее заме­ну,­ входной­ и выход­ной­ фай­ лы. Она созда­ет­ пото­ки,­ связан­ные­ с задан­ны­ми­ файла­ми,­ и вызы­­вает­ функцию­ stream-subst, кото­рая­ и выпол­ня­ет­ основ­ную­ рабо­ту­.

Вторая­ функция,­ stream-subst, исполь­зу­ет­ алго­ритм,­ кото­рый­ был схе­ мати­че­ски­ описан­ в нача­ле­ разде­ла­. Каждый­ раз она чита­ет­ из входно­­ го пото­ка­ один символ­. Если­ он не совпа­да­ет­ с первым­ элемен­том­ иско­­ мой строки,­ он тут же запи­сы­ва­ет­ся­ в выход­ной­ поток­ (1). Когда­ начи­на­­ ется­ совпа­де­ние,­ симво­лы­ ставят­ся­ в очередь­ в буфер­ buf (2).

(defun file-subst (old new file1 file2) (with-open-file (in file1 :direction :input)

(with-open-file (out file2 :direction :output :if-exists :supersede)

(stream-subst old new in out))))

(defun stream-subst (old new in out) (let* ((pos 0)

(len (length old)) (buf (new-buf len)) (from-buf nil))

(do ((c (read-char in nil :eof)

(or (setf from-buf (buf-next buf))

(read-char in nil :eof))))

 

((eql c :eof))

 

(cond ((char=

c (char old pos))

 

(incf pos)

 

(cond ((= pos len)

; 3

 

(princ new out)

 

 

(setf pos 0)

 

 

(buf-clear buf))

 

 

((not from-buf)

; 2

 

(buf-insert c buf))))

 

((zerop

pos)

; 1

(princ

c out)

 

(when from-buf

 

(buf-pop buf)

 

(buf-reset buf)))

 

(t

 

; 4

(unless from-buf

 

(buf-insert c buf))

 

(princ

(buf-pop buf) out)

 

(buf-reset buf) (setf pos 0))))

(buf-flush buf out)))

Рис. 7.2. Заме­на­ строк

140

Глава 7. Ввод и вывод

Пере­мен­ная­ pos указы­­вает­ на поло­же­ние­ симво­ла­ в иско­мой­ строке­. Ес­ ли оно равно­ длине­ самой­ строки,­ это озна­ча­ет,­ что совпа­де­ние­ найде­но­. В таком­ случае­ в выход­ной­ поток­ запи­сы­ва­ет­ся­ заме­на­ строки,­ а буфер­ очища­ет­ся­ (3). Если­ в какой­-либо­ момент­ после­до­ва­тель­ность­ симво­лов­ пере­ста­ет­ соот­вет­ст­во­вать­ иско­мой­ строке,­ мы вправе­ забрать­ первый­ символ­ из буфе­ра­ и запи­сать­ в выход­ной­ поток,­ а также­ уста­но­вить­ pos равным­ нулю,­ а сам буфер­ сбросить­ (4).

Следую­щая­ табли­ца­ нагляд­но­ демон­ст­ри­ру­ет,­ что проис­хо­дит­ при за­ мене­ "baro" на "baric" в файле,­ содер­жа­щем­ только­ одно­ слово­ barbarous:

Символ­

Источ­ник­

Совпа­де­ние­

Вари­ант­

Вывод­

Буфер­

b

файл

b

2

 

b

a

файл

a

2

 

b a

r

файл

r

2

 

b a r

b

файл

o

4

b

b.a r b.

a

буфер­

b

1

a

a.r b.

r

буфер­

b

1

r

r.b.

b

буфер­

b

1

 

r b:

a

файл

a

2

 

r b:a

r

файл

r

2

 

r b:a r

o

файл

o

3

baric

 

u

файл

b

1

u

 

s

файл

b

1

s

 

 

 

 

 

 

 

Первая­ колон­ка­ содер­жит­ теку­щий­ символ ­– значе­ние­ пере­мен­ной­ c; вторая­ пока­зы­­вает,­ отку­да­ он был считан ­– из буфе­ра­ или же прями­ком­ из файла;­ третья­ пока­зы­­вает­ символ,­ с кото­рым­ мы сравни­ва­ем, ­– эле­ мент old по индек­су­ pos; чет­вертая­ указы­­вает­ на вари­ант­ дейст­вия,­ со­ вершае­мо­го­ в данном­ случае;­ пятая­ колон­ка­ соот­вет­ст­ву­ет­ теку­ще­му­ содер­жи­мо­му­ буфе­ра­ после­ вы­полне­ния­ опера­ции­. В послед­ней­ колон­­ ке точка­ми­ после­ симво­ла­ пока­за­ны­ также­ пози­ции­ used и new. Если­ они совпа­да­ют,­ то это отобра­жа­ет­ся­ с помо­щью­ двоето­чия­.

Предпо­ло­жим,­ что у нас есть файл "test1", содер­жа­щий­ следую­щий­ текст:

The struggle between Liberty and Authority is the most conspicuous feature in the portions of history with which we are earliest familiar, particularly in that of Greece, Rome, and England.

После­ выпол­не­ния­ (file-subst " th" " z" "test1" "test2") файл "test2"

будет­ иметь следую­щий­ вид:

The struggle between Liberty and Authority is ze most conspicuous feature in ze portions of history with which we are earliest familiar, particularly in zat of Greece, Rome, and England.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]