结果
以你的为例,
x = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+','/']
start = "ABCDEFGHIJK/".split("")
下面是用我在下面构造的枚举器head_start_permutation得到的:
y = x.head_start_permutation(start)
#=> #<Enumerator: #<Enumerator::Generator:0x000001011e62f0>:each>
y.peek.join(' ') #=> "A B C D E F G H I J K /"
y.next.join(' ') #=> "A B C D E F G H I J K /"
y.next.join(' ') #=> "A B C D E F G H I J L K"
y.next.join(' ') #=> "A B C D E F G H I J L M"
y.take(3).map { |a| a.join(' ') }
#=> ["A B C D E F G H I J L M",
# "A B C D E F G H I J L N",
# "A B C D E F G H I J L O"]
第二个next 是最有趣的。由于'A' 和'/' 是x 的第一个和最后一个元素,'K/' 之后的下一个元素将是'LA',但由于'A' 已经出现在排列中,'LB' 被尝试并以同样的原因被拒绝,依此类推,直到'LK' 被接受。
另一个例子:
start = x.sample(12)
# => ["o", "U", "x", "C", "D", "7", "3", "m", "N", "0", "p", "t"]
y = x.head_start_permutation(start)
y.take(10).map { |a| a.join(' ') }
#=> ["o U x C D 7 3 m N 0 p t",
# "o U x C D 7 3 m N 0 p u",
# "o U x C D 7 3 m N 0 p v",
# "o U x C D 7 3 m N 0 p w",
# "o U x C D 7 3 m N 0 p y",
# "o U x C D 7 3 m N 0 p z",
# "o U x C D 7 3 m N 0 p 1",
# "o U x C D 7 3 m N 0 p 2",
# "o U x C D 7 3 m N 0 p 4",
# "o U x C D 7 3 m N 0 p 5"]
请注意,'x' 和 '3' 作为每个数组中的最后一个元素被跳过,因为排列的其余部分包含这些元素。
排列顺序
在考虑如何有效地处理你的问题之前,我们必须考虑排列顺序的问题。由于您希望从特定排列开始枚举,因此有必要确定哪些排列在前面,哪些在后面。
我假设您想通过数组元素的偏移量来使用数组的lexicographical ordering(如下所述),这就是Ruby 用于Array#permuation、Array#combinaton 等等。这是对单词“字典”排序的概括。
举例来说,假设我们想要以下元素的所有排列:
arr = [:a,:b,:c,:d]
一次拍三张。这是:
arr_permutations = arr.permutation(3).to_a
#=> [[:a,:b,:c], [:a,:b,:d], [:a,:c,:b], [:a,:c,:d], [:a,:d,:b], [:a,:d,:c],
#=> [:b,:a,:c], [:b,:a,:d], [:b,:c,:a], [:b,:c,:d], [:b,:d,:a], [:b,:d,:c],
#=> [:c,:a,:b], [:c,:a,:d], [:c,:b,:a], [:c,:b,:d], [:c,:d,:a], [:c,:d,:b],
#=> [:d,:a,:b], [:d,:a,:c], [:d,:b,:a], [:d,:b,:c], [:d,:c,:a], [:d,:c,:b]]
如果我们将arr的元素替换为它们的位置:
pos = [0,1,2,3]
我们看到了:
pos_permutations = pos.permutation(3).to_a
#=> [[0, 1, 2], [0, 1, 3], [0, 2, 1], [0, 2, 3], [0, 3, 1], [0, 3, 2],
# [1, 0, 2], [1, 0, 3], [1, 2, 0], [1, 2, 3], [1, 3, 0], [1, 3, 2],
# [2, 0, 1], [2, 0, 3], [2, 1, 0], [2, 1, 3], [2, 3, 0], [2, 3, 1],
# [3, 0, 1], [3, 0, 2], [3, 1, 0], [3, 1, 2], [3, 2, 0], [3, 2, 1]]
如果您将这些数组中的每一个都视为以 4 为底的三位数字 (arr.size),您可以看到我们在这里只是从零到最大的 333 来计算它们,而跳过了具有常见数字的那些。这是 Ruby 使用的顺序,也是我将使用的顺序。
请注意:
pos_permutations.map { |p| arr.values_at(*p) } == arr_permutations #=> true
这表明一旦我们有了pos_permutations,我们就可以将它应用于任何需要排列的数组。
简单的开头枚举器
假设对于上面的数组arr,我们需要一个枚举器,它一次置换三个所有元素,第一个是[:c,:a,:d]。我们可以通过如下方式获得该枚举器:
temp = arr.permutation(3).to_a
ndx = temp.index([:c,:a,:d]) #=> 13
temp = temp[13..-1]
#=>[ [:c,:a,:d], [:c,:b,:a], [:c,:b,:d], [:c,:d,:a], [:c,:d,:b],
# [:d, :a, :b], [:d,:a,:c], [:d,:b,:a], [:d,:b,:c], [:d,:c,:a], [:d,:c,:b]]
enum = temp.to_enum
#=> #<Enumerator: [[:c, :a, :d], [:c, :b, :a],...[:d, :c, :b]]:each>
enum.map { |a| a.map(&:to_s).join }
#=> [ "cad", "cba", "cbd", "cda", "cdb",
# "dab", "dac", "dba", "dbc", "dca", "dcb"]
但是等一下!如果我们只希望使用这个枚举器一次,这很难节省时间。如果我们打算多次使用enum(即,总是与相同的枚举起点),这当然是可能的。
滚动你自己的枚举器
上面第一节的讨论表明构造一个枚举器
head_start_permutation(start)
可能并不那么困难。第一步是为偏移数组创建一个next 方法。以下是一种可行的方法:
class NextUniq
def initialize(offsets, base)
@curr = offsets
@base = base
@max_val = [base-1] * offsets.size
end
def next
loop do
return nil if @curr == @max_val
rruc = @curr.reverse
ndx = rruc.index { |e| e < @base - 1 }
if ndx
ndx = @curr.size-1-ndx
@curr = @curr.map.with_index do |e,i|
case i <=> ndx
when -1 then e
when 0 then e+1
when 1 then 0
end
end
else
@curr = [1] + ([0] * @curr.size)
end
(return @curr) if (@curr == @curr.uniq)
end
end
end
我选择的特定实现并不是特别有效,但确实达到了目的:
nxt = NextUniq.new([0,1,2], 4)
nxt.next #=> [0, 1, 3]
nxt.next #=> [0, 2, 1]
nxt.next #=> [0, 2, 3]
nxt.next #=> [0, 3, 1]
nxt.next #=> [0, 3, 2]
nxt.next #=> [1, 0, 2]
注意这是如何跳过包含重复的数组的。
接下来,我们构造枚举器方法。我选择通过猴子修补类Array 来做到这一点,但可以采取其他方法:
class Array
def head_start_permutation(start)
# convert the array start to an array of offsets
offsets = start.map { |e| index(e) }
# create the instance of NextUtil
nxt = NextUniq.new(offsets, size)
# build the enumerator
Enumerator.new do |e|
loop do
e << values_at(*offsets)
offsets = nxt.next
(raise StopIteration) unless offsets
end
end
end
end
让我们试试吧:
arr = [:a,:b,:c,:d]
start = [:c,:a,:d]
arr.head_start_permutation(start).map { |a| a.map(&:to_s).join }
#=> [ "cad", "cba", "cbd", "cda", "cdb",
# "dab", "dac", "dba", "dbc", "dca", "dcb"]
请注意,构造一个枚举器会更容易
head_start_repeated_permutation(start)
唯一的区别是在NextUniq#next 中我们不会跳过有重复的候选人。