Ruby/Подробнее об ассоциативных массивах: различия между версиями
Содержимое удалено Содержимое добавлено
Byzantine (обсуждение | вклад) м орфография, викификатор |
DannyS712 (обсуждение | вклад) м <source> -> <syntaxhighlight> (phab:T237267) |
||
Строка 10:
Давайте создадим хеш, где в качестве ключа будем использовать целое число:
<
hash[5] #=> 3
hash[2] #=> nil это значит, что объект отсутствует
hash[3] #=> 2</
А вот так будет выглядеть та же самая программа, если мы будем использовать массив:
<
array[5] #=> 3
array[2] #=> nil
array[3] #=> 2</
'''Первый случай применимости хеша:''' если в массиве намечаются обширные незаполненные (то есть заполненные <code>nil</code>) области, то целесообразнее использовать хеш с целочисленным индексом.
Строка 28:
Продолжим поиски случаев применимости хеша и на этот раз подсчитаем, сколько раз каждое число повторяется в данном целочисленном массиве. Решение массивом:
<
array.uniq.map{ |i| [i, array.find_all{ |j| j == i }.size] } #=> [[1, 3], [2, 4], [3, 1], [4, 1], [5, 1]]</
Алгоритм получается ужасным. Не буду утомлять излишними терминами, а замечу, что по одному и тому же массиву итераторы (в количестве двух штук) пробегают много раз. А ведь достаточно одной «пробежки». Понятное дело, что такая программа не сделает вам чести. В качестве упражнения, предлагаю вам решить эту задачу другим, более оптимальным, способом.
Строка 35:
Теперь рассмотрим решение этой же задачи, но с применением хеша:
<
array = [1, 2, 1, 2, 3, 2, 1, 2, 4, 5]
array.inject(Hash.new{ 0 }){ |result, i|
result[i] += 1
result
} #=> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}</
Удалось избавиться от лишних методов и обойтись лишь одной «пробежкой» итератора по массиву.
Строка 54:
Теперь представим, что мы работаем системными администраторами. У нас есть список [[w:DNS|DNS]]-имён и [[w:IP-адрес|IP-адреса]]. Каждому DNS-имени соответствует только один IP-адрес. Как нам это соответствие записать в виде программы? Попробуем это сделать при помощи массива:
<
["comp2.mydomen.ru", "192.168.0.1"],
["comp3.mydomen.ru", "192.168.0.2"]]</
Всё бы ничего, но чтобы найти IP-адрес по DNS имени, придётся перелопатить весь массив в поиске нужного DNS:
<
array.find_all{ |key, value| key == dns_name }[0][-1] #=> "192.168.0.3"</
В данном примере было использовано два интересных приёма:
Строка 67:
* Если в двумерном массиве заранее известное количество столбцов (в нашем случае — два), то каждому из столбцов (в рамках любого итератора) можно дать своё имя (в нашем случае: <code>key</code> и <code>value</code>). Если бы мы такого имени не давали, то вышеописанное решение выглядело бы так:
<
Без именования столбцов, внутри итератора вы будете работать с массивом (в двумерном массиве каждый элемент — массив, а любой итератор «пробегает» массив поэлементно). Это высказывание действительно, когда «пробежка» осуществляется по двумерному массиву.
Строка 75:
Теперь ту же самую задачу решим, используя хеш:
<
"comp2.mydomen.ru"=>"192.168.0.1",
"comp3.mydomen.ru"=>"192.168.0.2"}
hash["comp1.mydomen.ru"] #=> "192.168.0.3"</
Нет ни одного итератора и, следовательно, не сделано ни одной «пробежки» по массиву.
Строка 88:
В заключении, как и было обещано, приводится решение задачи с использованием метода <code>.update</code>:
<
array.inject({}){ |result, i| result.update({ i=>1 }){ |key, old, new| old+new }}
#=> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}</
Описание метода <code>.update</code> будет дано ниже. На данном этапе попытайтесь угадать принцип работы метода <code>.update</code>.
Строка 99:
Если состояние объектов-ключей изменилось, то хешу необходимо вызвать метод <code>.rehash</code>.
<
array2 = ["в", "г"]
hash = {array1=>100, array2=>300}
Строка 106:
hash[array1] #=> nil
hash.rehash #=> {["я", "б"]=>100, ["в", "г"]=>300}
hash[array1] #=> 100</
В данном примере ключами хеша (<code>hash</code>) являются два массива (<code>array1</code> и <code>array2</code>). Одному из них (<code>array1</code>) мы изменили нулевой элемент (с <code>"а"</code> на <code>"я"</code>). После этого доступ к значению был потерян. После выполнения метода <code>.rehash</code> всё встало на свои места.
Строка 112:
Как Ruby отслеживает изменение ключа в ассоциативном массиве? Очень просто: с помощью метода <code>.hash</code>, который генерирует «контрольную сумму» объекта в виде целого числа. Например:
<
=== Способы создания ассоциативного массива ===
Строка 126:
Положим, что у нас в наличии индексный массив, где ключ и значение записаны последовательно. Тогда мы можем использовать связку методов <code>*</code> и <code>Hash[]</code>:
<
Hash[*array] #=> {1=>4, 5=>3, 2=>2}</
Элементы, стоящие на ''нечётной'' позиции (в данном случае: 1, 5 и 2) стали ключами, а элементы, стоящие на ''чётной'' позиции (то есть: 4, 3 и 2), стали значениями.
Строка 134:
Если повезло меньше и нам достался двумерный массив с элементами вида <code>[["ключ_1", "значение_1"], ["ключ_2", "значение_2"], ["ключ_3", "значение_3"], …]</code>, то его надо сплющить (<code>.flatten</code>) и тем задача будет сведена к предыдущей:
<
Hash[*array.flatten] #=> {1=>4, 5=>3, 2=>2}</
Каждый нулевой элемент подмассива станет ключом, а каждый первый — значением.
Строка 141:
Но может случиться так, что двумерный массив будет состоять из двух подмассивов: подмассива ключей и подмассива значений:
<
Вспоминаем методы работы с массивами. Там был метод <code>.transpose</code> (транспонирование массива), вызов которого сведёт задачу к предыдущей.
<
Hash[*array.transpose.flatten] #=> {1=>4, 5=>3, 2=>2}</
==== Нет данных ====
Если нет данных, то лучше записать хеш как пару фигурных скобок:
<
hash[1] = 4
hash[5] = 3
hash[2] = 2
hash #=> {1=>4, 5=>3, 2=>2}</
И уже по ходу дела разобраться, что к чему.
Строка 162:
Сведения о типе значений использовать следует так: создать хеш, в котором будет определён элемент по умолчанию. Элементом по умолчанию должен быть нулевой элемент соответствующего типа, то есть для строки это будет пустая строка (<code>""</code>), для массива — пустой массив (<code>[]</code>), а для числа — нуль (<code>0</code> или <code>0.0</code>). Это делается, чтобы к пустому элементу можно было что-то добавить и при этом не получить ошибку.
<
hash["песенка про зайцев"] += "В тёмно-синем лесу, "
hash["песенка про зайцев"] += "где трепещут осины"
hash #=> {"песенка про зайцев"=>"В темно-синем лесу, где трепещут осины"}</
Или ещё пример:
<
hash["зарплата"] += 60
hash["зарплата"] *= 21
hash #=> {"зарплата"=>1260}</
Но, как известно, из любого правила есть исключение: использовать нулевой элемент, когда значение будет записываться умножением, нежелательно. Потому как, даже не будучи пророком, можно предсказать результат. Он будет равен нулю.
Строка 179:
Если вам изначально известны все ключи и значения, то и записывайте их сразу в виде хеша, одним из способов:
<
{"март"=>400, "январь"=>350, "февраль"=>200} #=> на выходе такой же текст
{fox: 1, wolf: 2, dragon: 3} #=> {:fox=>1, :wolf=>2, :dragon=>3} обратите внимание на знак ':', он говорит что fox - это не строка,
# а чтото вроде перечисления (Enum), как в языке Си.
</syntaxhighlight>
Не изобретайте [[w:Велосипед|велосипед]] и поступайте как можно проще.
Строка 193:
Для получения отдельно массива ключей или значений существуют методы <code>.keys</code> и <code>.values</code>.
<
{1=>4, 5=>3, 2=>2}.values #=> [4, 3, 2]</
Ассоциативные массивы в Ruby неупорядоченны: массивы могут иметь любой порядок элементов.
Строка 201:
Чтобы поменять местами ключи и значения ассоциативного массива, следует применять метод <code>.invert</code>. Этот метод возвращает ассоциативный массив с ключами, заменёнными значениями, и значениями, заменёнными ключами.
<
hash.invert #=> {4=>"первый ключ", 5=>"второй ключ"}</
Поскольку ключи в ассоциативных массивах уникальны, то ключи с одинаковыми значениями будут отброшены:
<
hash.invert #=> {10=>"второй ключ"}</
Небольшая хитрость: <code>hash.invert.invert</code> возвратит нам хеш с уникальными значениями.
Строка 214:
Что вы делаете, если хотите обновить какую-то программу или игру? Правильно, устанавливаете апдейт. Вы не поверите, но для обновления значения в ассоциативном массиве используется метод <code>.update</code>. Странно, да? Пример использования этого метода в «боевых» условиях мы уже приводили в начале раздела. Если вы помните, то мы считали, ''сколько раз повторяется каждое число''. Наверняка, вы немного подзабыли его решение (у программистов есть привычка не помнить [[w:Константа|константы]]). Позволю себе его вам напомнить:
<
array = [1, 2, 1, 2, 3, 2, 1, 2, 4, 5]
array.inject({}){ |result, i| result.update({i=>1}){ |key, old, new| old + new } }
#=> {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}</
Страшноватая запись. Поэтому будем разбирать её по частям.
<
Сразу после названия метода (в нашем случае <code>.update</code>) идёт передача параметра. Страшная запись <code>{i=>1}</code> — это не что иное, как ещё один хеш. Ключ его хранится в переменной <code>i</code> (счётчик итератора <code>.inject</code>), а в качестве значения выбрана единица. Зачем? Расскажу чуть позже.
Строка 231:
Здесь вроде бы все понятно. Запись стала менее страшной, но всё равно вызывает дрожь. Будем это исправлять!
<
Раньше мы не встречались с такой записью. Но ничего страшного в ней нет. Это что-то вроде ''по́ля боя''. Нам выдали вооружение и необходимо провести некий манёвр. В нашем случае, [[w:Арсенал|арсенал]] у нас внушительный: <code>key</code>, <code>old</code> и <code>new</code>. Бой начинается при некоторых условиях. Наш бой начнется, когда при добавлении очередной пары (переданной в предыдущей части страшной записи) обнаружится, что такой ключ уже есть в хеше. Нам предлагается описать наши действия именно в таком случае. Что же это за действия?
<
Всего лишь сложение <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}} — новый).
Строка 248:
Для слияния двух массивов используется метод <code>.merge</code> или <code>.merge!</code>:
<
hash1 = {3 => "a", 4 => "c"}
hash2 = {5 => "r", 7 => "t"}
hash1.merge!(hash2) #=> {5=>"r", 7=>"t", 3=>"a", 4=>"c"}</
Если во втором массиве ключ будет совпадать с каким-либо ключем из первого массива, значение будет заменено на значение из второго массива.
Строка 258:
Ну вот, с новичками мы познакомились, теперь можно переходить к старым знакомым. Помните, как мы находили размер массива? Вот и с хешами точно также:
<
hash = {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}
hash.size #=> 5</
Стоит уточнить, что если в индексных массивах под размером понимается количество элементов, то в ассоциативном массиве это количество пар вида <code>ключ=>значение</code>. В остальном же это наш старый добрый <code>.size</code>.
Строка 267:
О том, как добавлять элементы в массив мы знаем, а вот про удаление — нет. Необходимо это исправить. Чем мы сейчас и займёмся.
<
hash = {5=>1, 1=>3, 2=>4, 3=>1, 4=>1}
hash.delete(5) #=> 1
hash #=> {1=>3, 2=>4, 3=>1, 4=>1}
hash.delete(5) #=> nil</
Как вы, наверно, уже догадались, удалением пары по ключу занимается метод <code>.delete</code>. Ему передаётся ключ от пары, которую следует удалить.
Строка 284:
Давайте посмотрим его в действии:
<
hash.shift #=> [5, 3]
hash #=> {1=>6, 3=>2}</
Обратите внимание, что метод <code>.shift</code> возвращает удаляемую пару в виде индексного массива <code>[ключ, значение]</code>.
Строка 301:
Чтобы преобразовать ассоциативный массив в индексный, надо использовать метод <code>to_a</code>. Его используют все, кто не может запомнить методов работы с хешами.
<
hash.to_a #=> [["гаечный ключ", 10], ["разводной ключ", 22]]</
Способ преобразования таков. Сперва пары (ключ=>значение) преобразуются в массив:
<
Затем «стрелку» заменяем на запятую:
<
и фигурные скобки выпрямляем, так что теперь их можно заправить в стэплер.
<
==== Упорядочение хеша ====
Да, множество пар в хеше неупорядоченно. Но это можно исправить, разве что результат потом будет не хешем, а двумерным массивом.
<
hash.sort #=> [["гаечный ключ", 4], ["разводной ключ", 10]]</
В методе <code>.sort_by</code> передаются два значения:
<
hash.sort_by{ |key, value| value } #=> [["гаечный ключ", 4], ["разводной ключ", 10]]</
Здесь мы упорядочили хеш по значению.
Строка 334:
Максимальная пара в хеше ищется точно также, как и максимальный элемент в массиве
<
hash.max #=> ["разводной ключ", 22]
hash.min #=> ["гаечный ключ" , 10]</
но с небольшими особенностями:
Строка 345:
Несколько больше возможностей приобрели методы <code>max_by</code> и <code>min_by</code>:
<
hash = {"гаечный ключ"=>10, "разводной ключ"=>22}
hash.max_by{ |key, value| value } #=> ["разводной ключ", 22]
hash.min_by{ |array| array[0] } #=> ["гаечный ключ" , 10]</
Также, как и в методе <code>sort_by</code> есть возможность по разному получать текущую пару: в виде массива или двух переменных.
Строка 374:
Зададим вопрос «Хеш пустой?», но используя известный нам [[w:Лексикон|лексикон]]. Для начала спросим «Пустой хеш тебе не брат-близнец?»
<
filled_hash = {"гаечный"=>20, "замочный"=>"английский", "разводной"=>34}
empty_hash == {} #=> true
filled_hash == {} #=> false</
Можно спросить по другому: «Размер у тебя не нулевой?»
<
empty_hash = {}
filled_hash = {"гаечный"=>20, "замочный"=>"английский", "разводной"=>34}
empty_hash .size.zero? #=> true
filled_hash.size.zero? #=> false</
Но давайте будем задавать правильные вопросы
<
empty_hash = {}
filled_hash = {"гаечный"=>20, "замочный"=>"английский", "разводной"=>34}
empty_hash .empty? #=> true
filled_hash.empty? #=> false</
а то ещё примут нас за приезжих…
Строка 405:
Если вам нужно узнать у хеша ответ на вопрос «есть у тебя такой ключ?», но вы не знаете как это правильно спросить, то скорее всего вы зададите вопрос в два этапа: «какие ключи у тебя есть?» и «есть среди них такой ключ?»
<
pocket.keys.include?("гаечный") #=> true</
В данном примере у нас в <code>pocket</code> нашёлся <code>"гаечный"</code> ключ.
Строка 412:
Но лучше задавать вопрос напрямую, это покажет ваше прекрасное знание языка.
<
pocket.key?("гаечный") #=> true</
или в стиле индексных массивов
<
pocket.include?("гаечный") #=> true</
Это несколько сократит первоначальное предложение, но тогда можно перепутать хеш с массивом.
Строка 427:
Давайте подумаем, как задать вопрос «есть такое значение?» хешу. Скорее всего, мы опять зададим вопрос в два этапа: «какие значения есть?» и «есть ли среди них нужное нам?»
<
pocket.values.include?("гаечный") #=> false — ой, забыл сменить
pocket.values.include?("английский") #=> true</
Но [[w:Коренные народы|аборигены]] говорят иначе и задают вопрос напрямую:
<
pocket.value?("английский") #=> true</
Задать вопрос «Есть такое значение?» можно не только при помощи метода <code>.value?</code>, но и при помощи более длинного <code>.has_value?</code>.
Строка 454:
Рассматривать заново работу каждого итератора в отдельности скучно. Поэтому мы будем рассматривать работу всех итераторов сразу.
<
hash.find_all{ |array| array[1] < 5 }
Строка 463:
hash.inject(0){ |result, array| result + array[1] }
#=> 14</
Обратите внимание на то, что в качестве счётчика передаётся массив из двух элементов. В наших примерах счётчик итератора мы назвали <code>array</code>. В своих программах вы вольны называть его как угодно.
Строка 471:
Теперь посмотрим, как можно развернуть <code>array</code> в две переменные. Делается это простой заменой <code>array</code> на <code>key, value</code>:
<
hash.find_all{ |key, value| value < 5 }
Строка 480:
hash.inject(0){ |result, key, value| result + value }
#=> Ошибка в методе "+": невозможно сложить nil и число типа Fixnum</
Обратите внимание, что развёртка массива прошла успешно только в первых двух итераторах. В третьем возникла ошибка. Давайте выясним, откуда там взялся <code>nil</code>. Дело в том, что развернуть массив не удалось, и теперь он стал называться не <code>array</code>, а <code>key</code>. Переменная <code>value</code> осталась «не у дел», и ей присвоилось значение <code>nil</code>. Чтобы это исправить, достаточно поставить круглые скобки:
<
#=> 14</
Ассоциативный массив, как и индексный массив, имеет метод <code>.map</code>, который передаёт замыканию ключ и соответствующее ему значение. При этом в замыкание на самом деле передаётся массив с ключом и значением, но Ruby «разворачивает» их в две переменные при передаче замыканию.
Строка 491:
Итератор <code>.map</code>, в свою очередь, возвращает индексный массив с результатами замыкания — по элементу массива на каждый ключ:
<
hash = {"гаечный ключ"=>4, "разводной ключ"=>10}
hash.map { | key, value | "#{key} на #{value}" } #=> ["гаечный ключ на 4", "разводной ключ на 10"]
hash.map #=> [["гаечный ключ", 4], ["разводной ключ", 10]]</
Итератор <code>.map</code>, вызванный без аргументов, аналогичен методу <code>.to_a</code>: просто раскладывает хеш в двумерный массив.
Строка 501:
Одному программисту надоело писать <code>hash["key"]</code> и он захотел сделать так, чтобы можно было написать <code>hash.key</code>.
<
class Hash
def method_missing(id)
Строка 510:
hash = {"hello"=>"привет", "bye"=>"пока"}
hash.hello #=> "привет"
hash.bye #=> "пока"</
Естественно, что ключи в таком хеше могут содержать только латиницу, символ подчёркивания и цифры (везде, кроме первого символа). Иначе говоря, удовлетворять всем требованиям, которые мы предъявляем к именам методов и именам переменных.
|