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

3.8. Деревья

57

Из послед­не­го­ приме­ра­ видно,­ как mapcar обра­ба­ты­ва­ет­ случай­ со спи­ сками­ разной­ длины­. Вычис­ле­ние­ обры­­вает­ся­ по оконча­нии­ само­го­ ко­ ротко­го­ списка­.

Похо­жим­ обра­зом­ дейст­ву­ет­ maplist, одна­ко­ приме­ня­ет­ функцию­ после­­ дова­тель­но­ не к car, а к cdr списка,­ начи­ная­ со всего­ списка­ цели­ком­.

> (maplist #’(lambda (x) x) ’(a b c))

((A B C) (B C) (C))

Среди­ других­ отобра­жаю­щих­ функций­ можно­ отме­тить­ mapc, кото­рая­ рассмат­ри­ва­ет­ся­ на стр. 102, а также­ mapcan, с кото­рой­ вы позна­ко­ми­­ тесь на стр. 209.

3.8. Деревья

Cons-ячейки­ также­ можно­ рассмат­ри­вать­ как двоич­ные­ дере­вья:­ car соот­­ ветст­ву­ет­ право­му­ подде­ре­ву,­ а cdr – лево­му­. К приме­ру,­ список­ (a (b c) d) представ­лен­ в виде­ дере­ва­ на рис. 3.8. (Если­ повер­нуть­ его на 45° против­ часо­вой­ стрелки,­ он будет­ напо­ми­нать­ рис. 3.3.)

В Common Lisp есть несколь­ко­ встроен­ных­ функций­ для рабо­ты­ с де­ ревья­ми­. Напри­мер,­ copy-tree при­нима­ет­ дере­во­ и возвра­ща­ет­ его ко­ пию. Опре­де­лим­ анало­гич­ную­ функцию­ само­стоя­тель­но:­

(defun our-copy-tree (tr) (if (atom tr)

tr

(cons (our-copy-tree (car tr)) (our-copy-tree (cdr tr)))))

Сравни­те­ ее с функци­ей­ copy-list (стр. 53). В то время­ как copy-list копи­­ рует­ только­ cdr списка,­ copy-tree копи­ру­ет­ еще и car.

a

nil

b

 

 

nil

d

 

 

 

 

 

 

 

 

 

 

 

 

c

Рис. 3.8. Бинар­ное­ дере­во­

58

Глава 3. Списки

Бинар­ные­ дере­вья­ без внутрен­них­ узлов­ вряд ли окажут­ся­ полез­ны­ми­. Common Lisp включа­ет­ в себя­ функции­ для опера­ций­ с деревь­я­ми­ не по­тому,­ что без деревь­ев­ нельзя­ обойтись,­ а по­тому­ что эти функции­ очень полез­ны­ для рабо­ты­ со списка­ми­ и подспи­ска­ми­. Напри­мер,­ пред­ поло­жим,­ что у нас есть список:­

(and (integerp x) (zerop (mod x 2)))

И мы хотим­ заме­нить­ x на y. Заме­нить­ элемен­ты­ в после­до­ва­тель­но­сти­ можно­ с помо­щью­ substitute:

> (substitute ’y ’x ’(and (integerp x) (zerop (mod x 2)))) (AND (INTEGERP X) (ZEROP (MOD X 2)))

Как види­те,­ исполь­зо­ва­ние­ substitute не дало­ резуль­та­тов,­ так как спи­ сок содер­жит­ три элемен­та,­ ни один из кото­рых­ не явля­ет­ся­ x. Здесь нам пона­до­бит­ся­ функция­ subst, рабо­таю­щая­ с деревь­я­ми:­

> (subst ’y ’x ’(and (integerp x) (zerop (mod x 2)))) (AND (INTEGERP Y) (ZEROP (MOD Y 2)))

Наше­ опре­де­ле­ние­ subst будет­ очень похо­же­ на copy-tree:

(defun our-subst (new old tree) (if (eql tree old)

new

(if (atom tree) tree

(cons (our-subst new old (car tree)) (our-subst new old (cdr tree))))))

Любые­ функции,­ опери­рую­щие­ с деревь­я­ми,­ будут­ выгля­деть­ похо­жим­ обра­зом,­ рекур­сив­но­ вызы­­вая себя­ с car и cdr. Такая­ рекур­сия­ назы­­ва­ ется­ двойной­.

3.9. Чтобы понять рекурсию, нужно понять рекурсию

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

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

Чтобы­ убедить­ся,­ что рекур­сия­ дела­ет­ то, что мы дума­ем,­ доста­точ­но­ спросить,­ покры­ва­ет­ ли она все вари­ан­ты­. Посмот­рим,­ к приме­ру,­ на рекур­сив­ную­ функцию­ для опре­де­ле­ния­ длины­ списка:­

3.9. Чтобы понять рекурсию, нужно понять рекурсию

59

(defun len (lst) (if (null lst)

0

(+ (len (cdr lst)) 1)))

Мож­но убедить­ся­ в коррект­но­сти­ функции,­ прове­рив­ две вещи­1:

1.Она рабо­та­ет­ со списка­ми­ нуле­вой­ длины,­ возвра­щая­ 0.

2.Если­ она рабо­та­ет­ со списка­ми,­ длина­ кото­рых­ равна­ n, то будет­ справед­ли­ва­ также­ и для списков­ длиной­ n+1.

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

Первое­ утвер­жде­ние­ совер­шен­но­ очевид­но:­ если­ lst – это nil, то функ­ ция тут же возвра­ща­ет­ 0. Теперь­ предпо­ло­жим,­ что она рабо­та­ет­ со спи­ ском длиной­ n. Соглас­но­ опре­де­ле­нию,­ для списка­ длиной­ n+1 она вернет­ число,­ на 1 большее­ длины­ cdr списка,­ то есть n+1.

Это все, что нам нуж­но знать. Представ­лять­ всю после­до­ва­тель­ность­ вызо­вов­ вовсе­ не обяза­тель­но,­ так же как необя­за­тель­но­ искать­ парные­ скобки­ в опре­де­ле­ни­ях­ функций­.

Для более­ сложных­ функций,­ напри­мер­ двойной­ рекур­сии,­ случа­ев­ бу­ дет больше,­ но проце­ду­ра­ оста­нет­ся­ преж­ней. К приме­ру,­ для функции­ our-copy-tree (стр. 41) потре­бу­ет­ся­ рассмот­реть­ три случая:­ атомы,­ про­ стые ячейки,­ дере­вья,­ содер­жа­щие­ n+1 ячеек­.

Первый­ случай­ носит­ назва­ние­ базо­во­го­ (base case). Если­ рекур­сив­ная­ функция­ ведет­ себя­ не так, как ожида­лось,­ причи­на­ часто­ заклю­ча­ет­ся­ в некор­рект­ной­ провер­ке­ базо­во­го­ случая­ или же в отсут­ст­вии­ провер­­ ки, как в приме­ре­ с функци­ей­ member:

(defun our-member

(obj lst)

; wrong2

(if (eql (car lst) obj)

 

lst

 

 

(our-member

obj (cdr lst))))

 

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

Способ­ность­ оценить­ коррект­ность­ рекур­сив­ной­ функции ­– лишь пер­ вая часть пони­ма­ния­ рекур­сии­. Вторая­ часть – необ­хо­ди­мо­ научить­ся­ писать­ собст­вен­­ные рекур­сив­ные­ функции,­ кото­рые­ дела­ют­ то, что вам требу­ет­ся­. Этому­ посвя­щен­ раздел­ 6.9.

1Такой­ метод­ дока­за­тель­ст­ва­ носит­ назва­ние­ мате­ма­ти­че­ской­ индук­ции­. –

Прим. перев­.

2Здесь ; wrong – это коммен­та­рий­. Коммен­та­рия­ми­ в Лиспе­ счита­ет­ся­ все от симво­ла­ «;» до конца­ теку­щей­ строки­.

60

Глава 3. Списки

3.10. Множества

Списки ­– хоро­ший­ способ­ представ­ле­ния­ неболь­ших­ множеств­. Чтобы­ прове­рить,­ при­надле­жит­ ли элемент­ множе­ст­ву,­ зада­вае­мо­му­ списком,­ можно­ восполь­зо­вать­ся­ функци­ей­ member:

> (member ’b ’(a b c)) (B C)

Если­ иско­мый­ элемент­ найден,­ member возвра­ща­ет­ не t, а часть списка,­ начи­наю­ще­го­ся­ с найден­но­го­ элемен­та­. Конеч­но,­ непус­той­ список­ логи­­ чески­ соот­вет­ст­ву­ет­ исти­не,­ но такое­ пове­де­ние­ member позво­ля­ет­ полу­­ чить больше­ инфор­ма­ции­. По умолча­нию­ member сравни­ва­ет­ аргу­мен­ты­ с помо­щью­ eql. Преди­кат­ сравне­ния­ можно­ задать­ вручную­ с помо­щью­ аргу­мен­та­ по ключу­. Аргу­мен­ты­ по ключу­ (keyword) – доволь­но­ распро­­ странен­ный­ в Common Lisp способ­ пере­да­чи­ аргу­мен­тов­. Такие­ аргу­­ менты­ пере­да­ют­ся­ не в соот­вет­ст­вии­ с их поло­же­ни­ем­ в списке­ пара­мет­­ ров, а с помо­щью­ особых­ меток,­ назы­вае­мых­ ключе­вы­ми­ слова­ми­. Клю­ чевым­ словом­ счита­ет­ся­ любой­ символ,­ начи­наю­щий­ся­ с двоето­чия­.

Одним­ из аргу­мен­тов­ по ключу, прини­мае­мых­ member, явля­ет­ся­ :test. Он позво­ля­ет­ исполь­зо­вать­ в каче­ст­ве­ преди­ка­та­ сравне­ния­ вместо­ eql произ­воль­ную­ функцию,­ напри­мер­ equal:

> (member ’(a) ’((a) (z)) :test #’equal) ((A) (Z))

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

Другой­ аргу­мент­ по ключу­ функции­ member – :key. С его помо­щью­ можно­ задать­ функцию,­ приме­няе­мую­ к каждо­му­ элемен­ту­ перед­ сравне­ни­ем:­

> (member ’a ’((a b) (c d)) :key #’car) ((A B) (C D))

В этом при­мере­ мы иска­ли­ элемент,­ car кото­ро­го­ равен­ a.

При жела­нии­ исполь­зо­вать­ оба аргу­мен­та­ по ключу­ мож­но зада­вать­ их в произ­воль­ном­ поряд­ке:­

>(member 2 ’((1) (2)) :key #’car :test #’equal) ((2))

>(member 2 ’((1) (2)) :test #’equal :key #’car) ((2))

Спомо­щью­ member-if можно­ найти­ элемент,­ удовле­тво­ряю­щий­ произ­воль­­ ному­ преди­ка­ту,­ напри­мер­ oddp (истин­но­му,­ когда­ аргу­мент­ нече­тен):­

>(member-if #’oddp ’(2 3 4))

(3 4)

Наша­ собст­вен­­ная функция­ member-if могла­ бы выгля­деть­ следую­щим­ обра­зом:­

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