我正在做一个64位置换增量器。我已经编写了所有工作代码。但是看看Ruby已经像Array :: permutation那样产生了一个枚举器;我想用它并更进一步。
不必通过使用'next'来完成每个排列,我可以设置起点吗?
x = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+','/']
y = x.permutation(12)
y.peek.join
=> "ABCDEFGHIJKL"
y.next
y.peek.join
=> "ABCDEFGHIJKM"
。 #DOT THEE LIKE THE HERS
y.set_current_enumerator_state_to("ABCDEFGHIJK/".split(""))
。 #并按以下方式选择
y.peek.join
=> "ABCDEFGHIJK/"
y.next
y.peek.join
=> "ABCDEFGHIJLK"
能够设置枚举器将继续的状态将简单地解决所有问题。我为此创造了一个高尔夫挑战赛。编写所有代码我无法将其编写为少于600多个字符的代码。但是,如果我可以设置Enumerator将从哪个状态开始,我可以轻松地在接近100或更少的代码字符的位置进行操作。
我真的正在尽我所能去学习Ruby的一些深层秘密,这是它的核心。
如果您对代码感兴趣,我会做的高尔夫挑战:http://6ftdan.com/2014/05/03/golf-challenge-unique-base-64-incrementor/
答案 0 :(得分:3)
<强>结果
对于您的示例,
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
,那么将完整的枚举器转换为数组,切断开头并将剩下的内容转换为枚举器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
我们不会跳过有重复的候选人。
答案 1 :(得分:0)
您可以尝试使用drop_while
(使用lazy
以避免让排列枚举器覆盖其所有元素):
z = y.lazy.drop_while { |p| p.join != 'ABCDEFGHIJK/' }
z.peek.join
# => "ABCDEFGHIJK/"
z.next
z.peek.join
# => "ABCDEFGHIJLK"