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

90

 

Глава 4. Специализированные структуры данных

 

 

 

 

 

(defun bst-traverse (fn bst)

 

 

(when bst

 

 

(bst-traverse fn (node-l bst))

 

 

(funcall fn (node-elt bst))

 

 

(bst-traverse fn (node-r bst))))

 

 

 

 

Рис. 4.8. Двоич­ные­ дере­вья­ поис­ка:­ обход­

 

Для этой цели­ опре­де­ле­на­ функция­ bst-traverse (рис. 4.8), кото­рая­ при­

 

меня­ет­ к каждо­му­ элемен­ту­ дере­ва­ функцию­ fn.

 

 

> (bst-traverse #’princ nums)

 

13456789

 

 

NIL

 

(Функция­ princ всего­ лишь отобра­жа­ет­ отдель­ный­ объект­.)

 

Код, представ­лен­­ный в этом разде­ле,­ служит­ осно­вой­ для реали­за­ции­

 

двоич­ных­ деревь­ев­ поиска. Веро­ят­но,­ вы захо­ти­те­ как-то усовер­шен­ст­­

 

вовать­ его в соот­вет­ст­вии­ с ваши­ми­ потреб­но­стя­ми­. Напри­мер,­ каждый­

 

узел в теку­щей­ реали­за­ции­ имеет­ лишь одно­ поле­ elt, в то время­ как

 

может­ оказать­ся­ полез­ным­ введе­ние­ двух полей ­– ключ и значе­ние­.

 

Данная­ версия­ хотя­ и не поддер­жи­ва­ет­ такую­ возмож­ность,­ но позво­­

 

ляет­ ее легко­ реали­зо­вать­.

 

Двоич­ные­ дере­вья­ поис­ка­ могут­ исполь­зо­вать­ся­ не только­ для управле­­

 

ния отсор­ти­ро­ван­ным­ набо­ром­ объек­тов­. Их при­мене­ние­ опти­маль­но,­

 

когда­ вставки­ и удале­ния­ узлов­ имеют­ равно­мер­ное­ распре­де­ле­ние­. Это

 

значит,­ что для рабо­ты,­ напри­мер,­ с очере­дя­ми,­ BST не лучший­ выбор­.

 

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

 

удале­ния­ будут­ всегда­ осуще­ст­в­лять­ся­ с конца­. Это будет­ при­водить­

 

к разба­лан­си­ров­ке­ дере­ва,­ и вместо­ ожидае­мой­ оцен­ки O(log n) мы по­

 

лучим­ O(n). Кроме­ того,­ для моде­ли­ро­ва­ния­ очере­дей­ удобнее­ исполь­зо­­

 

вать обычный­ список­ просто­ пото­му,­ что BST будет­ вести­ себя­ в конеч­­

 

ном счете­ так же, как и список­.°

4.8.Хеш-таблицы

Вглаве 3 было­ пока­за­но,­ что списки­ могут­ исполь­зо­вать­ся­ для пред­ ставле­ния­ множеств­ и отобра­же­ний­. Для доста­точ­но­ больших­ масси­вов­ данных­ (начи­ная­ уже с 10 элемен­тов)­ исполь­зо­ва­ние­ хеш-таблиц­ суще­­ ствен­­но увели­чит­ произ­во­ди­тель­ность­. Хеш-табли­цу­ мож­но создать­ с помо­щью­ функции­ make-hash-table, кото­рая­ не требу­ет­ обяза­тель­ных­ аргу­мен­тов:­

> (setf ht (make-hash-table)) #<Hash-Table BF0A96>

Хеш-табли­цы,­ как и функции,­ при печа­ти­ отобра­жа­ют­ся­ в виде­ #<...>.

4.8. Хеш-таблицы

91

Хеш-табли­ца,­ как и ассо­циа­тив­ный­ список, ­– это спо­соб ассо­ции­ро­ва­­ ния пар объек­тов­. Чтобы­ полу­чить­ значе­ние,­ связан­ное­ с задан­ным­ ключом,­ доста­точ­но­ вызвать­ gethash с этим ключом­ и табли­цей­. По умол­ чанию­ gethash возвра­ща­ет­ nil, если­ не нахо­дит­ иско­мо­го­ элемен­та­.

> (gethash ’color ht) NIL

NIL

Здесь мы впервые­ сталки­ва­ем­ся­ с важной­ особен­но­стью­ Common Lisp: выра­же­ние­ может­ возвра­щать­ несколь­ко­ значе­ний­. Функция­ gethash возвра­ща­ет­ два. Первое­ значе­ние­ ассо­ции­ро­ва­но­ с ключом­. Второе­ зна­ чение,­ если­ оно nil (как в нашем­ приме­ре),­ озна­ча­ет,­ что иско­мый­ эле­ мент не был найден­. Поче­му­ мы не можем­ судить­ об этом из перво­го­ nil? Дело­ в том, что элемен­том,­ связан­ным­ с ключом­ color, может­ оказать­ся­ nil, и gethash вернет­ его, но в этом случае­ в каче­ст­ве­ второ­го­ значе­ния ­– t.

Большин­ст­во­ реали­за­ций­ выво­дит­ после­до­ва­тель­но­ все возвра­щае­мые­ значе­ния,­ но если­ резуль­тат­ много­знач­ной­ функции­ исполь­зу­ет­ся­ дру­ гой функци­ей,­ то ей пере­да­ет­ся­ лишь первое­ значе­ние­. В разделе 5.5 объяс­ня­ет­ся,­ как полу­чать­ и исполь­зо­вать­ сразу­ несколь­ко­ значе­ний­.

Чтобы­ сопос­та­вить­ новое­ значе­ние­ како­му­-либо­ ключу,­ исполь­зу­ем­ setf вместе­ с gethash:

> (setf (gethash ’color ht) ’red) RED

Теперь­ gethash вернет­ вновь уста­нов­лен­­ное значе­ние:­

> (gethash ’color ht) RED

T

Второе­ значе­ние­ подтвер­жда­ет,­ что gethash вернул­ реаль­но­ имеющий­ся­ в табли­це­ объект,­ а не значе­ние­ по умолча­нию­.

Объек­ты,­ храня­щие­ся­ в хеш-табли­цах,­ могут­ иметь любой­ тип. Напри­­ мер, при жела­нии­ сопос­та­вить­ каждой­ функции­ ее краткое­ описа­ние­ можно­ создать­ табли­цу,­ в кото­рой­ ключа­ми­ будут­ функции,­ а значе­­ ниями ­– строки:­

>(setf bugs (make-hash-table)) #<Hash-Table BF4C36>

>(push "Doesn’t take keyword arguments. " (gethash #’our-member bugs))

("Doesn’t take keyword arguments. ")

Так как по умолчанию gethash возвра­ща­ет­ nil­, вызов­ push экви­ва­лен­тен­ setf, и мы просто­ кладем­ нашу­ строку­ на пустой­ список­. (Опре­де­ле­ние­ функции­ our-member нахо­дит­ся­ на стр. 39.)

Хеш-табли­цы­ так­же мож­но исполь­зо­вать­ вместо­ списков­ для представ­­ ления­ множеств­. Они суще­ст­вен­­но уско­ря­ют­ поиск­ значе­ний­ и их уда­ ление­ в случа­ях­ больших­ объемов­ данных­. Чтобы­ доба­вить­ элемент­

92

Глава 4. Специализированные структуры данных

в множе­ст­во,­ представ­лен­­ное в виде­ хеш-табли­цы,­ исполь­зуй­те­ setf вместе­ с gethash:

>(setf fruit (make-hash-table)) #<Hash-Table BFDE76>

>(setf (gethash ’apricot fruit) t)

T

Провер­ка­ на принад­леж­ность­ элемен­та­ множе­ст­ву­ выпол­ня­ет­ся­ с помо­­ щью gethash:

> (gethash ’apricot fruit) T

T

По умолча­нию­ gethash возвра­ща­ет­ nil, поэтому вновь создан­ная­ хештабли­­ца­ представ­ля­ет­ собой­ пустое­ множе­ст­во­.

Чтобы­ удалить­ элемент­ из множе­ст­ва,­ мож­но восполь­зо­вать­ся­ remhash:

> (remhash ’apricot fruit) T

Возвра­щая­ t, remhash сигна­ли­зи­ру­ет,­ что иско­мый­ элемент­ был найден­ и успеш­но­ удален­.

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

> (setf (gethash ’shape ht) ’spherical (gethash ’size ht) ’giant)

GIANT

> (maphash #’(lambda (k v)

(format t "~A = ~A~%" k v))

ht) SHAPE = SPHERICAL SIZE = GIANT COLOR = RED

NIL

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

Хеш-табли­цы­ могут­ нака­п­ли­вать­ любое­ коли­че­ст­во­ вхож­де­ний,­ так как способ­ны­ расши­рять­ся­ в процес­се­ рабо­ты,­ когда­ места­ для хране­ния­ элемен­тов­ пере­ста­нет­ хватать­. Задать­ исход­ную­ емкость­ табли­цы­ мож­ но с помо­щью­ ключа­ :size функции­ make-hash-table. Исполь­зуя­ этот па­ раметр,­ вам следу­ет­ помнить­ о двух вещах:­ большой­ размер­ табли­цы­ позво­лит­ избе­жать­ ее частых­ расши­ре­ний­ (это доволь­но­ затрат­ная­ про­ цеду­ра),­ одна­ко­ созда­ние­ табли­цы­ большо­го­ разме­ра­ для мало­го­ набо­ра­ данных­ приве­дет­ к необос­но­ван­ным­ затра­там­ памя­ти­. Важный­ момент:­

Итоги главы

93

пара­метр­ :size опре­де­ля­ет­ не коли­че­ст­во­ храни­мых­ объек­тов,­ а коли­­ чест­во­ пар ключ-значе­ние­. Выра­же­ние:­

(make-hash-table :size 5)

вернет­ хеш-табли­цу,­ кото­рая­ сможет­ вместить­ пять вхож­де­ний­ до того,­ как ей придет­ся­ расши­рять­ся­.

Как и любая­ структу­ра,­ подра­зу­ме­ваю­щая­ возмож­ность­ поис­ка­ элемен­­ тов, хеш-табли­ца­ может­ исполь­зо­вать­ различ­ные­ преди­ка­ты­ провер­ки­ экви­ва­лент­но­сти­. По умолча­нию­ исполь­зу­ет­ся­ eql, но также­ можно­ приме­нить­ eq, equal или equalp; исполь­зуе­мый­ преди­кат­ указы­ва­ет­ся­

спомо­щью­ ключа­ :test:

>(setf writers (make-hash-table :test #’equal) #<Hash-Table C005E6>

>(setf (gethash ’(ralph waldo emerson) writers) t)

T

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

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

Итоги главы

1.Common Lisp поддер­жи­ва­ет­ масси­вы­ не менее­ семи­ размер­но­стей­. Одно­мер­ные­ масси­вы­ назы­­вают­ся­ векто­ра­ми­.

2.Строки ­– это векто­ры­ знаков­. Знаки ­– это полно­цен­ные­ само­стоя­­ тельные­ объек­ты­.

3.После­до­ва­тель­но­сти­ включа­ют­ в себя­ списки­ и векто­ры­. Большин­­ ство­ функций­ для рабо­ты­ с после­до­ва­тель­но­стя­ми­ имеют­ аргу­мен­ты­ по ключу­.

4.Синтак­си­че­ский­ разбор­ не состав­ля­ет­ труда­ в Common Lisp благо­да­­ ря нали­чию­ множе­ст­ва­ полез­ных­ функций­ для рабо­ты­ со строка­ми­.

5.Вызов­ defstruct опре­де­ля­ет­ структу­ру­ с имено­ван­ны­ми­ поля­ми­. Это хоро­ший­ пример­ програм­мы,­ кото­рая­ пишет­ другие­ програм­мы­.

6.Двоич­ные­ дере­вья­ поиска удобны­ для хране­ния­ отсор­ти­ро­ван­но­го­ набо­ра­ объек­тов­.

7.Хеш-табли­цы ­– это эффек­тив­ный­ способ­ представ­ле­ния­ множеств­ и отобра­же­ний­.

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