Статьи

Руководство по коллекциям Ruby, II: хэши, наборы и диапазоны

collections_hashes

Первая статья этой серии была посвящена Array и основам идиоматической итерации Ruby. Массив — довольно гибкий класс, но есть более подходящие решения для конкретных ситуаций. В этой статье рассматриваются некоторые другие типы коллекций, которые поставляются с Ruby.

Хэш

Иногда вам нужно сопоставить одно значение другому. Например, вы можете сопоставить идентификатор продукта с массивом, содержащим информацию об этом продукте. Если бы идентификаторы продуктов были целыми числами, вы могли бы сделать это с массивом, но рискуя потратить много места между идентификаторами. Хэши Ruby функционируют как ассоциативные массивы, где ключи не ограничены целыми числами. Они похожи на словари Python.

Творчество

Как и массивы, хэши имеют как синтаксис инициализации литерала, так и конструктора.

>> colors = {}
>> colors['red'] = 0xff0000

>> colors = Hash.new
>> colors['red'] = 0xff0000

Как и в случае с массивами, можно создать хэш с начальными значениями. Здесь мы видим оператора idiomatic => («hash rocket»).

 >> colors = {
>>   'red' => 0xff0000,
>>   'blue' => 0x0000ff
>> } 
=> {"red"=>16711680, "blue"=>255}

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

 >> h = Hash.new
>> h[:blah]
=> nil

>> h = Hash.new(0)
>> h[:blah]
=> 0

Обратите внимание, что доступ к несуществующему ключу не создает его.

 >> h = {}
>> h.size
=> 0
>> h[:blah]
>> h.size
=> 0

делеция

Если вы хотите удалить пару ключей из хеша, вы можете использовать #delete Может быть заманчиво просто установить значение ключа равным nil, как в таблицах Lua, но ключ все равно будет частью хэша и, следовательно, будет включен в итерацию.

 >> colors['red'] = nil
>> colors.size
=> 2

>> colors.delete('red')
>> colors.size 
=> 1

итерация

Хэши повторяются как массивы, за исключением того, что в блоки передаются два значения вместо одного.

 >> hash = {"Juan" => 24, "Isidora" => 35}
>> hash.each { |name, age| puts "#{name}: #{age}" }
Juan: 24
Isidora: 35

Блочные переменные, такие как nameage Они произвольны и могут быть чем угодно, хотя рекомендуется делать их описательными.

Хэшировать ракету или не хэшировать ракету

Распространено использование символов в качестве ключей Hash, потому что они описательные, как строки, но быстрые, как целые числа.

 >> farm_counts = {
>>   :cow => 8,
>>   :chicken => 23,
>>   :pig => 11,
>> }
=> {:cow=>8, :chicken=>23, :pig=>11}

Начиная с Ruby 1.9, хэши, ключи которых являются символами, могут быть созданы без хэш-ракеты (=>), больше похожей на JavaScript или Python.

 >> farm_counts = {
>>   cow: 8,
>>   chicken: 23,
>>   pig: 11
>> }
=> {:cow=>8, :chicken=>23, :pig=>11}

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

Аргументы с ключевыми словами

Python предоставляет возможность вызывать функции с помощью аргументов ключевых слов. При использовании ключевых слов нет необходимости передавать аргументы в определенном порядке или передавать какие-либо конкретные аргументы вообще. Хотя технически Ruby не предоставляет аргументы ключевых слов, для их моделирования можно использовать хеш. Если хеш является последним аргументом в вызове метода, фигурные скобки могут быть опущены.

 >> class Person
>>   attr_accessor :first, :last, :weight, :height

>>   def initialize(params = {})
>>     @first = params[:first]
>>     @last = params[:last]
>>     @weight = params[:weight]
>>     @height = params[:height]   
>>   end
>> end

>> p = Person.new(
>>   height: 170cm,
>>   weight: 72,
>>   last: 'Doe',
>>   first: 'John'
>> )

Обратите внимание, что params = {}ArgumentError

Меньшие хэши с полями массива

У кого-то появилась блестящая идея сделать более легкий хэш из класса Array .

 $ gem install arrayfields

>> require 'arrayfields'
>> h = ArrayFields.new
>> h[:lunes] = "Monday"
>> h[:martes] = "Tuesday"
>> h.fields
=> [:lunes, :martes]
>> h.values
=> ["Monday", "Tuesday"]

Я не очень знаком с гемом arrayfields или с тем, как он применяется в различных реализациях Ruby, но он очень популярен в Ruby Toolbox, и если вы собираетесь сериализовать много данных Hash, возможно, стоит проверить.

наборы

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

В отличие от других типов коллекций, вы должны добавить оператор require, чтобы использовать класс Set.

 >> require 'set'

Кроме того, в отличие от Array и Hash, Set не имеет какого-либо специального литерального синтаксиса. Тем не менее, вы можете передать массив в Set#new

 >> s = Set.new([1,2,3])
=> #<Set: {1, 2, 3}>

В качестве альтернативы вы можете использовать Array#to_set

 >> [1,2,3,3].to_set
=> #<Set: {1, 2, 3}>

Set использует оператор <<#add#push

 >> s = Set.new
>> s << 1
>> s.add 2

Чтобы удалить элемент из набора, используйте метод #delete

 >> s.delete(1)
=> #<Set: {2}>

Как с массивом, #include? может быть использован для тестирования членства.

 >> s.include? 1
=> false
>> s.include? 2
=> true

Одна из полезных функций Set — то, что он не будет добавлять элементы, которые он уже включает.

 >> s = Set.new [1,2]
=> #<Set: {1, 2}> 
>> s.add 2
=> #<Set: {1, 2}>

Ранее я указывал, что Array может выполнять логические операции. Естественно, Сет может сделать это также.

 >> s1 = [1,2,3].to_set
>> s2 = [2,3,4].to_set

>> s1 & s2
=> #<Set: {2, 3}>

>> s1 | s2
=> #<Set: {1, 2, 3, 4}>

В отличие от Array, он также может выполнять эксклюзивные операции или операции с оператором ^

 >> [1,2,3] ^ [2,3,4]
=> NoMethodError: undefined method `^' for [1, 2, 3]:Array

>> s1 ^ s2
=> #<Set: {4, 1}>

Изменяется

Ранее в части I я указывал на диапазоны. Класс Range является своего рода квази-коллекцией. Он может повторяться, как и другие коллекции, использующие Enumerable, но он не является контейнером для произвольных элементов.

 >> r = Range.new('a', 'c')
=> 'a'..'c'
>> r.each { |i| puts i }
a
b
c

Ранее я показал, что диапазоны могут нарезать массивы или создавать индексы для их итерации.

 >> letters = [:a,:b,:c,:d,:e]
>> letters[1..3]
=> [:b, :c, :d]

>> (1..3).map { |i| letters[i].upcase }
=> [:B, :C, 😀]

В дополнение к нарезке массивов, диапазоны могут упростить логику оператора регистра.

 >> def theme(year)
>>   case year
>>     when 1970..1979 then "War Bad, Black People Not Bad"
>>     when 1980..1992 then "Cocaine, Money, and The Future"
>>     when 1993..2000 then "Gillian Anderson, Sitcoms in The FriendZone, and AOL"
>>     when 2000..2013 then "RIP, Music"
>>   end
>> end

>> theme(1987)
=> "Cocaine, Money, and The Future"

Существует также вопрос о стекопереработке о генерации случайных строк, которые получили ответы на некоторые вопросы, которые хорошо используют диапазоны.

 >> (0...10).map{ ('a'..'z').to_a[rand(26)] }.join
=> "vphkjxysly"

Вывод

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