2016-05-28 3 views
1

Я узнаю о волшебстве Перечислимого в Ruby. Я слышал, что нужно просто включить Enumerable и реализовать метод each и может обладать полномочиями Enumerable для этого класса.реализовать `each` для перечислимого mixin в Ruby

Итак, я подумал о реализации моего собственного пользовательского класса Foo для практики. Это выглядит, как показано ниже:

class Foo 
    include Enumerable 

    def initialize numbers 
    @num = numbers 
    end 

    def each 
    return enum_for(:each) unless block_given? 
    @num.each { |i| yield i + 1 } 
    end 
end 

Этот класс принимает массив и его each работает почти аналогична Array#each. Вот разница:

>> f = Foo.new [1, 2, 3] 
=> #<Foo:0x00000001632e40 @num=[1, 2, 3]> 
>> f.each { |i| p i } 
2 
3 
4 
=> [1, 2, 3] # Why this? Why not [2, 3, 4]? 

Все работает так, как я ожидаю, кроме одной вещи, которая является последним утверждением. Я знаю его возвращаемое значение, но не должно быть [2, 3, 4]. Есть ли способ сделать это [2, 3, 4].

Также прокомментируйте, как я применил each. Если есть лучший способ, дайте мне знать. Сначала в моей реализации у меня не было этой строки return enum_for(:each) unless block_given?, а затем она не работала, когда не было выделено никаких блоков. Я заимствовал эту строку откуда-то, а также, пожалуйста, скажите мне, правильно ли это справиться с ситуацией или нет.

ответ

2

Возвращаемое значение each должен быть приемником, т.е. self , Но вы возвращаете результат звонка @num.each. Теперь, как я только что сказал, each возвращает self, ergo @num.each возвращает @num.

Исправление просто: вернуться self:

def each 
    return enum_for(:each) unless block_given? 
    @num.each { |i| yield i + 1 } 
    self 
end 

Или, возможно, немного больше Rubyish:

def each 
    return enum_for(:each) unless block_given? 
    tap { @num.each { |i| yield i + 1 }} 
end 

[На самом деле, так как Руби 1.8.7+, each также предполагается для возврата Enumerator при вызове без блока, но вы уже правильно обрабатываете это. Совет. Если вы хотите реализовать оптимизированные версии некоторых других методов Enumerable, переопределив их или хотите добавить свои собственные методы Enumerable, аналогичные поведению, как и исходные, вы собираетесь вырезать пасту &, которая точно такая же строка кода снова и снова, и в какой-то момент вы случайно забудете изменить имя метода. Если вы замените строку на return enum_for(__callee__) unless block_given?, вам не нужно запоминать изменение имени.]

2

each не модифицирует массив. Если вы хотите, чтобы вернуть измененный массив, используйте map:

def each 
    return enum_for(:each) unless block_given? 
    @num.map { |i| yield i + 1 } 
end 
f.each { |i| p i } 
2 
3 
4 
=> [2, 3, 4] 

Но я рекомендую использовать каждый внутри пользовательский метод. Вы можете увеличивать каждый элемент вашего массива на 1 в методе initialize, так как вы хотите использовать его для всех вычислений. Кроме того, вы можете изменить свой метод each, чтобы избежать использования enum_for, пройдя block_given? внутри блока. Наконец ваш код будет выглядеть следующим образом:

class Foo 
    include Enumerable 

    def initialize(numbers) 
    @num = numbers.map {|n| n + 1 } 
    end 

    def each 
    @num.each { |i| yield i if block_given? } 
    end 
end 

f = Foo.new [1, 2, 3] 
=> #<Foo:0x00000000f8e0d0 @num=[2, 3, 4]> 
f.each { |i| p i } 
2 
3 
4 
=> [2, 3, 4] 
+0

С 'yield (i + 1) if block_given?' Я получаю неправильный результат для f.each – Lokesh

+0

@Lokesh, возможно, вы пропустили некоторый код. Пожалуйста, см. Текущую версию ответа – Ilya

+0

Мне очень нравится ваше первое решение. Второе решение, которое вы предложили изменить для инициализации, не вписывается в мою потребность, потому что вместо (i + 1) у меня есть что-то вроде ('abc' * i), которое занимает пространство, поэтому изменение @num перед рукой будет на большее пространство, чем это необходимо. – Lokesh

1

Вы должны использовать map вместо each.

f.map { |i| p i } 
#=> [2,3,4] 

Тот факт, что Foo включает в себя Enumerable означает, что все методы Enumerable можно ссылаться на экземпляры Foo

+0

Привет. Проблема в том, что я хочу, чтобы мой пользовательский «каждый» вел себя как обычный «каждый». – Lokesh

+0

@Lokesh, он ведет себя как обычный «каждый». То есть, Array ведет себя одинаково. Я делаю возможной цепочку. «Карта» - это путь. – steenslag

0

Этот класс принимает массив и его каждый работает практически аналогичен Array#each.

Я знаю его возвращаемое значение, но не должно быть [2, 3, 4].

  1. A По умолчанию возвращает результат последнего утверждения, который был выполнен.

  2. Array#each возвращает исходный массив.

Применяя эти правила к вашему DEF:

def each 
    return enum_for(:each) unless block_given? 
    @num.each { |i| yield i + 1 } #Array#each returns @num 
end #When a block is given, the result of the last statement that was executed is @num 

Вы всегда могли бы сделать что-то вроде этого:

class Foo 
    include Enumerable 

    def initialize numbers 
    @num = numbers 
    @enum_vals = [] 
    end 

    def each 
    if block_given? 
     @num.each do |i| 
     yield i + 1 
     @enum_vals << i + 1 
     end 

     @enum_vals 
    else 
     enum_for 
    end 
    end 
end 

result = Foo.new([1, 2, 3, ]).each {|i| p i} 
p result 

--output:-- 
2 
3 
4 
[2, 3, 4]