Ruby Ха sh .Новый с блоком необходимо подробное объяснение - PullRequest
2 голосов
/ 23 января 2020

Я смотрю на решение проблемы под названием duped_index и не совсем понимаю концепцию этой конкретной переменной Ha sh .new:

def duped_index(arr)
    result = Hash.new { |hash, key| hash[key] = [] }

    arr.each_with_index do |ele, idx|
        result[ele] << idx
    end

    result.select { |alphabet, indices| indices.length > 1 }
end

p duped_index(["a", "b", "c", "c", "b", "b", "c", "d", "b"]) => # {"b"=>[1,4,5,8], "c"=>[2,3,6]}

Не могли бы вы объяснить мне, что происходит между блоком Ха sh .нов? Был бы более эффективный способ решить это упражнение в целом?

Ответы [ 5 ]

3 голосов
/ 23 января 2020

TL; DR

Значения по умолчанию - это способ объявления значения stati c или dynamici c для ключей Ha sh без необходимости явного назначения каждой клавише заранее. Прагматически, это часто используется, чтобы гарантировать, что какое-то разумное значение возвращается для всех новых ключей без необходимости явного присвоения каждому созданному ключу.

Ваш код использует блочную форму инициализации Ha sh, чтобы установить значение по умолчанию ценность. Я объясняю блочную форму ниже, а затем сопоставляю ее с двумя более простыми примерами.

Установка значения по умолчанию с помощью блока

В Ruby, объект Ha sh может быть создан в ряд разных способов. Один из способов - передать блок в Hash # new . Этот блок будет вызываться для любого ключа, который не имеет значения.

Рассмотрим пример, связанный с этим:

# define a default value using a block
h = Hash.new { |hash, key| hash[key] = [] }

# block dynamically assigns empty array
# to new keys
h.has_key? 'foo' #=> false
h['foo']         #=> []
h.has_key? 'foo' #=> true

Здесь h назначен новый Ха sh объект с блоком. Этот блок в основном назначает пустой объект Array в качестве «значения по умолчанию» для новых членов Ha sh, которым не даны явные значения. На практике это означает, что значение, возвращаемое блоком при поиске ранее неназначенного ключа, будет [].

Теперь рассмотрим:

h = Hash.new { |hash, key| hash[key] = [] }

# block does nothing for assigned keys
h.has_key? 'bar' #=> false
h['bar'] = nil
h['bar']         #=> nil
h.has_key? 'bar' #=> true

Обратите внимание, как присваивается значение ( четное ноль) устанавливает ожидаемое значение. Значение по умолчанию действительно используется только при первом доступе к ключу, который еще не существует.

Зачем использовать блок?

Объявление блока обычно более полезно, когда вы хотите вычислить значение по умолчанию во время выполнения или когда значение по умолчанию для новых ключей должно быть Dynami c. Например:

# define some variable that will change
@foo = 0

# declare a Hash that dynamically calculates
# its default value
h = Hash.new { @foo + 1 }

h['foo']  #=> 1
@foo += 1
h['bar']  #=> 2

Если вам не нужна эта дополнительная гибкость, вы могли бы с такой же легкостью передать литерал Array конструктору. Например:

# sets default values to `[]` instead of invoking
# a block each time
h = Hash.new []

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

См. Также: Hash # fetch

Другой способ получить поведение, аналогичное значению по умолчанию, состоит в использовании блочной формы Hash # fetch . Например, при наличии Ha sh без значения по умолчанию вы все равно можете объявить значение по умолчанию при поиске ключа:

h = {}
h.fetch 'foo', []
#=> []

Семантика и варианты использования для #fetch отличаются от #new, но в примере, подобном вашему, практические результаты должны быть такими же. Подход, который вы выберете, в конечном итоге будет зависеть от того, что вы пытаетесь express с помощью своего кода.

2 голосов
/ 23 января 2020

Другие ответы касались Hash.new волхвов c, так что вот минимальное решение сорта:

def duped_index(list)
  list.map.with_index.group_by(&:first).values.select do |l|
    l.length > 1
  end.map do |l|
    l.map(&:last)
  end
end

Здесь list.map.with_index преобразует список в [["a",0],["b",1],...], где это значение и индекс пара. Это сгруппировано по первому значению в ha sh вида {"a"=>[["a",0]],"b"=>[...]}, где каждая пара сгруппирована по своей первой записи.

Нас интересуют только сгруппированные values и те select те значения length, которые больше 1.

После того как это отфильтровано, удалите исходное значение и извлеките только запись last каждой пары, индекса.

Где он производит:

p duped_index(["a", "b", "c", "c", "b", "b", "c", "d", "b"])
# => [[1, 4, 5, 8], [2, 3, 6]]
2 голосов
/ 23 января 2020
arr = [1, 2, 1, 3, 2, 1]

Вы можете написать свой код без наворотов

def duped_index(arr)
  result = {}
  arr.each_with_index do |ele, idx|
    result[ele] = [] unless result.key?(ele)
    result[ele] << idx
  end
  result.select { |ele, indices| indices.length > 1 }
end

duped_index(arr)
  #=> {1=>[0, 2, 5], 2=>[1, 4]}

Другой способ заключается в создании пустых массивов на лету, по мере необходимости

def duped_index(arr)
  result = {}
  arr.each_with_index { |ele, idx| (result[ele] ||= []) << idx }
  result.select { |ele, indices| indices.length > 1 }
end

duped_index(arr)
  #=> {1=>[0, 2, 5], 2=>[1, 4]} 

Ruby синтаксический анализатор расширяет сокращение присваивание result[ele] ||= []:

result[ele] = result[ele] || = []

Если result не имеет ключа ele, result[ele] #=> nil, поэтому

result[ele] = nil || = []
  #=> []

Если result имеет ключ ele result[ele] остается без изменений. Следовательно,

(result[ele] ||= []) << idx

приводит к добавлению idx к массиву (пустому или иному), который является значением result для ключа ele.

Этот метод будет чаще пишется так:

def duped_index(arr)
  arr.each_with_index.with_object({}) { |(ele, idx), result|
       (result[ele] ||= []) << idx }.
      select { |ele, indices| indices.length > 1 }
end

Третий способ - создать га sh с про c по умолчанию, как в вопросе

Предположим:

result = Hash.new { |hash, key| hash[key] = [] }
  #=> {}

Теперь выполните следующую операцию:

result['dog'] << 'woof'
  #=> ["woof"] 
result
  #=> {"dog"=>["woof"]} 

Когда выполняется result['dog'] Ruby видит это result.key? #=> false, поэтому она выполняет блок, сначала присваивание значений переменным блока:

hash, key = [result, 'dog']
  #=> [{}, 'dog']
hash
  #=> {}
key
  #=> 'dog' 

Затем выполняется:

hash['key'] = []

, что приводит к:

result
  #=> { 'dog'=>[] }

Затем она выполняет:

result['dog'] << 'woof'
result
  #=> {"dog"=>["woof"]}

Теперь предположим, что мы выполняем:

result['dog'] << 'I love kibble!'
result
  #=> {"dog"=>["woof", "I love kibble!"]}

На этот раз Ruby видит, что result имеет ключ 'dog', поэтому она просто добавляет "I love kibble!" в массив result['dog'], не ссылаясь блок.

Давайте рассмотрим другой пример:

result = Hash.new do |hash, key|
  puts "I just launched the missiles...just kidding"
  hash[key] = []
end

result['dog'] << 'woof'
I just launched the missiles...just kidding
  #=> ["woof"] 

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

Метод, использующий эту форму Hash # new обычно пишут:

def duped_index(arr)
  arr.each_with_index.
      with_object(Hash.new { |h,k| h[k]=[] }) { |(ele,idx), result|
        result[ele] << idx }.
      select { |ele, indices| indices.length > 1 }
end

Выбор того, какой подход выбрать, в основном зависит от вкуса, но я ожидаю, что большинство Rubyists выберет # 2 или # 3.

1 голос
/ 23 января 2020

do c для Hash # new немного сбивает с толку. Нормальным поведением для ха sh является ответ nil, когда к нему обращаются с ключом, которого он не содержит. «Не в книге». Иногда этого недостаточно.

Код в блоке Ha sh .new запускается при обращении к ha sh с помощью ключа, которого у него (пока) нет. Он дает вам параметры | h, k | (ха sh сам и новый ключ) для работы. Таким образом, простой Ха sh может быть записан как h = Hash.new{|h,k| nil }

. В этом случае, когда га sh "запрашивается" для первого ключа символа "а", мы не хотим nil, мы хотим добавить новый ключ "a" в ha sh с пустым массивом в качестве значения, к которому мы можем добавить индекс. Блок обеспечивает это.

0 голосов
/ 23 января 2020

Из документации Hash::new:

new → new_ha sh

new (obj) → new_ha sh

новый {| га sh, ключ | block} → new_ha sh

Возвращает новый, пустой ха sh. Если к этому ha sh впоследствии получит доступ ключ, который не соответствует записи ha sh, возвращаемое значение зависит от стиля new, использованного для создания ha sh. В первой форме доступ возвращает nil. Если указано obj , этот единственный объект будет использоваться для всех значений по умолчанию . Если указан блок, он будет вызван с объектом ha sh и ключом и должен вернуть значение по умолчанию. Блок обязан хранить значение в га sh при необходимости.

h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"]           #=> 100
h["c"]           #=> "Go Fish"
# The following alters the single default object
h["c"].upcase!   #=> "GO FISH"
h["d"]           #=> "GO FISH"
h.keys           #=> ["a", "b"]

# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"]           #=> "Go Fish: c"
h["c"].upcase!   #=> "GO FISH: C"
h["d"]           #=> "Go Fish: d"
h.keys           #=> ["c", "d"]
...