【问题标题】:How slice an array while avoiding duplicate values in each slice?如何在避免每个切片中重复值的同时对数组进行切片?
【发布时间】:2014-10-05 10:40:52
【问题描述】:

假设我有这个数组:

a = [1,2,3,3,3,3,3,3,3,3,3,4,5,6,6,7,8,9,10,11]

a.each_slice(2).to_a 将生成对,但这些对将包含非唯一值,例如 [3,3]。所以我想我正在寻找某种 unique_each_slice 方法。

我想要的是能够不断改组这个数组,直到我有唯一的 2 对(不必是 2,可以是任何东西),就像这样(以 2 为例) :

[3, 1, 3, 7, 6, 3, 4, 5, 8, 3, 9, 3, 2, 3, 6, 3, 3, 11, 10, 3]

如果你对这个数组执行 each_slice(2),你会得到唯一的对:

[[3, 1], [3, 7], [6, 3], [4, 5], [8, 3], [9, 3], [2, 3], [6, 3], [3, 11], [10, 3]]

与原来的相比:

[[1, 2], [3, 3], [3, 3], [3, 3], [3, 3], [3, 4], [5, 6], [6, 7], [8, 9], [10, 11]]

每个都有非唯一的对,例如 [3,3]

另一个例子,假设我有:

a = [1,2,3,3,3,3,3,3,3,3,3,4,5,6,6,7,8,9,10,11,12,13,14,15,16,17]

现在,假设有一些函数 a.unique_slices_of(3),我会得到:

[[4, 16, 3], [1, 9, 3], [3, 6, 17], [3, 6, 10], [15, 3, 2], [3, 8, 12], [11, 3, 14], [7, 13, 3], [3, 5]]

“唯一切片”是指相同数字不重复两次的切片:[1,2,3] 是唯一切片,[3,1,3] 不是。

到目前为止,我采用了以下方法,似乎需要多次迭代才能正确处理:

class Array
  def unique_slices_of!(slices)
    loop do
      unique = true
      self.each_slice(slices) do |slice|
        if slice != slice.uniq
          self.shuffle!
          unique = false # so we know whether to loop again
          break
        end
      end
      break if unique # if unique didn't change, that means all slices were equal
      if unique == false then unique == true end # reset and start again
    end
    self 
  end
end

我的代码的主要问题是 a) 我认为我没有使用一些惯用的 Ruby 方法,它可以将这个过程缩短一半或更多。 b) 如果数组不能包含唯一切片,则可能出现无限循环。我可能需要在这里使用一些组合理论,但我不确定如何。

【问题讨论】:

  • 输出是否必须是随机的,或者如果你得到一个这样的数组就足够了,即使这是一个系统计算的数组?
  • 长度不必是倍数。关于随机性/系统计算的,没关系。
  • @meagar,我认为这个属于灰色区域。尽管有标题,OP实际上是在问,“我想实现一个算法......我尝试了以下方法,其中随机选择序列,但可能需要很长时间才能解决一些问题。”这类问题在 SO 上相当常见。
  • 我建议您将标题更改为“重新排列数组,以便 each_slice 生成具有唯一元素的数组”。随机生成可能的解决方案是一种方法,但正如您上面所说,这不是必需的。您也可以将“shuffle”替换为“permuting the elements of”。

标签: ruby arrays random-sample


【解决方案1】:
a = [1,2,3,3,3,3,3,3,3,3,3,4,5,6,6,7,8,9,10,11]

您可以通过以下方式测试切片是否“唯一”:

a.each_slice(2).all?{|x| x == x.uniq}

所以现在你只是洗牌,直到你得到你想要的:

a.shuffle! until a.each_slice(2).all?{|x| x == x.uniq}

避免无限循环的最简单方法是使用timeout

require 'timeout'
# raise an error if it takes more than 1 second
timeout(1){ a.shuffle! until a.each_slice(3).all?{|x| x == x.uniq} }

【讨论】:

  • 哇,这实际上相当不错。竖起大拇指!问题是,是否有一些数学方法可以首先确定数组是否实际上可以被唯一地“切片”,所以如果数组不能被唯一地切片,我会得到一个 nil 或引发错误,而不是使用超时方法。
  • 我确定有,但我不喜欢数学:(
  • 我试图想出一些算法,但是有太多的边缘情况,猜测超时会做到这一点,可以概率返回数组本身或数组非唯一切片以防万一失败。
【解决方案2】:

对独特组合进行抽样

如果您正在寻找更惯用的东西,并且算法的效率不是您最关心的问题,您可以尝试以下方法:

a = [1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11]
a.combination(2).reject { |pair| pair[0] == pair[1] }.sample(a.size / 2)

这种方法的主要缺点是 a 很大时的速度,因为Array#combination 会在您用Array#rejectArray#sample 筛选结果之前生成所有可能的组合。但是,对于中等大小的数组,它确实似乎足够快。

评估解决方案的性能

休闲测试表明,这对于中等大小的数组来说已经足够快了。考虑:

require 'benchmark'

a = [1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11]

Benchmark.measure do
  a.combination(2).reject { |pair| pair[0] == pair[1] }.sample(a.size / 2)
end.to_s
#=> "  0.000000   0.000000   0.000000 (  0.000052)\n"

即使迭代了 100,000 次,在我的系统上仍然只用了 3.650299 秒。鉴于您发布的语料库,这对于实际使用来说似乎足够快,但您的里程可能会有所不同。

允许比较任意子数组大小

比较成员和计数

在 cmets 中,OP 询问这是否可以推广到具有 2、3 或 4 个元素的子数组。是的,只需进行一些重构,尽管性能会随着组合中元素数量的增加而降低。考虑:

array = [1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11]
element_size = 4 

array.combination(element_size).
  reject { |element| element.map { |member| element.count(member) > 1 }.any? }.
      sample(array.size / element_size)

这使用所需的 element_size 来确定要动态采集的样本数。这具有删除任何部分填充的数组的附带好处,消除了您使用#each_slice 获得的“悬空”元素。

这里的主力仍然是拒绝方法,它现在使用#count 迭代每个子数组的每个成员,并拒绝具有#any? 成员在该子数组中出现多次的元素。即使有更好的变量名称,它也比我们有固定元素大小时更难理解,但它肯定更灵活。

更易读(且速度稍快)的比较

向@pguardiario 致敬(请参阅this related answer),您甚至可以将其缩短一点通过仅选择所有数组成员所在的子数组来使其更具可读性#uniq。例如:

array.combination(element_size).
  select { |subarray| subarray == subarray.uniq }.
    sample(array.size / element_size)

【讨论】:

  • 是否可以修改此代码使其适用于 3、4 等的组合?
  • @daremkd 我添加了一个部分来解决您对每个大于两个元素的子数组的需求。希望对您有所帮助!
  • 是的,它真的很有帮助,谢谢。然而,我在使用 .combination 和循环直到 .shuffle 之间做了一个基准测试!完成它的工作(pguardirio 逻辑)并且每次都使用 .shuffle!完成它的工作比尝试手动提出所有组合要快(并且使用更大的数组等会变得更糟)。非常感谢你的努力,给你竖起大拇指。
【解决方案3】:

我有一个似乎可行的解决方案。基本思想是将具有最大计数的元素分配到尽可能多的切片中。添加几个shuffle 使其看起来随机。

class Array
  def unique_slices_of(slice_length)
    buf = []
    arr = []
    hash = Hash.new 0
    self.each {|i| hash[i] += 1}
    sorted = hash.sort_by {|k, v| v}.reverse
    # sorted[][0] holds the element and sorted[][1] holds the count
    return nil if sorted[0][1] > ((self.length * 1.0) / slice_length).ceil
    index = 0
    until sorted.length.zero?
      # Add element to buf and decrement count
      # if count == 0, remove the entry from sorted
      buf << sorted[index][0]
      sorted[index][1] -= 1
      if sorted[index][1] == 0
        sorted.delete_at index
        break if sorted.length == 0
        index -= 1
      end
      index = (index + 1) % sorted.length
      if buf.length == slice_length
        arr << buf.shuffle
        buf.clear
        index = 0
      end
    end
    arr << buf.shuffle if buf.length > 0
    arr.shuffle
  end
end

输出:

[3, 1, 3, 7, 6, 3, 4, 5, 8, 3, 9, 3, 2, 3, 6, 3, 3, 11, 10, 3].unique_slices_of(2)
#=> [[8, 3], [3, 6], [3, 5], [1, 10], [3, 4], [3, 6], [7, 3], [9, 3], [3, 11], [3, 2]]

[1,2,3,3,3,3,3,3,3,3,3,4,5,6,6,7,8,9,10,11,12,13,14,15,16,17].unique_slices_of(3)
#=> [[3, 9, 6], [6, 14, 3], [3, 2, 17], [7, 3, 16], [3, 11, 10], [15, 3, 8], [4, 3, 5], [3, 13, 12], [1, 3]]

【讨论】:

    【解决方案4】:

    非随机方式

    这里的想法是将不同的值放入容器中。然后,虽然还有剩余的垃圾箱:

    • 按大小排列垃圾箱,最大的垃圾箱在前
    • 通过为第一个 max_slice_size 箱的每个取一个数字来制作一个切片
    • 删除空垃圾箱

    由于切片中的每个值都取自不同的 bin,因此可以保证切片中包含不同的值。

    代码:

    def slices_without_repeats(a, max_slice_size)
      slices = []
      bins = a.group_by { |e| e }.values
      until bins.empty?
        bins = bins.sort_by(&:size).reverse
        slice_size = [max_slice_size, bins.size].min
        slice = slice_size.times.map do |i|
          bins[i].pop
        end
        slices << slice
        bins.reject!(&:empty?)
        if slice.size < max_slice_size && !bins.empty?
          raise ArgumentError, "An element repeats too much"
        end
      end
      slices
    end
    

    此算法不使用显式随机性。它确实使用了 Ruby 的快速排序,它是不稳定的,并且可能会利用随机性(例如在选择枢轴点时)。

    使用中:

    a = [1,2,3,3,3,3,3,3,3,3,3,4,5,6,6,7,8,9,10,11]
    p slices_without_repeats(a, 2)
    # [[3, 6], [3, 9], [3, 7], [3, 2], [3, 6],
    #  [3, 10], [3, 11], [3, 4], [1, 8], [3, 5]]
    

    它会检测何时无法完成:

    p slices_without_repeats(a, 3)
    # An element repeats too much (ArgumentError)
    

    它处理最后一个切片未满的情况:

    p slices_without_repeats([1, 2, 3, 3, 4, 4, 4], 3)
    # [[4, 3, 2], [4, 3, 1], [4]]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多