Несколько итераций - PullRequest
       18

Несколько итераций

7 голосов
/ 05 апреля 2011

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

(1..10).each do |i| 
  (1..10).each do |j|
    (1..10).each do |k|
      (1..10).each do |l|
         puts "#{i} #{j} #{k} #{l}"
      end
    end
  end
end

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

(1..10).magic(4) { |i, j, k, l| puts "#{i} #{j} #{k} #{l}" }

Или еще лучше ...

magic(10, 4) { |i, j, k, l| puts "#{i} #{j} #{k} #{l}" }

Если нет чего-то встроенного, как бы я написал метод, подобный последнему?

Ответы [ 3 ]

9 голосов
/ 05 апреля 2011

Если вы используете Ruby 1.9, вы можете сделать это:

range = (1..10).to_a
range.repeated_permutation(4) do |set|
  puts set.join(" ")
end

В Ruby 1.8:

range = (1..10).to_a
range.product(range, range, range).each do |set|
  puts set.join(" ")
end
2 голосов
/ 05 апреля 2011

Я позволил себе сменить порядок ваших magic параметров в предположении, что база 10 является более распространенной и необязательной:

def magic(digits,base=10)
  raise "Max magic base of 36" unless base <= 36
  (base**digits).times do |i|
    str   = "%#{digits}s" % i.to_s(base)
    parts = str.scan(/./).map{ |n| n.to_i(base)+1 }
    yield *parts
  end
end

magic(3,2){ |a,b,c| p [a,b,c] }
#=> [1, 1, 1]
#=> [1, 1, 2]
#=> [1, 2, 1]
#=> [1, 2, 2]
#=> [2, 1, 1]
#=> [2, 1, 2]
#=> [2, 2, 1]
#=> [2, 2, 2]

magic(2,16){ |a,b| p [a,b] }
#=> [1, 1]
#=> [1, 2]
#=> [1, 3]
#=> ...
#=> [16, 15]
#=> [16, 16]

Объяснение :

Преобразовав исходную задачу из 1..10 в 0..9 и объединив цифры, мы увидим, что результат просто подсчитывается с доступом к каждой цифре.

0000
0001
0002
...
0010
0011
0012
...
9997
9998
9999

Так вот, что мой кодвыше делает.Он рассчитывает от 0 до максимального числа (на основе количества цифр и допустимых значений на цифру), и для каждого числа он:

  1. Преобразует число в соответствующую «базу»:
    i.to_s(base) # e.g. 9.to_s(8) => "11", 11.to_s(16) => "b"

  2. Использует String#% для дополнения строки правильным количеством символов:
    "%#{digits}s" % ... # e.g. "%4s" % "5" => " 5"

  3. Превращает эту единственную строку в массив односимвольных строк:
    str.scan(/./) # e.g. " 31".scan(/./) => [" ","3","1"]
    Обратите внимание, что в Ruby 1.9 это лучше сделать с str.chars

  4. Преобразует каждую из этих односимвольных строк обратно в число:
    n.to_i(base) # e.g. "b".to_i(16) => 11, " ".to_i(3) => 0

  5. Добавляет 1 к каждому из этих чисел,поскольку желание было начинать с 1 вместо 0

  6. Выводит этот новый массив чисел в качестве аргументов для блока, по одному на каждый параметр блока:
    yield *parts

2 голосов
/ 05 апреля 2011

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

def magic(ranges, &block)
  magic_ = lambda do |ranges, args, pos, block|
    if pos == ranges.length
      block.call(*args)
    else
      ranges[pos].each do |i|
        args[pos] = i
        magic_.call(ranges, args, pos+1, block)
      end
    end
  end
  magic_.call(ranges, [nil]*ranges.length, 0, block)
end

magic([1..10] * 4) do |a,b,c,d|
  puts [a, b, c, d].inspect
end

Тем не менее, производительность - это сложная вещь, и я не знаю, насколько эффективен Ruby с вызовами функций, поэтому, возможно, придерживаться библиотечных функцийсамый быстрый путь.

Обновление : взял предложение Phrogz и поместил magic_ внутри magic.( Обновление : принял предложение Phrogz снова и, надеюсь, сделал это правильно на этот раз с lambda вместо def).

Обновление : Array#product возвращает Array, поэтому я предполагаю, что это полностью материализовано.У меня нет Ruby 1.9.2, но Младен Ябланович отметил, что Array#repeated_permutation, вероятно, не материализует все это (даже при том, что начальный диапазон материализован с to_a).

...