2013-02-20 1 views
1

Ruby использует функции из «функциональной концепции» в большой степени, например, карта, каждая. Они действительно зависят от автономной функции, которая называется блок в Ruby.Как зацикливать элементы в массиве 2d для построения строки в «Ruby functional Style»

Очень часто петля, хотя есть 2d-массив, создает строку об элементах.

В Java, это может выглядеть как

public String toString(){ 
     String output = "["; 
     for (int i =0; i<array.length; i++) { 
      output+= "Row "+(i+1)+" : "; 
      for (int j=0; j<array[0].length;j++) { 
       output += array[i][j]+", "; 
      } 
      output += "\n"; 
     } 

     return output += "]"; 
    } 

Я попытался переписать такую ​​вещь в «Рубин функциональный стиль», но я думаю, что есть еще некоторые улучшения. Например. Я хочу, чтобы удалить изменяемый переменную OUTPUT

def to_s 
     output = "[\n" 
     @data.each_with_index do |row,i| 
      output << "Row #{i+1} : " 
      row.each { |num| output << "#{num}," } 
      output << "\n" 
     end 

     output+"]" 
    end 

ответ

3

Всякий раз, когда вы видите картину:

  1. инициализировать аккумулятор (в вашем случае output)
  2. на каждой итерации какая-то коллекция модифицирует аккумулятор (в вашем случае прилагается к нему)
  3. вернуть аккумулятор

это fold, или в Ruby terms a inject.

Собственно, это немного тавтология. A fold - универсальный метод итерации: все, которое может быть выражено путем итерации по элементам коллекции, также может быть представлено как fold по коллекции. Другими словами: все методы на Enumerable (включая each!) Также можно определить в терминах inject в качестве примитивного метода вместо each.

Подумайте об этом так: коллекция может быть пустой или может быть текущий элемент. Нет третьего варианта, если вы покроете эти два случая, тогда вы все охватили. Ну, fold принимает два аргумента: один, который сообщает ему, что делать, когда коллекция пуста, и тот, который сообщает ему, что делать с текущим элементом. Или добавьте еще один способ: вы можете увидеть коллекцию в виде серии инструкций, а fold - это интерпретатор этих инструкций. Существует только два вида инструкций: инструкция END и инструкция VALUE(el). И вы можете предоставить код интерпретатора для обеих этих инструкций для fold.

В Ruby второй аргумент не является частью списка аргументов, это блок.

Итак, что это будет за fold?

def to_s 
    @data.each_with_index.inject("[\n") do |acc, (row, i)| 
    acc + "Row #{i+1} : #{row.join(',')}\n" 
    end + ']' 
end 

Если вы хотите знать, может ли или нет each_with_index заразить ваш код с некоторым нефункциональной примеси, будьте уверены, что вы можете так же легко избавиться от него, включая индекс в аккумуляторе:

def to_s 
    @data.inject(["[\n", 1]) do |(s, i), row| 
    [s + "Row #{i} : #{row.join(',')}\n", i+1] 
    end.first + ']' 
end 

отметить также, что в первом случае, сeach_with_index, мы на самом деле не делать ничего «интересного» с аккумулятором, в отличие от второго случая, когда мы используем его, чтобы подсчет индекса , Фактически, первый случай на самом деле является ограниченной формой fold, он не использует всю свою мощность. Это действительно просто map:

def to_s 
    "[\n" + @data.map.with_index(1) do |row, i| 
    "Row #{i} : #{row.join(',')}\n" 
    end.join + ']' 
end 

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

def to_s 
    "[\n" << @data.map.with_index(1) do |row, i| 
    "Row #{i} : #{row.join(',')}\n" 
    end.join << ']' 
end 

Это спасает нас от создания несколько ненужных строковых объектов, но что более важно: это более идиоматично. Реальная проблема: shared измененное состояние, но мы не делимся с нашей изменяемой строкой здесь: когда to_s возвращает своего вызывающего абонента , получает доступ к строке, но to_s сам вернулся и, следовательно, больше не имеет к нему доступа.

Если вы хотите, чтобы получить реальные фантазии, вы можете даже использовать интерполяцию строки:

def to_s 
    %Q<[\n#{@data.map.with_index(1) do |row, i| 
    "Row #{i} : #{row.join(',')}\n" 
    end.join}]> 
end 

К сожалению, это не только нарушает подсветку синтаксиса IRB, но и мой мозг ;-)

+0

Хм, я серьезно подумывал предложить ваш последний пример ... Кстати, 'with_index' (из перечислителя) принимает параметр смещения. – steenslag

+0

@steenslag: Не знал этого! Вы узнаете что-то новое каждый день, даже обучаясь. –

+0

@ JörgWMittag Спасибо за подробный ответ :) Но я не понимаю, как мы можем знать порядок «параметров» между | .. | блока, когда мы связываем счетчики. – code4j

0

то же самое, но короче, с меньшим количеством блоков.

def to_s 
     output = "[\n" 
     @data.each_with_index do |row,i| 
      output << "Row #{i+1} : #{row.join(',')}\n" 
     end 

     output+"]" 
    end 
+0

'' << не является функциональным. Вы все еще используете mutable vars. – dbenhur

+0

цитата: «То же самое, но короче» –

+0

«Я попытался переписать такую ​​вещь в« Ruby functional Style », но я думаю, что все еще есть некоторые улучшения. Например, я хочу удалить изменяемый вывод переменной« – dbenhur

1

Вот метод, без изменяемых Варс:

def to_s 
    (
    [ "[" ] + 
    @data.map.with_index { |row,i| "Row #{i+1} : #{row * ','}" } + 
    [ "]" ] 
).join("\n") 
end 
+0

спасибо за ваш ответ :) – code4j

Смежные вопросы