Ruby:你如何设置枚举器的状态?

时间:2014-05-11 02:40:16

标签: ruby permutation enumeration enumerable enumerator

我正在做一个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/

2 个答案:

答案 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#permuationArray#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"