[SOLUTION] Ruby Quiz #15 Animal Quiz

Discussion in 'Ruby' started by David Tran, Jan 17, 2005.

  1. David Tran

    David Tran Guest

    Just got a chance to solve it today ...
    Here is my solution; many @todo exist, however, it responds to the Quiz.

    # Program : Ruby Quiz #15 Animal Quiz
    # Author : David Tran
    # Date : 2005-01-17

    =begin

    +------+
    | |
    | V
    | [init (load learned data?)]
    | |<-------------------+
    | V |
    | [leaf ?] ----> [question and move to next node]
    | |y n
    | V
    | [quest animal] ----> [learn]
    | |y n |
    | V |
    | [show win text] |
    | | |
    | V |
    +--- [ask play again] <-----+
    y |n
    V
    exit (persistence learned data ?)

    * use array of array to simulate binary research tree
    * @todo: maybe implement binary data structure
    * many @todo for future version

    =end

    class AnimalQuest

    def initialize
    @qtree = [['an elephant']] # @todo: maybe load saved data
    @state = :INIT
    end

    def play
    while (@state != :EXIT) do
    case @state
    when :INIT : init
    when :CHECK_TREE : check_tree
    when :QUEST_ANIMAL : quest_animal
    when :LEARN : learn
    when :pLAY_AGAIN : play_again
    end
    end
    end

    private

    def get_answer
    # $stdout.flush
    gets.chomp.upcase == 'Y'
    end

    def init
    @node = @qtree[0]
    puts "Think of an animal..."
    @state = :CHECK_TREE
    end

    def check_tree
    if @node.size == 1 # leaf node ?
    @state = :QUEST_ANIMAL
    else
    # @state = :CHECK_TREE # (state unchange)
    puts( @node[0] + " (y or n)" )
    @node = @node[get_answer ? 1 : 2]
    end
    end

    def quest_animal
    puts("Is it " + @node[0] + " (y or n)")
    if (get_answer)
    puts("I win. Pretty smart, aren't I?")
    @state = :pLAY_AGAIN
    else
    @state = :LEARN
    end
    end

    def learn
    puts "You win. Help me learn from my mistake before you go..."
    puts "What animal were you thinking of?"
    animal = gets.chomp
    # @todo: check if animal already exist on the database
    # then the player is cheating!! => show cheating message
    puts "Give me a question to distinquish " + animal + " from " +
    @node[0] + "."
    question = gets.chomp
    # @todo: check conflict of question,
    # for example, question already asked before.
    puts "For " + animal + ", what is the answer to your question? (y or n)"
    if get_answer
    @node[0,1] = [question, [animal], @node.dup]
    else
    @node[0,1] = [question, @node.dup, [animal]]
    end
    puts "Thanks."
    @state = :pLAY_AGAIN
    end

    def play_again
    puts("Play again? (y or n)")
    @state = get_answer ? :INIT : :EXIT
    # @todo: save learned data before exit
    end

    end

    AnimalQuest.new.play

    --

    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 17, 2005
    #1
    1. Advertising

  2. David Tran

    David Tran Guest

    Quick review, find a performance and memory issue ...

    The code:
    if get_answer
    @node[0,1] = [question, [animal], @node.dup]
    else
    @node[0,1] = [question, @node.dup, [animal]]
    end

    is better replace by:
    temp = @node
    if get_answer
    @node[0,1] = [question, [animal], temp]
    else
    @node[0,1] = [question, temp, [animal]]
    end

    --

    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 17, 2005
    #2
    1. Advertising

  3. David Tran

    Aquila Guest

    David Tran wrote:

    > Quick review, find a performance and memory issue ...
    >
    > The code:
    > if get_answer
    > @node[0,1] = [question, [animal], @node.dup]
    > else
    > @node[0,1] = [question, @node.dup, [animal]]
    > end
    >
    > is better replace by:
    > temp = @node
    > if get_answer
    > @node[0,1] = [question, [animal], temp]
    > else
    > @node[0,1] = [question, temp, [animal]]
    > end


    I'm very new to Ruby performance: why? In other languages this really isn't
    a performance neither a memory issue, what is the problem here?

    Always keen to learn...
    --
    "May the source be with you"
     
    Aquila, Jan 17, 2005
    #3
  4. Hi --

    On Tue, 18 Jan 2005, David Tran wrote:

    > Quick review, find a performance and memory issue ...
    >
    > The code:
    > if get_answer
    > @node[0,1] = [question, [animal], @node.dup]
    > else
    > @node[0,1] = [question, @node.dup, [animal]]
    > end
    >
    > is better replace by:
    > temp = @node
    > if get_answer
    > @node[0,1] = [question, [animal], temp]
    > else
    > @node[0,1] = [question, temp, [animal]]
    > end


    I believe that the second one is equivalent to:

    if get_answer
    @node[0,1] = [question, [animal], @node]
    else
    @node[0,1] = [question, @node, [animal]]
    end

    since temp is just another reference to the object that @node is a
    reference to, not to a copy of that object. So you end up with a
    recursive data structure.


    David

    --
    David A. Black
     
    David A. Black, Jan 17, 2005
    #4
  5. David Tran

    David Tran Guest

    I try to avoid "dup" (and reuse the "pointer"), but the way I did is
    not good, it creates recursive array.
    So the changed code below is not good. Sorry for the confuse.

    BTW: My english is really poor; in my code, all the word "quest"
    should read as "guess" instead.


    On Mon, 17 Jan 2005 11:36:59 -0500, David Tran <> wrote:
    > Quick review, find a performance and memory issue ...
    >
    > The code:
    > if get_answer
    > @node[0,1] = [question, [animal], @node.dup]
    > else
    > @node[0,1] = [question, @node.dup, [animal]]
    > end
    >
    > is better replace by:
    > temp = @node
    > if get_answer
    > @node[0,1] = [question, [animal], temp]
    > else
    > @node[0,1] = [question, temp, [animal]]
    > end
    >
    > --
    >
    > David Tran
    > http://www.doublegifts.com
    >



    --

    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 17, 2005
    #5
  6. David Tran

    David Tran Guest

    There are no performance and memory issue,
    If you look at origin source code,
    the "dup" is only to duplicate array of 1 element.

    so either :
    @node[0,1] = [question, [animal], @node.dup]
    or
    @node[0,1] = [question, [animal], [@node[0]]]
    works, but not
    @node[0,1] = [question, [animal], @node]

    I try to make it better but it is unnecessary.
    Sorry again for my mistake.
    I am newbie on Ruby too. ;-)

    On Mon, 17 Jan 2005 18:22:14 -0500, David Tran <> wrote:
    > I try to avoid "dup" (and reuse the "pointer"), but the way I did is
    > not good, it creates recursive array.
    > So the changed code below is not good. Sorry for the confuse.
    >
    > BTW: My english is really poor; in my code, all the word "quest"
    > should read as "guess" instead.
    >
    >
    > On Mon, 17 Jan 2005 11:36:59 -0500, David Tran <> wrote:
    > > Quick review, find a performance and memory issue ...
    > >
    > > The code:
    > > if get_answer
    > > @node[0,1] = [question, [animal], @node.dup]
    > > else
    > > @node[0,1] = [question, @node.dup, [animal]]
    > > end
    > >
    > > is better replace by:
    > > temp = @node
    > > if get_answer
    > > @node[0,1] = [question, [animal], temp]
    > > else
    > > @node[0,1] = [question, temp, [animal]]
    > > end
    > >


    --

    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 17, 2005
    #6
  7. David Tran

    David Tran Guest

    =begin
    My second solution.

    Most solutions do a tree walk.
    Kids will get boring soon,
    because it always ask the questions in the same order.
    No fun at all...

    Here I try to do "ask question in random order".
    ( ==> Not good to quick find the answer. )


    Random select "possible" question.
    If we try to count the "weight" of the possible questions,
    and select the "heaviest" one, we end up like tree walk order.
    Except, if there are equal-heavy, example:
    Q1 Q2
    / \ ==> / \
    Q2 Q2 Q1 Q1

    Note: You could change the program to take
    the average weigth question instead of
    random select.

    Since we random ask "possible" questions,
    that may help to get more information about
    existing knowledge animal, example:
    Q1 ==> Q1
    / \ / \
    Q2 c Q2 Q2
    / \ / \ \
    a b a b Q3
    / \
    c d

    ==> this may happend ask Q2 first,
    and finally distinct c,d by Q3.


    A little explanation about my data structure:
    * db_questions: array to store questions. (index 0 no use)
    * db_animals: hash; key == animals,
    value == array of questions, the absolute value map
    to db_questions's index; and positive for 'Yes' answer
    and negative for 'No' answer.

    =end

    require 'yaml'

    ANIMALS_FILE = 'animals.yaml'
    QUESTIONS_FILE = 'questions.yaml'


    # reuse Jim Weirich ConsoleUi class and modified
    class ConsoleUi
    def ask(prompt)
    print prompt + "\n"
    answer = gets
    answer ? answer.chomp : nil
    end

    def ask_if(prompt)
    answer = ask(prompt + " (y or n)")
    answer =~ /^\s*[Yy]/
    end

    def say(*msg)
    puts msg
    end
    end

    def ui
    $ui ||= ConsoleUi.new
    end

    def get_possible_questions(animals, asked_questions)
    questions = []
    animals.each_value do |qs|
    qs.each do |q|
    q = q.abs;
    if !questions.include?(q) &&
    !asked_questions.include?(q) &&
    !asked_questions.include?(-q)
    questions << q
    end
    end
    end
    questions
    end

    def filter_animals(animals, question)
    animals.each do |animal, questions|
    animals.delete(animal) if questions.include? question
    end
    end

    db_animals = File.exist?(ANIMALS_FILE) ?
    YAML.load_file(ANIMALS_FILE) :
    { 'an elephant' => [] }

    db_questions = File.exist?(QUESTIONS_FILE) ?
    YAML.load_file(QUESTIONS_FILE) :
    [ '' ]

    loop do
    asked_questions = []
    animals = db_animals.dup

    ui.say "Think of an animal..."

    while animals.size > 1
    qs = get_possible_questions(animals, asked_questions)
    q = qs[rand(qs.size)]
    q = -q unless ui.ask_if db_questions[q]
    asked_questions << q
    filter_animals(animals, -q)
    end

    animal = animals.keys[0]
    if ui.ask_if "Is it #{animal}?"
    ui.say "I win!"
    # update knowledge, we may have more infomation
    # about the animal, since we random asked questions
    animals[animal] = asked_questions.dup
    else
    ui.say "You win. Help me play better next time."
    new_animal = ui.ask "What animal were you thinking of?"
    question = ui.ask "Give me a question to distinguish " +
    "#{animal} from #{new_animal}."
    response = ui.ask_if "For #{new_animal}, " +
    "what is the answer to your question?"
    ui.say "Thanks."

    if db_animals.key?(new_animal)
    ui.say "Hey! You are cheating, accroding asked questions," +
    "it cannot be #{new_animal}."
    # ...
    end

    q = db_questions.index(question)
    if q
    if asked_questions.include?(q) || asked_questions.include?(-q)
    ui.say "Hey! That question already asked! You try to confuse me."
    # ...
    end
    else
    db_questions << question
    q = db_questions.size - 1
    end
    db_animals[animal] = asked_questions.dup
    db_animals[animal] << (response ? -q : q)
    db_animals[new_animal] = asked_questions.dup
    db_animals[new_animal] << (response ? q : -q)
    end

    break unless ui.ask_if "Play again?"
    ui.say "\n\n"
    end

    open(ANIMALS_FILE, 'w') { |f| f.puts db_animals.to_yaml }
    open(QUESTIONS_FILE, 'w') { |f| f.puts db_questions.to_yaml }


    --

    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 20, 2005
    #7
  8. David Tran

    David Tran Guest

    Something need to be updated for better solution ...

    ui.say "I win!"
    animals[animal] = asked_question.dup

    is better replace by

    ui.say "I win!"
    animals[animal] += asked_question
    animals[animal].uniq!

    (if not, it may lose some old knowledge about the animal ... )

    Some as:
    db_animals[animal] = asked_questions.dup
    db_animals[animal] << (response ? -q : q)

    is better replace by

    db_animals[animal] += asked_questions
    db_animals[animal] << (response ? -q : q)
    db_animals[animal].uniq!

    --
    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 20, 2005
    #8
  9. David Tran

    David Tran Guest

    Just found a bug, animals[animal] += asked_question
    should be db_animals[animal] += asked_question ... etc

    Resend the correct one:

    =begin
    My second solution.

    Most solutions do a tree walk.
    Kids will get boring soon,
    because it always ask the questions in the same order.
    No fun at all...

    Here I try to do "ask question in random order".
    ( ==> Not good to quick find the answer. )


    Random select "possible" question.
    If we try to count the "weight" of the possible questions,
    and select the "heaviest" one, we end up like tree walk order.
    Except, if there are equal-heavy, example:
    Q1 Q2
    / \ ==> / \
    Q2 Q2 Q1 Q1

    Note: You could change the program to take
    the average weigth question instead of
    random select.

    Since we random ask "possible" questions,
    that may help to get more information about
    existing knowledge animal, example:
    Q1 ==> Q1
    / \ / \
    Q2 c Q2 Q2
    / \ / \ \
    a b a b Q3
    / \
    c d

    ==> this may happend ask Q2 first,
    and finally distinct c,d by Q3.


    A little explanation about my data structure:
    * db_questions: array to store questions. (index 0 no use)
    * db_animals: hash; key == animals,
    value == array of questions, the absolute value map
    to db_questions's index; and positive for 'Yes' answer
    and negative for 'No' answer.

    =end

    require 'yaml'

    ANIMALS_FILE = 'animals.yaml'
    QUESTIONS_FILE = 'questions.yaml'


    # reuse Jim Weirich ConsoleUi class and modified
    class ConsoleUi
    def ask(prompt)
    print prompt + "\n"
    answer = gets
    answer ? answer.chomp : nil
    end

    def ask_if(prompt)
    answer = ask(prompt + " (y or n)")
    answer =~ /^\s*[Yy]/
    end

    def say(*msg)
    puts msg
    end
    end

    def ui
    $ui ||= ConsoleUi.new
    end

    def get_possible_questions(animals, asked_questions)
    questions = []
    animals.each_value do |qs|
    qs.each do |q|
    q = q.abs;
    if !questions.include?(q) &&
    !asked_questions.include?(q) &&
    !asked_questions.include?(-q)
    questions << q
    end
    end
    end
    questions
    end

    def filter_animals(animals, question)
    animals.each do |animal, questions|
    animals.delete(animal) if questions.include? question
    end
    end

    db_animals = File.exist?(ANIMALS_FILE) ?
    YAML.load_file(ANIMALS_FILE) :
    { 'an elephant' => [] }

    db_questions = File.exist?(QUESTIONS_FILE) ?
    YAML.load_file(QUESTIONS_FILE) :
    [ '' ]

    loop do
    asked_questions = []
    animals = db_animals.dup

    ui.say "Think of an animal..."

    while animals.size > 1
    qs = get_possible_questions(animals, asked_questions)
    q = qs[rand(qs.size)]
    q = -q unless ui.ask_if db_questions[q]
    asked_questions << q
    filter_animals(animals, -q)
    end

    animal = animals.keys[0]
    if ui.ask_if "Is it #{animal}?"
    ui.say "I win!"
    # update knowledge, we may have more infomation
    # about the animal, since we random asked questions
    db_animals[animal] += asked_questions
    db_animals[animal].uniq!
    else
    ui.say "You win. Help me play better next time."
    new_animal = ui.ask "What animal were you thinking of?"
    question = ui.ask "Give me a question to distinguish " +
    "#{animal} from #{new_animal}."
    response = ui.ask_if "For #{new_animal}, " +
    "what is the answer to your question?"
    ui.say "Thanks."

    if db_animals.key?(new_animal)
    ui.say "Hey! You are cheating, accroding asked questions," +
    "it cannot be #{new_animal}."
    # ...
    end

    q = db_questions.index(question)
    if q
    if asked_questions.include?(q) || asked_questions.include?(-q)
    ui.say "Hey! That question already asked! You try to confuse me."
    # ...
    end
    else
    db_questions << question
    q = db_questions.size - 1
    end
    db_animals[animal] += asked_questions
    db_animals[animal] << (response ? -q : q)
    db_animals[animal].uniq!
    db_animals[new_animal] = asked_questions.dup
    db_animals[new_animal] << (response ? q : -q)
    end

    break unless ui.ask_if "Play again?"
    ui.say "\n\n"
    end

    open(ANIMALS_FILE, 'w') { |f| f.puts db_animals.to_yaml }
    open(QUESTIONS_FILE, 'w') { |f| f.puts db_questions.to_yaml }

    --
    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 20, 2005
    #9
  10. David Tran

    David Tran Guest

    Previous exists logic error
    Correct again...

    =begin
    My second solution.

    Most solutions do a tree walk.
    Kids will get boring soon,
    because it always ask the questions in the same order.
    No fun at all...

    Here I try to do "ask question in random order".
    ( ==> Not good to quick find the answer. )


    Random select "possible" question.
    If we try to count the "weight" of the possible questions,
    and select the "heaviest" one, we end up like tree walk order.
    Except, if there are equal-heavy, example:
    Q1 Q2
    / \ ==> / \
    Q2 Q2 Q1 Q1

    Note: You could change the program to take
    the average weigth question instead of
    random select.

    Since we random ask "possible" questions,
    that may help to get more information about
    existing knowledge animal, example:
    Q1 ==> Q1
    / \ / \
    Q2 c Q2 Q3
    / \ / \ / \
    a b a b Q2 c
    \
    d

    ==> this may happend ask Q2 first, then Q1,
    and finally distinct c,d by Q3.


    A little explanation about my data structure:
    * db_questions: array to store questions. (index 0 no use)
    * db_animals: hash; key == animals,
    value == array of questions, the absolute value map
    to db_questions's index; and positive for 'Yes' answer
    and negative for 'No' answer.

    =end

    require 'yaml'

    ANIMALS_FILE = 'animals.yaml'
    QUESTIONS_FILE = 'questions.yaml'


    # reuse Jim Weirich ConsoleUi class and modified
    class ConsoleUi
    def ask(prompt)
    print prompt + "\n"
    answer = gets
    answer ? answer.chomp : nil
    end

    def ask_if(prompt)
    answer = ask(prompt + " (y or n)")
    answer =~ /^\s*[Yy]/
    end

    def say(*msg)
    puts msg
    end
    end

    def ui
    $ui ||= ConsoleUi.new
    end

    def get_possible_questions(animals, asked_questions)
    questions = []
    animals.each_value do |qs|
    qs.each do |q|
    q = q.abs;
    if !questions.include?(q) &&
    !asked_questions.include?(q) &&
    !asked_questions.include?(-q)
    questions << q
    end
    end
    end
    questions
    end

    def filter_animals(animals, question)
    animals.each do |animal, questions|
    animals.delete(animal) if questions.include? question
    end
    end

    db_animals = File.exist?(ANIMALS_FILE) ?
    YAML.load_file(ANIMALS_FILE) :
    { 'an elephant' => [] }

    db_questions = File.exist?(QUESTIONS_FILE) ?
    YAML.load_file(QUESTIONS_FILE) :
    [ '' ]

    loop do
    asked_questions = []
    animals = db_animals.dup

    ui.say "Think of an animal..."

    while animals.size > 1
    qs = get_possible_questions(animals, asked_questions)
    q = qs[rand(qs.size)]
    q = -q unless ui.ask_if db_questions[q]
    asked_questions << q
    filter_animals(animals, -q)
    end

    animal = animals.keys[0]
    if ui.ask_if "Is it #{animal}?"
    ui.say "I win!"
    # update knowledge, we may have more infomation
    # about the animal, since we random asked questions
    db_animals[animal] += asked_questions
    db_animals[animal].uniq!
    else
    ui.say "You win. Help me play better next time."
    new_animal = ui.ask "What animal were you thinking of?"
    question = ui.ask "Give me a question to distinguish " +
    "#{animal} from #{new_animal}."
    response = ui.ask_if "For #{new_animal}, " +
    "what is the answer to your question?"
    ui.say "Thanks."

    if db_animals.key?(new_animal)
    ui.say "Hey! You are cheating, accroding asked questions," +
    "it cannot be #{new_animal}."
    # ...
    end

    q = db_questions.index(question)
    if q
    if asked_questions.include?(q) || asked_questions.include?(-q)
    ui.say "Hey! That question already asked! You try to confuse me."
    # ...
    end
    else
    db_questions << question
    q = db_questions.size - 1
    end
    db_animals[animal] << (response ? -q : q)
    db_animals[animal].uniq!
    db_animals[new_animal] = asked_questions
    db_animals[new_animal] << (response ? q : -q)
    end

    break unless ui.ask_if "Play again?"
    ui.say "\n\n"
    end

    open(ANIMALS_FILE, 'w') { |f| f.puts db_animals.to_yaml }
    open(QUESTIONS_FILE, 'w') { |f| f.puts db_questions.to_yaml }
    --

    David Tran
    http://www.doublegifts.com
     
    David Tran, Jan 21, 2005
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Steve Green

    is there such an animal

    Steve Green, Mar 24, 2005, in forum: Java
    Replies:
    8
    Views:
    403
    Joona I Palaste
    Mar 25, 2005
  2. cyber science
    Replies:
    0
    Views:
    291
    cyber science
    Sep 1, 2009
  3. email55555 email55555

    [SOLUTION] Ruby Quiz #14 LCD Numbers ( solution #2 )

    email55555 email55555, Jan 9, 2005, in forum: Ruby
    Replies:
    16
    Views:
    295
    David Tran
    Jan 10, 2005
  4. Ruby Quiz

    [QUIZ] Animal Quiz (#15)

    Ruby Quiz, Jan 14, 2005, in forum: Ruby
    Replies:
    11
    Views:
    402
    James Edward Gray II
    Jan 18, 2005
  5. Ruby Quiz

    [SUMMARY] Animal Quiz (#15)

    Ruby Quiz, Jan 20, 2005, in forum: Ruby
    Replies:
    0
    Views:
    158
    Ruby Quiz
    Jan 20, 2005
Loading...

Share This Page