在Ruby中获取一个不重复的随机元素数组

3

我有一个问题数组,想从中随机获取一个不重复的问题。例如,有5个问题,如果全部问完,我将重新开始。我希望将其放入一个方法(或类似方法)中。

def askrandom
questions = ["A?", "B?" , "C?" , "D?"]

return #random question
end

输出应该类似于: A? C? D? B? #所有问题都被问了一次,因此要重复 B? D? C? A? ...

1
如果你有一个巨大(或无限)数量的问题,而你无法将它们全部加载到内存中,那么这就变得有趣了。然后,你可以使用一些数论技巧来使你的选择足够随机。例如 - 任何n个连续的数字通过模n的质数幂次方会给出从0到*n-1的数字。* - ndnenkov
哎呀,我说谎了一点。质数不应该是偶数(也就是2)。 - ndnenkov
5个回答

2
为了避免重复提问,您需要将剩余的问题存储在某个地方,让我们使用一个实例变量进行存储:
def initialize
  @remaining_questions = []
end

让我们将问题提取到一个自己的方法中:

def questions
  ["A?", "B?" , "C?" , "D?"]
end

现在,如果@remaining_questions为空,则使用questions的随机副本对其进行初始化。然后,您只需删除(并返回)第一个项目即可。
def ask_random
  @remaining_questions = questions.shuffle if @remaining_questions.empty?
  @remaining_questions.shift
end

不加构造函数的话,@remaining_questions ||= questions.shuffle 会不会显得过于设计了? - Aleksei Matiushkin
@mudasobwa 这只能起作用一次,因为 [] 是真值。 - Stefan
由于我们引入了一个类,它的生命周期可能很容易缩短为“new ⇒ ask ⇒ stay”。需要新的问卷调查⇒创建新对象。无论如何,这是一个超出范围实现设计的问题。 - Aleksei Matiushkin
@mudasobwa 我假设 ask_random 应该返回一个问题,而且你可以无限调用它。因此会有额外的开销。 - Stefan
感谢Stefan和Holger Just的帮助,但是当我尝试使用它时,它会抛出一个“undefined method 'empty?' for nil:NilClass(NoMethodError)”异常。 - Chlodwig Radulf
@ChlodwigRadulf 似乎你忘记了 @remaining_questions = [] 这一部分。 - Stefan

2

这个解决方案与@Stefan的解决方案非常接近,只是想法稍微改变了一下。

class Questions
  def initialize(array_of_questions)
    @questions = array_of_questions
    @nums ||= get_nums
  end

  def get_nums
    (0...@questions.size).to_a.shuffle
  end

  def get_num
    @nums.pop or (@nums = get_nums).pop
  end

  def pick
    @questions[get_num]
  end
end

questions = Questions.new(["A", "B", "C", "D"])

10.times.map{ questions.pick }
#=> ["B", "D", "C", "A", "C", "A", "B", "D", "A", "B"]

0

纯函数式方法:

def ask(question)
  question.tap { |q| puts "Asking question #{q}" }
end

def askrandom(asked = [], remaining = ["A?", "B?" , "C?" , "D?"].shuffle)
  return asked if remaining.empty?
  askrandom(asked << ask(remaining.pop), remaining)
end

@ndn 人们往往会对他们不理解的解决方案进行负面评价,而 Ruby 社区在 Rails 方式的丑陋问题上产生了负面影响。 - Aleksei Matiushkin
@ndn 我运行了代码,但输出不符合作者的预期。我想这可能是原因。 - fl00r
@fl00r,原帖明确说明“输出应该类似于...”。在这里使用面向对象编程是过度设计,几乎是一种代码异味。 - Aleksei Matiushkin
@mudasobwa 是的,OP 对他想要的东西有一个模糊的定义。我同意你的观点,在许多情况下,OOP 是过度设计,但无论如何,你所说的这个声明是一个非常有争议的陈述 :). - fl00r
我很好奇,为什么你使用这个结构 question.tap { |q| puts "Asking question #{q}" } 而不是简单的 puts "Asking question #{question}"。你为什么在这里使用了 tap - fl00r
@fl00r,这样它就会返回问题,而不是nil - ndnenkov

0
def fire_away(questions)
  @n = (@n || -1) + 1
  @order = [*0...questions.size].shuffle if @n % questions.size == 0
  questions[@order.shift]
end

q = ["A?", "B?" , "C?" , "D?"]

fire_away q #=> "D?" 
fire_away q #=> "A?" 
fire_away q #=> "C?" 
fire_away q #=> "B?" 
fire_away q #=> "B?" 
fire_away q #=> "C?" 
fire_away q #=> "A?" 
fire_away q #=> "D?" 
fire_away q #=> "A?" 
fire_away q #=> "C?" 
fire_away q #=> "B?" 
fire_away q #=> "D?" 

你可能还需要这个方法

def reset_questions
  @n = nil
end

@fl00r建议采用以下方法,以避免需要在定义fire_away的类中可见的实例变量(并避免需要reset_questions方法):
def fire_away(questions)
  n = -1
  order = nil
  Proc.new do
    n += 1
    order = [*0...questions.size].shuffle if n % questions.size == 0
    questions[order.shift]
  end
end

iterator = fire_away ["A", "B", "C", "D"]

iterator.call #=> "C" 
iterator.call #=> "A" 
iterator.call #=> "B" 
iterator.call #=> "D"
iterator.call #=> "D"

另一种方法是创建一个单独的类(这非常接近于@fl00r的答案)。
class Questions
  def initialize(*questions)
    @questions = questions
    @n = -1
  end
  def next_question
    @n += 1
    @order = [*0...@questions.size].shuffle if @n % @questions.size == 0
    @questions[@order.shift]        
  end
end

q = Questions.new("A?", "B?" , "C?" , "D?")
q.next_question #=> "C?" 
q.next_question #=> "A?" 
q.next_question #=> "D?" 
q.next_question #=> "B?" 
q.next_question #=> "B?" 

这两个修改显然比我的原始答案更优秀。


1
嗯,我认为这里有太多的全局状态。我会通过将状态封装到lambda/fiber/enumerator或某个类中来添加一些封装。其中一个副作用是,如果您使用不同的输入重复使用方法:fire_away(q1); fire_away(q2) - fl00r
我是指像这样的东西 https://gist.github.com/fl00r/9ac83cc7e15f0e441f2bfa6ba37ff242 - fl00r
@fl00r,非常好的建议!我以前没有见过那种技巧。我会进行编辑。 - Cary Swoveland

-1

通常我会创建一个新数组,然后在随机后将值附加到新数组中,前提是该值在新数组中不存在。
如果你得到与上次相同的输出,那么这意味着输出已经在新数组中,因为你已经将它附加到了新数组中。
对于我的拙劣英语表示抱歉。


它将操作成本增加了三倍,而且在具有重复项的数组上失败。这是实现目标的最糟糕的方法。 - Aleksei Matiushkin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接