3

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

2
  • 1
    This gets interesting if you have an huge (or infinite) number of questions that you simply can't load in memory. Then you can use some tricks of number theory to make your picks random enough. For example - any n consecutive numbers raised to the power of a prime number by modulo of n give the numbers from 0 to n - 1. Commented Jan 10, 2017 at 13:02
  • Oops, I lied a little. The prime number shouldn't be even (aka 2). Commented Jan 10, 2017 at 14:55

5 Answers 5

2

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"]
Sign up to request clarification or add additional context in comments.

Comments

2

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

6 Comments

Won’t @remaining_questions ||= questions.shuffle without a constructor make it a bit less overdesigned?
@mudasobwa that would only work once because [] is truthy
Since we have introduced a class, it’s lifetime might be easily shrunk to “new ⇒ ask ⇒ stay.” Need new questionnaire ⇒ create new object. Anyway, it’s a matter of out of scope implementation design.
@mudasobwa I assumed that ask_random is supposed to return a single question and that you can keep calling it indefinitely. Hence the overhead.
Thanks for Stefan and Holger Just, but somehow this this thows an undefined method empty?' for nil:NilClass (NoMethodError)` exception, when I try to use it.
|
0

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

6 Comments

@ndn people tend to downvote the solutions they do not understand, and ruby community has a negative impact of rails-way uglyness.
@ndn I've run the code and the output was not as expected by the author. It could be the reason, I suppose.
@fl00r the OP clearly states “The output should be something like.” Using OOP here is so much an overkill, that it is nearly a code smell.
@mudasobwa yep, that's true that the OP has a vague definition what he wants. I am with you that OOP in many cases is overkill, but anyway, this is a very arguable statement you've stated :).
I'm curious why do you use this construction question.tap { |q| puts "Asking question #{q}" } instead of simple puts "Asking question #{question}". Why did you use tap here?
|
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?" 

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.

3 Comments

Mmm. I would say that here is too much of global state. I would add some encapsulation either by wrapping the state into a lambda/fiber/enumerator, or into some class. One of side effects is if you will reuse your method with different inputs: fire_away(q1); fire_away(q2)
@fl00r, excellent suggestion! I hadn't seen that technique before. I'll edit.
-1

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.

1 Comment

It increases an operational cost thrice plus it fails on arrays having duplicates. This is the worst possible approach to achieve the goal.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.