ruby 如何随机置换两个列表中的元素,同时遵守非连续的排序约束?

ioekq8ef  于 2023-06-22  发布在  Ruby
关注(0)|答案(3)|浏览(106)

我是一项研究的一部分,其中受试者从4个类别(1,2,3,4)输入,每个类别有4个单词(a,b,c,d)。每个主题的单词顺序需要随机化,但连续的单词应该来自不同的类别。
我对编码相当陌生,一直在研究一种可以做到这一点的算法,但它变得相当长和复杂,我觉得应该有一种更简单的方法来做到这一点,不太容易出错。任何帮助将不胜感激!

fykwrbwg

fykwrbwg1#

这里是一个“蛮力”(计算效率低,但简单)的方法。
1.生成随机列表。
1.检查列表是否符合标准。
1.重复此操作,直到收集到所需数量的有效列表。

source = ["1a","1b","1c","1d","2a","2b","2c","2d","3a","3b","3c","3d","4a","4b","4c","4d"]
    valid = []
    num_valid = 100
    
    until valid.length >= num_valid do
      candidate = source.shuffle
    
      if candidate.each_cons(2).all? {|a,b| a[0] != b[0] }
        valid |= [candidate]
      end
    end

当上面的示例代码终止时,valid将包含100个不同的有效列表。

6uxekuva

6uxekuva2#

分解问题(以一种方式;还有其他的):

  • 需要非重复类别的列表
  • TBD:[a, b, a, ...]怎么样
  • 例如,每组单词.长度组合是否需要每个类别中的一个单词
  • TBD:猫是否需要对每组单词的顺序相同
  • 例如:
  • [c, b, d, a, c, b, d, a, ...](每个单词组相同的猫)或
  • [c, b, d, a, b, a, c, d, ...](不同的猫)
  • 无论哪种方式都有微小的调整
  • 该列表的长度为cats.length * words.length(组合数)

冗长但又不清楚:它是一个类别 Shuffle 的平面图,每words.length一个,重新 Shuffle (如果需要),直到 Shuffle 的第一个条目与前一个 Shuffle 的最后一个条目不同。
将其分成words.length块,并将每个类别与混洗单词列表中的单词相关联。
可能是简单的实现,但相当清晰,还有十几行或两行代码,没有试图变得聪明(我绝对没有:rofl:)。换句话说:它不需要非常复杂来满足需求--也许值得尝试以不同的方式来考虑它。

2uluyalo

2uluyalo3#

这里有一个方法,与@user513951的方法略有不同,但可能在数组较大时更有效。像前面的方法一样,它循环直到随机选择具有所需属性。这两种方法都产生统计随机样本(在软件中产生真正的随机值时受到限制)。
前面的方法提取块{ |a,b| a[0] != b[0] }中两个字符串的类别。然而,这假设不超过9个类别。如果类别可能由两个或更多个数字组成,则必须将数字从字符串的前面剥离。也许有一个预处理步骤,使前面提到的块两元素数组中的ab更有效(这需要在最后进行一些整理)。这将是对先前方法的简单改变。
如果我们被给予

a = ["1a", "1b", "1c", "1d", "20a", "20b", "20c", "20d",
     "3a", "3b", "3c", "3d", "41a", "41b", "41c", "41d"]

我们可以从计算1开始

arr = a.map { |s| s.split(/(?<=\d)(?=\D)/) }
  #=> [["1", "a"], ["1", "b"], ["1", "c"], ["1", "d"],
  #    ["20", "a"], ["20", "b"], ["20", "c"], ["20", "d"],
  #    ["3", "a"], ["3", "b"], ["3", "c"], ["3", "d"],
  #    ["41", "a"], ["41", "b"], ["41", "c"], ["41", "d"]]

与前面的方法相比,我建议的唯一变化是首先获得满足所需属性的类别的随机序列。一旦找到,我将转换为一个返回值,仍然是一个公正的样本选择。
实际上,我将块{ |a,b| a[0] != b[0] }简化为{ |x,y| x != y },其中xy是类别。效率的提高对于非常大的阵列可能是显著的。
首先创建一个哈希,将类别Map到它们的单词数组。

h = arr.each_with_object(Hash.new { |h,k| h[k] = [] }) do |(cat,word),h|
  h[cat] << word
end
  #=> {"1"=>["a", "b", "c", "d"], "20"=>["a", "b", "c", "d"],
  #    "3"=>["a", "b", "c", "d"], "41"=>["a", "b", "c", "d"]}

接下来,对每个值的元素进行混洗,以保持稍后的随机性。

h.transform_values!(&:shuffle)
  #=> {"1"=>["b", "d", "a", "c"], "20"=>["b", "c", "a", "d"],
  #    "3"=>["d", "b", "c", "a"], "41"=>["a", "d", "c", "b"]}

然后选择按其值大小复制的键(不需要所有键都相同)。

keys = h.each_with_object([]) { |(k,v),a| a.concat([k]*v.size) }
  #=> ["1", "1", "1", "1", "20", "20", "20", "20",
  #    "3", "3", "3", "3", "41", "41", "41", "41"]

现在找到一个满足所需属性的类别序列。

loop do
  n += 1
  keys.shuffle!
  break keys if keys.each_cons(2).all? { |x,y| x != y }
end
  #=> ["1", "41", "3", "20", "41", "20", "1", "20",
       "3", "41", "1", "3", "1", "20", "41", "3"]

(Upon执行该操作几次总是花费不到70次迭代来找到有效序列。)
最后,为每个类别添加随机化的单词,以生成一个满足所需属性的随机数组。

keys.map { |k| "#{k}#{h[k].shift}" }
  #=> ["1b", "41a", "3d", "20b", "41d", "20c", "1d", "20a",
  #    "3b", "41c", "1a", "3c", "1c", "20d", "41b", "3a"]
  1. (?<=\d)是一个 * 正向后查找 *,它Assert匹配前面有一个数字。(?=\D)是一个 * positive lookahead *,它Assert匹配后面跟着一个字符而不是数字。因此,此正则表达式匹配字符串中最后一个数字和第一个非数字之间的零宽度位置。

相关问题