Подтвердить что ты не робот

Могу ли я создать массив в Ruby со значениями по умолчанию?

Perl довольно хорошо относится к значениям по умолчанию:

: [email protected]; perl -e '@foo; printf "%d\n", $foo[123]'
0
: [email protected]; perl -e '%foo; printf "%d\n", $foo{bar}'
0

Ruby может сделать то же самое, по крайней мере для хэшей:

>> foo = Hash.new(0)
=> {}
>> foo[:bar]
=> 0

Но то же самое, похоже, не работает для массивов:

>> foo = Array.new(0)
=> []
>> foo[123]
=> nil
>> foo[124] = 0
=> 0
>> foo[456] = 0
=> 0
>> foo[455,456]
=> [nil, 0]

Возможно ли предоставить значение по умолчанию для массивов, поэтому, когда они автоматически расширены, они заполняются 0 вместо nil?

Конечно, я могу обойти это, но ценою выразительности:

>> foo[457,458] = 890, 321
=> [890, 321]
>> foo[456] += 789
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+
>> foo.inject(0) {|sum, i| sum += (i || 0) }
=> 1211
>> foo.inject(:+)
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+

Обновление 1: Один из моих коллег указал, что я могу использовать #compact для решения проблемы #inject и #to_i для решения стандартной проблемы с элементом по индексу:

>> foo.include? nil
=> true
>> foo.compact.inject(:+)
=> 1211
>> foo[456,457]
=> [0, 890, 321]
>> foo[455..457]
=> [nil, 0, 890]
>> foo[455..457].map(&:to_i)
=> [0, 0, 890]

Обновление 2: Благодаря Andrew Grimm для решения проблемы +=:

>> foo = []
=> []
>> def foo.[](i)
>>   fetch(i) {0}
>> end
=> nil
>> foo[4]
=> 0
>> foo
=> []
>> foo[4] += 123
=> 123
>> foo
=> [nil, nil, nil, nil, 123]

Обновление 3:, это начинает выглядеть как whack-a-mole!

>> foo
=> [nil, nil, nil, nil, 123]
>> foo[-2..-1]
TypeError: can't convert Range into Integer

Но мы можем справиться с этим:

>> def foo.[](index)
>>   if index.is_a? Range
>>     index.map {|i| self[i] }
>>   else
?>     fetch(index) { 0 }  # default to 0 if no element at index; will not cause auto-extension of array
>>   end
>> end
=> nil
>> foo
=> [nil, nil, nil, nil, 123]
>> foo[-2..-1]
=> [nil, 123]

Теперь я должен признать (застенчиво), что я буду подклассифицировать Array, чтобы избежать загромождения моего кода:

class MyClass
  class ArrayWithDefault < Array
    def [](index)
      if index.is_a? Range
        index.map {|i| self[i] }
      else
        fetch(index) { 0 }  # default to 0 if no element at index; will not cause auto-extension of array
      end
    end
  end
end

Спасибо за все творческие решения. TIMTOWTDI действительно!

4b9b3361

Ответ 1

Учитывая, что Ruby возвращает nil для несуществующего элемента (в отличие от ошибки типа index-out-of-bounds), вы можете просто использовать "или":

a = [1,2,3]
puts a[5]  # => nil
puts a[5] || "a default"  # => a default

Вы можете использовать подход патчей обезьян, но вы, вероятно, не захотите делать это ни в чем крупном, чем 1 файл script:

a = [1,2,3]
def a.[](index)
  self.at(index) || "a default"
end
puts a[5]   # => "a default"

Ответ 2

Не авторасширен, но инициализирован до указанной длины со значением по умолчанию:

>> Array.new(123, 0)  
=> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Ответ 3

Если вы имеете дело с целыми числами, вы можете вызвать to_i:

foo = []
foo[100]
#=> nil
foo[100].to_i
#=> 0
foo[100] = 3
foo[100]
#=> 3

UPD

О, я не читал всю тему:)

чтобы вы могли использовать это:

foo.inject{|a,b| a.to_i + b.to_i }

который, на самом деле, не самый умный

Ответ 4

Я положу элегантное решение Johans: foo.compact.inject(:+)

Ответ 5

Другой подход будет переопределять метод Array#[] и вернуть значение по умолчанию, если нет элемента

class Array         
  def [](index)
     self.at(index) ? self.at(index) : 0
  end
end

и

arr = [1,2,3]
puts arr[0]  # print 1
puts arr[5]  # print 0

Ответ 6

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

Изменить (из нашего обсуждения): Важно то, что код для достижения вашей цели находится в нужном месте (принцип единой ответственности), и это место не ваш "клиентский код", следовательно, нужен новый класс. Расширение существующего класса Array (через наследование /mixin ), вероятно, лучше, чем инкапсуляция желаемого поведения в совершенно новый класс.