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

3

Списки

Списки ­– это одна­ из базо­вых­ структур­ данных­ в Лиспе­. В ранних­ диа­ лектах­ списки­ были­ единст­вен­­ной структу­рой­ данных,­ и именно­ им Лисп обязан­ своим­ назва­ни­ем:­ «LISt Processor» (Обра­бот­чик­ списков)­ . Ны­нешний­ Лисп уже не соот­вет­ст­ву­ет­ этому­ акро­ни­му­. Common Lisp явля­ет­ся­ язы­ком обще­го­ назна­че­ния­ и предос­тав­ля­ет­ програм­ми­сту­ широ­кий­ набор­ структур­ данных­.

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

3.1. Ячейки

В разделе 2.4 вводят­ся­ cons, car и cdr – простей­шие­ функции­ для мани­­ пуля­ций­ со списка­ми­. В дейст­ви­тель­но­сти,­ cons объеди­ня­ет­ два объек­­ та в один, назы­­ваемый­ ячейкой­ (cons). Если­ быть точнее,­ то cons – это пара­ указа­те­лей,­ первый­ из кото­рых­ указы­­вает­ на car, второй ­– на cdr.

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

Неза­чем­ представ­­лять каждый­ список­ в виде­ cons-ячеек,­ но нужно­ пом­ нить, что они могут­ быть зада­ны­ таким­ обра­­зом. Любой­ непус­той­ список­ может­ считать­ся­ парой,­ содер­жа­щей­ первый­ элемент­ списка­ и осталь­­ ную его часть. В Лиспе­ списки­ явля­ют­ся­ вопло­ще­ни­ем­ этой идеи. Имен­ но поэто­му­ функция­ car позво­ля­ет­ полу­чить­ первый­ элемент­ спис­ка,­

3.1. Ячейки

49

а cdr – его оста­ток­ (кото­рый­ явля­ет­ся­ либо­ cons-ячей­кой, либо­ nil). И до­ гово­рен­ность­ всегда­ была­ тако­вой:­ исполь­зо­вать­ car для обозна­че­ния­ перво­го­ элемен­та­ списка,­ а cdr – для его остат­ка­. Так эти назва­ния­ ста­ ли сино­ни­ма­ми­ опера­ций­ first и rest. Таким­ обра­зом,­ списки ­– это не отдель­ный­ вид объек­тов,­ а всего­ лишь набор­ связан­ных­ между­ собой­ cons-ячеек­.

Если­ мы попы­та­ем­ся­ исполь­зо­вать­ cons вместе­ с nil,

> (setf x (cons ’a nil))

(A)

то полу­чим­ список,­ состоя­щий­ из одной­ ячейки,­ как пока­за­но­ на рис. 3.1. Такой­ способ­ изобра­же­ния­ ячеек­ назы­­вает­ся­ блочным­, пото­му­ что каж­ дая ячейка­ представ­ля­ет­ся­ в виде­ блока,­ содер­жа­ще­го­ указа­те­ли­ на car и cdr. Вызы­­вая car или cdr, мы полу­ча­ем­ объект,­ на кото­рый­ указы­­вает­ соот­вет­ст­вую­щий­ указа­тель:­

>(car x)

A

>(cdr x) NIL

nil

a

Рис. 3.1. Список,­ состоя­щий­ из одной­ ячейки­

Состав­ляя­ список­ из несколь­ких­ элемен­тов,­ мы полу­ча­ем­ цепоч­ку­ ячеек:­

> (setf y (list ’a ’b ’c)) (A B C)

Эта структу­ра­ пока­за­на­ на рис. 3.2. Теперь­ cdr списка­ будет­ указы­­вать на список­ из двух элемен­тов:­

> (cdr y) (B C)

nil

a

b

c

Рис. 3.2. Список­ из трех ячеек­

50

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

Для списка­ из несколь­ких­ элемен­тов­ указа­тель­ на car дает­ первый­ эле­ мент списка,­ а указа­тель­ на cdr – его оста­ток­.

Элемен­та­ми­ списка­ могут­ быть любые­ объек­ты,­ в том числе­ и другие­ списки:­

> (setf z (list ’a (list ’b ’c) ’d)) (A (B C) D)

Соот­вет­ст­вую­щая­ структу­ра­ пока­за­на­ на рис. 3.3; car второй­ ячей­ки указы­­вает­ на другой­ список:­

> (car (cdr z)) (B C)

nil

a

 

 

 

 

 

 

 

 

nil

d

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

bc

Рис. 3.3. Вложен­ный­ список­

В этих двух при­мерах­ списки­ состоя­ли­ из трех элемен­тов­. При этом в послед­нем­ при­мере­ один из элемен­тов­ тоже­ явля­ет­ся­ списком­. Такие­ списки­ назы­­вают­ вложен­ны­ми­ (nested), в то время­ как списки,­ не со­ держа­щие­ внутри­ себя­ подспи­сков,­ назы­­вают­ пло­скими­ (flat).

Прове­рить,­ явля­ет­ся­ ли объект­ cons-ячей­кой, мож­но с помо­щью­ функ­ ции consp. Поэто­му­ listp можно­ опре­де­лить­ так:

(defun out-listp (x)

(or (null x) (consp x)))

Теперь­ опре­де­лим­ преди­кат­ atom, учиты­вая­ тот факт, что атома­ми­ в Лис­ пе счита­ет­ся­ все, кроме­ cons-ячеек:­

(defun our-atom (x) (not (consp x)))

Исклю­че­ни­ем­ из этого­ прави­ла­ счита­ет­ся­ nil, кото­рый­ явля­ет­ся­ и ато­ мом, и списком­ одно­вре­мен­но­.

3.2. Равенство

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

3.3. Почему в Лиспе нет указателей

51

> (eql (cons ’a nil) (cons ’a nil)) NIL

Функция­ eql1 возвра­ща­ет­ t (true), только­ если­ сравни­вае­мые­ значе­ния­ соот­вет­ст­ву­ют­ одно­му­ объек­ту­ в памя­ти­ Лиспа­.

>(setf x (cons ’a nil))

(A)

>(eql x x)

T

Для провер­ки­ идентич­но­сти­ списков­ (и других­ объек­тов)­ исполь­зу­ет­ся­ преди­кат­ equal. С его точки­ зрения­ два одина­ко­во­ выгля­дя­щих­ объек­та­ равны:­

> (equal x (cons ’a nil)) T

Пока­жем,­ как можно­ опре­де­лить­ функцию­ equal для част­но­го­ случая­ провер­ки­ на равен­ст­во­ списков­2, предпо­ла­гая,­ что ес­ли два объек­та­ рав­ ны для преди­ка­та­ eql, то они будут­ равны­ и для equal:

(defun our-equal (x y) (or (eql x y)

(and (consp x) (consp y)

(our-equal (car x) (car y)) (our-equal (cdr x) (cdr y)))))

3.3. Почему в Лиспе нет указателей

Чтобы­ понять,­ как устро­ен­ Лисп, необ­хо­ди­мо­ осознать,­ что меха­низм­ присваи­ва­ния­ значе­ния­ пере­мен­ным­ похож­ на построе­ние­ списков­ из объек­тов­. Пере­мен­ной­ соот­вет­ст­ву­ет­ указа­тель­ на ее значе­ние,­ так же как cons-ячей­ки имеют­ указа­те­ли­ на car и cdr.

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

>(setf x ’(a b c)) (A B C)

>(setf y x)

(A B C)

1В ранних­ диалек­тах­ Лиспа­ зада­чу­ eql выпол­нял­ eq. В Common Lisp eq – бо­ лее строгая­ функция,­ а основ­ным­ преди­ка­том­ провер­ки­ идентич­но­сти­ яв­ ляет­ся­ eql. Роль eq разъяс­ня­ет­ся­ на стр. 234.

2Функция­ our-equal приме­ни­ма­ не к любым­ спискам,­ а только­ к спискам­ сим­ волов­. Неточ­ность­ в ориги­на­ле­ указа­на­ Биллом­ Стретфор­дом­. – Прим. перев­.

52

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

x =

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

y =

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a

 

 

 

 

b

 

 

 

 

c

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 3.4. Две пере­мен­ные,­ указы­ваю­щие­ на один список­

Что проис­хо­дит,­ когда­ мы пыта­ем­ся­ присво­ить­ y значе­ние­ x? Место­ в па­ мяти,­ связан­ное­ с пере­мен­ной­ x, содер­жит­ не сам список,­ а указа­тель­ на него­. Чтобы­ присво­ить­ пере­мен­ной­ y то же значе­ние,­ доста­точ­но­ просто­ скопи­ро­вать­ этот указа­тель­ (рис. 3.4). В данном­ случае­ две пере­мен­ные­ будут­ одина­ко­вы­ми­ с точки­ зрения­ eql:

> (eql x y) T

Таким­ обра­зом,­ в Лиспе­ указа­те­ли­ явно­ не исполь­зу­ют­ся,­ пото­му­ что любое­ значе­ние,­ по сути,­ явля­ет­ся­ указа­те­лем­. Когда­ вы присваи­вае­те­ значе­ние­ пере­мен­ной­ или сохра­няе­те­ его в какую­-либо­ структу­ру­ дан­ ных, туда,­ на самом­ деле,­ запи­сы­ва­ет­ся­ указа­тель­. Когда­ вы запра­ши­­ ваете­ содер­жи­мое­ какой­-либо­ структу­ры­ данных­ или значе­ние­ пере­мен­­ ной, Лисп возвра­ща­ет­ данные,­ на кото­рые­ ссыла­ет­ся­ указа­тель­. Но это проис­хо­дит­ неяв­но,­ поэто­му­ вы може­те­ запи­сы­вать­ значе­ния­ в структу­­ ры или «в» пере­мен­ные,­ не заду­мы­ва­ясь­ о том, как это проис­хо­дит­.

Из сооб­ра­же­ний­ произ­во­ди­тель­но­сти­ Лисп взамен­ указа­те­лей­ иногда­ ис­ пользу­ет­ непо­сред­ст­вен­ное­ представ­ле­ние­ данных­. Напри­мер,­ неболь­­ шие целые­ числа­ зани­ма­ют­ не больше­ места,­ чем указа­тель,­ поэто­му­ неко­то­рые­ реали­за­ции­ Лиспа­ опери­ру­ют­ непо­сред­ст­вен­­но целы­ми­ чис­ лами,­ а не указа­те­ля­ми­ на них. Но для програм­ми­ста­ важ­но лишь то, что в Лиспе­ по умолча­нию­ можно­ поло­жить­ что угодно­ куда­ угодно­. И если­ вы явно­ не объя­ви­ли­ обрат­но­го,­ то мож­но запи­сать­ любой­ вид объек­та­ в любую­ структу­ру­ данных,­ включая­ и саму­ структу­ру­ в себя­.

x =

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a

 

 

 

 

b

 

 

 

 

c

y = nil

Рис. 3.5. Резуль­тат­ копи­ро­ва­ния­

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