Chlodwig Radulf
Chlodwig Radulf

Reputation: 131

Get a random non repeating element of an array in ruby

I have an array of questions and I want to get a non-reapting random one out of them. So for instance 5 questions, and if asked all i will simply start again. I would like to put it into an method (or something like that)

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

return #random question
end

The output should be something like

A? C? D? B? #all questions where asked once, so repeat

B? D? C? A? ...

Upvotes: 3

Views: 579

Answers (5)

Cary Swoveland
Cary Swoveland

Reputation: 110725

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?" 

You may also need the method

def reset_questions
  @n = nil
end

@fl00r suggested the following to avoid the need for instance variables that are visible within the class in which fire_away is defined (and avoid the need for the method 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"

Another way would be to create a separate class (which is quite close to @fl00r's answer).

class Questions
  def initialize(*questions)
    @questions = questions
    @n = -1
  end
  def next_question
    @n += 1
    @order = [*[email protected]].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?" 

Both of these modifications are clearly superior to my original answer.

Upvotes: 0

fl00r
fl00r

Reputation: 83680

It is very close to @Stefan's solution with a slightly changed idea.

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

  def get_nums
    ([email protected]).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"]

Upvotes: 2

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Pure functional approach:

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

Upvotes: 0

Stefan
Stefan

Reputation: 114218

To avoid repeating a question, you have to store the remaining questions somewhere, let's use an instance variable:

def initialize
  @remaining_questions = []
end

And let's extract the questions into a method of its own:

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

Now, if @remaining_questions is empty, you initialize it with a shuffled copy of questions. Then, you simply remove (and return) the first item:

def ask_random
  @remaining_questions = questions.shuffle if @remaining_questions.empty?
  @remaining_questions.shift
end

Upvotes: 2

Grak T
Grak T

Reputation: 26

I usally create a new array and after random I append the value which just random if it does not exist in new array.
if you get a same output like last time, it means output was in a new array because you appended it.
Sorry about my silly English.

Upvotes: -1

Related Questions