Smalltalk по-русски
Advertisement
9.0

Протокол для всех наборов[]

9.diagramma

Набор это группа объектов. Эти объекты называются элементами набора. Например, Ряд это набор. Ряд

#( 'слово' 3 5 $Г #( 1 2 3 ) )

это набор пяти элементов. Первый элемент это Цепь, второй и третий это Малые целые, четвёртый элемент это Знак и пятый элемент это Ряд. Первый элемент, Цепь, также является набором; в данном случае это набор Знаков.

Наборы предоставляют простейшие структуры данный для программирования в системе Смолток. Элементы одних наборов неупорядоченны а элементы других наборов упорядочены. Набор с неупорядоченными элементами Мешок позволяет дублирование элементов а Множество не позволяет дублирование. Также есть Словари которые связывают пары объектов. Некоторые наборы с упорядоченными элементами задают порядок при добавлении элементов (Упорядоченный набор, Ряд, Цепь) а другие определяют порядок на основании самих элементов (Сортированный набор). Например, обычные структуры данных такие как ряды и цепи предоставляются классами которые связывают целый номер с элементом и которые внешний порядок соответствующий порядку номеров.

Эта глава вводит протокол для всех наборов. Каждое сообщение описанное в этой главе понимается любым видом набора, если только набор не запрещает его. Описание каждого вида набора представлено в следующей главе.

Наборы поддерживают четыре категории сообщений доступа к элементам:

  • сообщения для добавления нового элемента
  • сообщения для удаления элементов
  • сообщения для проверки присутствия элемента
  • сообщения для перебора элементов

Можно добавить или удалить из набора один или несколько элементов. Можно проверить пуст ли набор или включает ли он некоторый элемент. Также можно определить количество включений элемента в набор. Перебор позволяет получить доступ к элементам без удаления их из набора.

Добавление, удаление и проверка элементов[]

Основной протокол для всех наборов определяется суперклассом всех классов наборов, именуемым Набор. Класс Набор это подкласс класса Объект. Протокол для добавления, удаления и проверки элеметов:

Протокол экземпляров Набора
добавление
добавить: новый объект Включает аргумент, новый объект, как элемент получателя. Возвращает новый объект.
добавить все: набор Включает все элементы аргумента, набор, как элементы получателя. Возвращает набор.
удаление
удалить: старый объект Удаляет аргумент, старый объект, из элементов получателя. Возвращает старый объект, если у получателя не элементов равных старому объекту, то возникает ошибка.
удалить: старый объект если нету: блок исключение Удаляет аргумент, старый объект, из элементов получателя. Если несколько элементов равны старому объекту, то удаляется только один. Если нет ни одного элемента равного старому объекту, то возвращается значение выполнения блока исключения. Иначе возвращается старый объект.
удалить все: набор Удаляет каждый элемент аргумента, набор, из получателя. Если удаление успешно для каждого элемента аргумента, то возвращается аргумент, набор. Иначе возникает ошибка удаления.
проверки
содержит: объект Отвечает равен ли аргумент, объект, хотя бы одному элементу получателя.
пустой Отвечает не содержит ли получатель элементы.
вхождений: объект Отвечает сколько элементов получателя равно аргументу, объект.

Чтобы показать использование этих сообщений введём набор лотерея а

(272 572 852 156)

и набор лотерея б

(572 621 274)

Будем подразумевать что эти два набора, представляющие числа выбранные в лотерее, являются экземплярами Мешка, подкласса Набора. Сам Набор это абстрактный класс в том смысле что он описывает протокол для всех наборов. Набор не предоставляет достаточного представления для сохранения элементов, поэтому нельзя реализовать в Наборе всех его сообщений. Из за незавершённости определения Набора нет смысла создавать экземпляры Набора. Мешок это конкретный класс в том смысле что он представляет возможность запоминания элементов и реализует сообщения которые невозможно реализовать в его суперклассах.

Все наборы отвечают на сообщение размер которое возвращает количество элементов набора. Поэтому можно определить что

лотерея а размер

это 4 и

лотерея б размер

это 3. Выполнив предложения в следующем порядке получим:

предложение результат лотерея а если она изменилась
лотерея а пустой. ложь
лотерея а содержит: 572. истина
лотерея а добавить: 596. 596 Мешок (272 572 852 156 596)
лотерея а добавить все: лотерея б. Мешок (572 621 274) Мешок (272 274 852 156 596 572 572 621)
лотерея а вхождений: 572. 2
лотерея а удалить: 572. 572 Мешок (272 274 852 156 596 572 621)
лотерея а размер. 7
лотерея а удалить все: лотерея б. Мешок (572 621 274) Мешок (272 852 596 156)
лотерея а размер. 4

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

Блоки были введены в главе 2. Сообщение удалить: старый объект если нету: блок исключение - использует блок для определения поведения набора при возникновении ошибки. Аргумент блок исключение выполняется если объект на который ссылается переменная старый объект не является элементом набора. Этот блок может содержать текст обрабатывающий ошибки или просто игнорирующий её. Например, предложение

лотерея а удалить: 122 если нету: [].

ничего не делает когда определяется что 121 не является элементом лотереи а.

Поведение по умолчанию для сообщения удалить: это уведомление об ошибке при помощи посылки сообщения ошибка: 'объект не содержится в наборе'. (Вспомните что сообщение ошибка: определено в протоколе для всех объектов и поэтому понятно любому набору).

Перебор элементов[]

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

   количество0.
   буквы
      делать: [
         :каждый |
         каждый как маленькая буква =
            истина: [ количествоколичество + 1. ]. ].

Эти предложения увеличивают счётчик, количество, на единицу для каждого элемента который является маленькой или большой буквой а. Желаемый результат это конечное значение переменной количество.

В протоколе всех наборов определено шесть расширений основного сообщения перебора. Описание этих сообщений перечислений указывает что создаётся "новый набор подобный получателю" для сбора результирующей информации. Эта фраза означает что новый набор это экземпляр того же класса что и класс получателя. Например, если получатель сообщения выбрать: это Множество или Ряд, то ответ это соответственно новое Множество или Ряд. Единственное исключение в системе Смолток для этих сообщений есть в реализации класса Интервал, который возвращает новый Упорядоченный набор, а не новый Интервал. Причиной данного исключения является способ создания интервала, элементы интервала создаются вместе с созданием экземпляра Интервала, нельзя поместить элементы в существующий интервал.

Протокол экземпляров Набора
перебор
делать: блок Выполняет аргумент, блок, для каждого элемента получателя.
выбрать: блок Выполняет аргумент, блок, для каждого элемента получателя. Собирает в новый набор подобный получателю только те элементы для которых блок возвратил истину. Возвращает новый набор.
отбросить: блок Выполняет аргумент, блок, для каждого элемента получателя. Собирает в новый набор подобный получателю только те элементы для который блок возвратил ложь. Возвращает новый набор.
собрать: блок Выполняет аргумент, блок, для каждого элемента получателя. Возвращает новый набор подобный получателю содержащий значения возвращённые блоком при каждом его выполнении.
выявить: блок Выполняет аргумент, блок, для каждого элемента получателя. Возвращает первый элемент для которого блок возвратил истину. Если блок ни разу не возвратил истину, то выводится сообщение об ошибке.
выявить: блок если ни одного: блок исключение Выполняет аргумент, блок, для каждого элемента получателя. Возвращает первый элемент для которого блок возвратил истину. Если блок ни разу не возвратил истину, то выполняется аргумент, блок исключение. Блок исключение должен быть блоком без аргументов.
ввести: это значение в: бинарный блок Выполняет аргумент, бинарный блок, один раз для каждого элемента получателя. У блока есть два аргумента: второй аргумент это элемент получателя; первый аргумент это значение предыдущего выполнения блока, начальное значение этого аргумента равно аргументу, это значение. Возвращает конечное значение блока.

Каждое сообщение перебора предоставляет краткий способ выражения последовательности сообщения для проверки или сбора информации об элементах набора.

Выбор и отбрасывание[]

Можно определить сколько раз встречается буква а или А используя сообщение выбрать:.

(буквы выбрать: [ :каждый | каждый как маленькая буква = $a. ]) размер.

Это предложение создаён рабор содержащий только буквы а или А, и затем возвращает размер набора результата.

Так же можно определить количество букв а или А используя сообщение отбросить:.

(буквы отбросить: [ :каждый | каждый как маленькая буква ~= $a. ]) размер.

Здесь создаётся набор из элементв которы не являются буквой а или А, и затем возвращается размер набора результата.

Выбор между выбрать: и отбросить: должен основываться на лучшем выражении для условия выбора. Если условие выбора лучше записывается в терминах принятия элемента то проще использовать выбрать:, если условие выбора проще записывается в терминах отклонения то проще использовать отбросить:. В данном примере подходящим сообщением является выбрать:.

Другой пример, допустим служащие это набор работников, каждый из которых отвечает на сообщение оклад. Чтобы получить набор служащих с окладом по крайней мере 10000 $ нужно использовать:

служащие выбрать: [ :каждый | каждый оклад >= 10000. ].

или

служащие отбросить: [ :каждый | каждый оклад < 10000. ].

Получающиеся наборы будут одинаковыми. Выбор сообщения выбрать: или отбросить: зависит от способа которым программист хочет записать условие "по крайней мере 10000 $".

Собирание[]

Допустим нужно создать новый набор в котором собраны оклады каждого работника из набора сотрудники.

служащие собрать: [ :каждый | каждый оклад. ].

Набор результат имеет то же размер что и набор служащие. Каждый элемент нового набора это оклад соответствующего элемента набора служащие.

Выявление[]

Допустим нужно в наборе служащие найти работника с окладом болье чем 20000 $. Выражение

служащие выявить: [ :каждый | каждый оклад > 20000. ]

вернёт такого работника, если он существует. Если он не существует, то будет послано сообщение ошибка: ' в наборе нет объекта'. Так же как и для сообщения удаления, программист может указать поведение при неудаче сообщеня выявить:. Следующее выражение возвращает работника чей оклад превышает 20000 $, или, если такого не существует, возвращает пусто.

   служащие
      выявить: [ :каждый | каждый оклад > 20000. ]
      если ни одного: [ пусто. ]

Ввод значения[]

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

   служащие
      ввести: 0
      в: [ :подсумма :след работник | подсумма + след работник оклад. ].

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

Используя сообщение ввести:в:, программист может задать временную переменную и избежать создания объекта в котором собираются результаты. Например, в вышеприведённом примере вычисления количества букв а или А в наборе буквы мы использовали счётчик, количество.

   количество0.
   буквы
      делать: [
         :каждый |
         каждый как маленькая буква =
            истина: [ количествоколичество + 1. ]. ].

Альтернативным способом подсчёта является использование сообщения ввести:в:. В предложениях примера результат накапливается в переменной количество, количество вначале равно 0. Если следующая буква это а или А, то к количеству добавляется 1, иначе добавляется 0.

   буквы
      ввести: 0
      в: [
         :количество :след элемент |
         количество
            + (след элемент как маленькая буква = истина: [ 1. ] ложь: [ 0. ]). ].

Создание экземпляров[]

В начале этой главы, были приведены примеры в которых наборы были записаны при помощи литералов. Эти наборы были Рядами и Цепями. Например, выражение для создания рада

#('первый' 'второй' 'третий')

где каждый элемент это Цепь записаная литерально.

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

Протокол класса Набор
создание экземпляра
с: объект Возвращает экземпляр набора содержащий объект.
с: первый объект с: второй объект Возвращает экземпляр набора содержащий первый объект и второй объект в качестве элементов.
с: первый объект с: второй объект с: третий объект Возвращает экземпляр набора содержащий первый объект, второй объект и третий объект в качестве элементов.
с: первый объект с: второй объект с: третий объект с: четвёртый объект Возвращает экземпляр набора содержащий первый объект, второй объект, третий объект и четвёртый объект в качестве элементов.

Например, Множество это подкласс Набора. Чтобы создать новое Множество с тремя элементами буквами s, e и t, нужно выполнить предложение.


Множество с: $s с: $e с: $t.

Преобразование различных классов наборов[]

Полное описание и понимание допустимых преобразований между видами наборов зависит от представления всех подклассов Набора. Здесь только указывается что в протоколе преобразования для всех наборов есть пять сообщений для преобразования получателя в Мешок, Множество, Упорядоченный набор и Сортированный набор. Эти сообщения определены в классе Набор потомучто можно преобразовать любой набор в любой из этих пяти видов наборов. Порядок элементов для любого неупорядоченного набора, при преобразовании в набор с упорядоченными элементами, произволен.

Протокол экзэмпляров Набора
преобразование
как мешок Возвращает мешок с теми же элементами что и у получателя.
как множество Возвращает множество с теми же элементами что и у получателя (однако любые повторения игнорируются).
как упорядоченный набор Возвращает Упорядоченный набор с теми же элементами что и у получателя (возможно произвольное упорядчивание).
как сортированный набор Возвращает Сортированный набор с теми же элементами что и у получателя, отсортированными так что каждый элемент меньше чем либо равен (<=) следующего.
как сортированный набор: блок Возвращает Сортированный набор с теми же элементами что и у получателя, отсортированными в соответствии с аргументом блок.

Поэтому если лотерея а это Мешок содержащий элементы

272 572 852 156 596 272 572 то

лотерея а как множество.

это Множество содержащее элементы

852 596 156 572 272

и

лотерея а как сортированный набор.

это Сортированный набор содержащий элементы в таком порядке

156 272 272 572 572 596 852

Advertisement