Ruby/Подробнее об ассоциативных массивах: различия между версиями

м
орфография, викификатор
м (орфография, викификатор)
== Подробнее об ассоциативных массивах ==
 
Различают два типа массивов: индексные, у которых в качестве индекса только целое число, и ассоциативные, где индексом может быть любой объект.
 
'''Первый случай применимости хеша:''' если в массиве намечаются обширные незаполненные (то есть заполненные <code>nil</code>) области, то целесообразнее использовать хеш с целочисленным индексом.
 
Использовать хеш в данном случае лучше потому, что, формально, хеш для данного примера состоит из трёх значащих пар, а массив  — из шести элементов, из которых лишь три элемента значащие. Исходя из этого, можно заключить, что массив будет хранить избыточную информацию, а хеш  — только нужную.
 
Продолжим поиски случаев применимости хеша и на этот раз подсчитаем, сколько раз каждое число повторяется в данном целочисленном массиве. Решение массивом:
В данном примере было использовано два интересных приёма:
 
* Если в двумерном массиве заранее известное количество столбцов (в нашем случае  — два), то каждому из столбцов (в рамках любого итератора) можно дать своё имя (в нашем случае: <code>key</code> и <code>value</code>). Если бы мы такого имени не давали, то вышеописанное решение выглядело бы так:
 
<source lang="ruby">array.find_all{ |array| array[0] == dns_name }[0][-1] #=> "192.168.0.3"</source>
 
Без именования столбцов, внутри итератора вы будете работать с массивом (в двумерном массиве каждый элемент  — массив, а любой итератор «пробегает» массив поэлементно). Это высказывание действительно, когда «пробежка» осуществляется по двумерному массиву.
 
* Метод <code>.find_all</code> возвращает двумерный массив примерно следующего вида: <code><nowiki>[["comp1.mydomen.ru", "192.168.0.3"]]</nowiki></code>, чтобы получить строку <code>"192.168.0.3"</code> необходимо избавиться от двумерности. Делается это при помощи метода <code>[]</code>, который вызывается два раза (понижает размерность c двух до нуля). Метод <code>[0]</code> возвращает в результате  — <code>["comp1.mydomen.ru", "192.168.0.3"]</code>, а метод <code>[-1]</code>  — <code>"192.168.0.3"</code>. Как раз это нам и было нужно.
 
Теперь ту же самую задачу решим, используя хеш:
Вполне естественно, что существуют и другие случаи применимости хеша, но вероятность столкнуться с ними в реальной работе намного меньше. Вышеописанных трёх случаев должно хватить надолго.
 
В заключении, как и было обещаннообещано, приводится решение задачи с использованием метода <code>.update</code>:
 
<source lang="ruby">array = [1, 2, 1, 2, 3, 2, 1, 2, 4, 5]
 
=== Что используется в качестве ключей? ===
В качестве ключей ассоциативного массива можно использовать любые типы. Например, другие ассоциативные массивы, строки, числа, символы или просто обьектыобъекты любых классов.
 
В качестве ключей ассоциативного массива можно использовать любые типы. Например, другие ассоциативные массивы, строки, числа, символы или просто обьекты любых классов.
 
Если состояние объектов-ключей изменилось, то хешу необходимо вызвать метод <code>.rehash</code>.
В данном примере ключами хеша (<code>hash</code>) являются два массива (<code>array1</code> и <code>array2</code>). Одному из них (<code>array1</code>) мы изменили нулевой элемент (с <code>"а"</code> на <code>"я"</code>). После этого доступ к значению был потерян. После выполнения метода <code>.rehash</code> всё встало на свои места.
 
Как Ruby отслеживает изменение ключа в ассоциативном массиве? Очень просто: с помощью метода <code>.hash</code>, который генерирует «контрольную сумму» обьектаобъекта в виде целого числа. Например:
 
<source lang="ruby">[1, 2, 3].hash #=> 25</source>
 
=== Способы создания ассоциативного массива ===
 
При создании ассоциативного массива важно ответить на несколько вопросов:
 
* Какие данные имеются?
* Какого типа эти данные?
* Что будет ключом, а что  — значением?
 
Ответы определят способ создания хеша.
 
==== Из одномерного массива ====
 
Положим, что у нас в наличии индексный массив, где ключ и значение записаны последовательно. Тогда мы можем использовать связку методов <code>*</code> и <code>Hash[]</code>:
 
 
==== Из двумерного массива ====
 
Если повезло меньше и нам достался двумерный массив с элементами вида <code>[["ключ_1", "значение_1"], ["ключ_2", "значение_2"], ["ключ_3", "значение_3"], …]</code>, то его надо сплющить (<code>.flatten</code>) и тем задача будет сведена к предыдущей:
 
Hash[*array.flatten] #=> {1=>4, 5=>3, 2=>2}</source>
 
Каждый нулевой элемент подмассива станет ключом, а каждый первый  — значением.
 
Но может случиться так, что двумерный массив будет состоять из двух подмассивов: подмассива ключей и подмассива значений:
 
==== Нет данных ====
 
Если нет данных, то лучше записать хеш как пару фигурных скобок:
 
 
==== Известен только тип значений ====
Сведения о типе значений использовать следует так: создать хеш, в котором будет определён элемент по умолчанию. Элементом по умолчанию должен быть нулевой элемент соответствующего типа, то есть для строки это будет пустая строка (<code>""</code>), для массива  — пустой массив (<code>[]</code>), а для числа  — нуль (<code>0</code> или <code>0.0</code>). Это делается, чтобы к пустому элементу можно было что-то добавить и при этом не получить ошибку.
 
Сведения о типе значений использовать следует так: создать хеш, в котором будет определён элемент по умолчанию. Элементом по умолчанию должен быть нулевой элемент соответствующего типа, то есть для строки это будет пустая строка (<code>""</code>), для массива — пустой массив (<code>[]</code>), а для числа — нуль (<code>0</code> или <code>0.0</code>). Это делается, чтобы к пустому элементу можно было что-то добавить и при этом не получить ошибку.
 
<source lang="ruby">hash = Hash.new("")
 
==== Всё известно и дано ====
 
Если вам изначально известны все ключи и значения, то и записывайте их сразу в виде хеша, одним из способов:
 
 
=== Методы работы с ассоциативными массивами ===
 
Когда речь пойдёт о методах, которые присутствуют в ассоциативных массивах, то частенько будет возникать чувство [[w:Дежавю|дежавю]]. Во всяком случае, учить заново итераторы вам не придётся. Вполне естественно, что появятся новички, но их будет немного. Тем не менее, прилежный преподаватель первым делом представляет новичков группе. Поэтому и мы начнем с тех методов, которые будут необходимы нам при работе с ассоциативными массивами, но отсутствуют у индексных.
 
==== Получение массива значений и массива ключей ====
 
Для получения отдельно массива ключей или значений существуют методы <code>.keys</code> и <code>.values</code>.
 
 
==== Замена ключей на значения ====
 
Чтобы поменять местами ключи и значения ассоциативного массива, следует применять метод <code>.invert</code>. Этот метод возвращает ассоциативный массив с ключами, заменёнными значениями, и значениями, заменёнными ключами.
 
 
==== Обновление пары ====
Что вы делаете, если хотите обновить какую-то программу или игру? Правильно, устанавливаете апдейт. Вы не поверите, но для обновления значения в ассоциативном массиве используется метод <code>.update</code>. Странно, да? Пример использования этого метода в "«боевых"» условиях мы уже приводили в начале раздела. Если вы помните, то мы считали, ''сколько раз повторяется каждое число''. Наверняка, вы немного подзабыли его решение (у программистов есть привычка не помнить [[w:Константа|константы]]). Позволю себе его вам напомнить:
 
Что вы делаете, если хотите обновить какую-то программу или игру? Правильно, устанавливаете апдейт. Вы не поверите, но для обновления значения в ассоциативном массиве используется метод <code>.update</code>. Странно, да? Пример использования этого метода в "боевых" условиях мы уже приводили в начале раздела. Если вы помните, то мы считали, ''сколько раз повторяется каждое число''. Наверняка, вы немного подзабыли его решение (у программистов есть привычка не помнить [[w:Константа|константы]]). Позволю себе его вам напомнить:
 
<source lang="ruby">
<source lang="ruby">result.update({i=>1}){ |key, old, new| old + new }</source>
 
Сразу после названия метода (в нашем случае <code>.update</code>) идёт передача параметра. Страшная запись <code>{i=>1}</code>   это не что иное, как ещё один хеш. Ключ его хранится в переменной <code>i</code> (счётчик итератора <code>.inject</code>), а в качестве значения выбрана единица. Зачем? Расскажу чуть позже.
 
Не обязательно писать именно <code>{i=>1}</code>. Можно «сократить» фигурные скобки и записать <code>i=>1</code>.
 
'''Счётчик итератора'''  — это переменная в которую итератор записывает текущий элемент последовательности.
 
Здесь вроде бы все понятно. Запись стала менее страшной, но всё равно вызывает дрожь. Будем это исправлять!
<source lang="ruby">… { … old + new } …</source>
 
Всего лишь сложение <code>old</code> и <code>new</code>. Ничего не говорит? Тогда расскажу, что значат переменные <code>key</code>, <code>old</code> и <code>new</code>. В переменную <code>key</code> передаётся значение текущего ключа, в <code>old</code>  — старое значение ключа ({{англ|old}}  — старый), а в переменную <code>new</code>  — добавляемое значение ключа ({{англ|new}}  — новый).
 
Теперь переведём запись <code>old + new</code> на русский: в случае обнаружения ключа в хеше, нам необходимо сложить старое значение с новым. Если помните, то новое значение равняется единице, то есть в случае когда ключ, хранимый в <code>i</code> уже есть в хеше <code>result</code>, то к старому значению просто добавляется единица. Вот и всё… а вы боялись.
 
==== Слияние двух массивов ====
 
Для слияния двух массивов используется метод <code>.merge</code> или <code>.merge!</code>:
 
 
==== Размер ассоциативного массива ====
 
Ну вот, с новичками мы познакомились, теперь можно переходить к старым знакомым. Помните, как мы находили размер массива? Вот и с хешами точно также:
 
 
==== Удаление пары по ключу ====
О том, как добавлять элементы в массив мы знаем, а вот про удаление  — нет. Необходимо это исправить. Чем мы сейчас и займёмся.
 
О том, как добавлять элементы в массив мы знаем, а вот про удаление — нет. Необходимо это исправить. Чем мы сейчас и займёмся.
 
<source lang="ruby">
Как вы, наверно, уже догадались, удалением пары по ключу занимается метод <code>.delete</code>. Ему передаётся ключ от пары, которую следует удалить.
 
Метод <code>.delete</code> возвращает значение, которое соответствовало ключу в удаляемой паре. Если в хеше отсутствует пара с передаваемым ключом, то метод <code>.delete</code> возвращает <code>nil</code>. Напоминаем, что <code>nil</code>  — это символ пустоты.
 
==== Удаление произвольной пары ====
 
Многие программисты удивляются, когда узнаю́т, что ассоциативные массивы имеют метод <code>.shift</code>. Связано это удивление с тем, что у индексных массивов он удаляет первый элемент, возвращая его во время удаления. А вот как понять, какая пара является первой? И что такое первый в неупорядоченной последовательности пар?
 
 
==== Преобразовать в индексный массив ====
 
Чуть ранее уже говорилось, что в большинстве случаев индексные массивы удобней ассоциативных.
 
 
==== Упорядочение хеша ====
 
Да, множество пар в хеше неупорядоченно. Но это можно исправить, разве что результат потом будет не хешем, а двумерным массивом.
 
Здесь мы упорядочили хеш по значению.
 
Сначала хеш упорядочивается по ключам, а потом, в случаях равнозначных ключей при использовании <code>sort_by</code>,  — по значениям.<!-- прошу прощения за множественные правки = ) -->
 
==== Поиск максимальной/минимальной пары ====
 
Максимальная пара в хеше ищется точно также, как и максимальный элемент в массиве
 
но с небольшими особенностями:
 
* результат поиска  — массив из двух элементов вида <code>[ключ, значение]</code>,
* сначала поиск происходит по ключу, а в случае равноправных ключей при использовании <code>max_by</code> и <code>min_by</code>  — по значению.
 
Несколько больше возможностей приобрели методы <code>max_by</code> и <code>min_by</code>:
 
=== Логические методы ===
 
Работа логических методов похожа на допрос с пристрастием. Помните, как в детективах во время теста на детекторе лжи, главный герой восклицал: «Отвечать только „да“ или „нет“!» Если перевести это на язык Ruby, то это будет звучать примерно так: «Отвечать только <code>true</code> или <code>false</code>!»
 
 
==== Хеш пустой? ====
 
Зададим вопрос «Хеш пустой?», но используя известный нам [[w:Лексикон|лексикон]]. Для начала спросим «Пустой хеш тебе не брат-близнец?»
 
 
==== Есть такой ключ? ====
 
Если вам нужно узнать у хеша ответ на вопрос «есть у тебя такой ключ?», но вы не знаете как это правильно спросить, то скорее всего вы зададите вопрос в два этапа: «какие ключи у тебя есть?» и «есть среди них такой ключ?»
 
 
==== Есть такое значение? ====
 
Давайте подумаем, как задать вопрос «есть такое значение?» хешу. Скорее всего, мы опять зададим вопрос в два этапа: «какие значения есть?» и «есть ли среди них нужное нам?»
 
 
=== Итераторы ===
 
У ассоциативных массивов есть следующие итераторы:
 
* <code>.find_all</code>  — поиск всех элементов, которые удовлетворяют логическому условию;
* <code>.map</code>  — изменение всех элементов по некоторому алгоритму;
* <code>.inject</code>  — сложение, перемножение и агрегация элементов массива.
 
Набор итераторов точно такой же, как и у индексных массивов  — сказывается их родство. Вот только ведут себя они несколько иначе:
 
* Результатом является двумерный массив (как после метода <code>.to_a</code>).
Ассоциативный массив, как и индексный массив, имеет метод <code>.map</code>, который передаёт замыканию ключ и соответствующее ему значение. При этом в замыкание на самом деле передаётся массив с ключом и значением, но Ruby «разворачивает» их в две переменные при передаче замыканию.
 
Итератор <code>.map</code>, в свою очередь, возвращает индексный массив с результатами замыкания  — по элементу массива на каждый ключ:
 
<source lang="ruby">
 
=== Хитрости ===
 
Одному программисту надоело писать <code>hash["key"]</code> и он захотел сделать так, чтобы можно было написать <code>hash.key</code>.
 
 
=== Задачи ===
 
# Дан массив слов. Необходимо подсчитать, сколько раз встречается каждое слово в массиве.
 
128

правок