Velocity Reviews > Ruby > [SOLUTION] Ruby Quiz #15 Animal Quiz

# [SOLUTION] Ruby Quiz #15 Animal Quiz

David Tran
Guest
Posts: n/a

 01-17-2005
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
| |<-------------------+
| V |
| [leaf ?] ----> [question and move to next node]
| |y n
| V
| [quest animal] ----> [learn]
| |y n |
| V |
| [show win text] |
| | |
| V |
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 LAY_AGAIN : play_again
end
end
end

private

# \$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)")
puts("I win. Pretty smart, aren't I?")
@state = LAY_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,
puts "For " + animal + ", what is the answer to your question? (y or n)"
@node[0,1] = [question, [animal], @node.dup]
else
@node[0,1] = [question, @node.dup, [animal]]
end
puts "Thanks."
@state = LAY_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

David Tran
Guest
Posts: n/a

 01-17-2005
Quick review, find a performance and memory issue ...

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

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

--

David Tran

Aquila
Guest
Posts: n/a

 01-17-2005
David Tran wrote:

> Quick review, find a performance and memory issue ...
>
> The code:
> @node[0,1]*=*[question,*[animal],*@node.dup]
> else
> @node[0,1]*=*[question,*@node.dup,*[animal]]
> end
>
> is better replace by:
> temp*=*@node
> @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"

David A. Black
Guest
Posts: n/a

 01-17-2005
Hi --

On Tue, 18 Jan 2005, David Tran wrote:

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

I believe that the second one is equivalent to:

@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
http://www.velocityreviews.com/forums/(E-Mail Removed)

David Tran
Guest
Posts: n/a

 01-17-2005
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"

On Mon, 17 Jan 2005 11:36:59 -0500, David Tran <(E-Mail Removed)> wrote:
> Quick review, find a performance and memory issue ...
>
> The code:
> @node[0,1] = [question, [animal], @node.dup]
> else
> @node[0,1] = [question, @node.dup, [animal]]
> end
>
> is better replace by:
> temp = @node
> @node[0,1] = [question, [animal], temp]
> else
> @node[0,1] = [question, temp, [animal]]
> end
>
> --
>
> David Tran
>

--

David Tran

David Tran
Guest
Posts: n/a

 01-17-2005
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 <(E-Mail Removed)> 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"
>
>
> On Mon, 17 Jan 2005 11:36:59 -0500, David Tran <(E-Mail Removed)> wrote:
> > Quick review, find a performance and memory issue ...
> >
> > The code:
> > @node[0,1] = [question, [animal], @node.dup]
> > else
> > @node[0,1] = [question, @node.dup, [animal]]
> > end
> >
> > is better replace by:
> > temp = @node
> > @node[0,1] = [question, [animal], temp]
> > else
> > @node[0,1] = [question, temp, [animal]]
> > end
> >

--

David Tran

David Tran
Guest
Posts: n/a

 01-20-2005
=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,
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

=end

require 'yaml'

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

# reuse Jim Weirich ConsoleUi class and modified
class ConsoleUi
print prompt + "\n"
end

end

def say(*msg)
puts msg
end
end

def ui
\$ui ||= ConsoleUi.new
end

questions = []
animals.each_value do |qs|
qs.each do |q|
q = q.abs;
if !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) ?
{ 'an elephant' => [] }

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

loop do
animals = db_animals.dup

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

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

animal = animals.keys[0]
ui.say "I win!"
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}, " +
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
# ...
end
else
db_questions << question
q = db_questions.size - 1
end
db_animals[animal] << (response ? -q : q)
db_animals[new_animal] << (response ? q : -q)
end

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

David Tran
Guest
Posts: n/a

 01-20-2005
Something need to be updated for better solution ...

ui.say "I win!"

is better replace by

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

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

Some as:
db_animals[animal] << (response ? -q : q)

is better replace by

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

--
David Tran

David Tran
Guest
Posts: n/a

 01-20-2005
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,
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

=end

require 'yaml'

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

# reuse Jim Weirich ConsoleUi class and modified
class ConsoleUi
print prompt + "\n"
end

end

def say(*msg)
puts msg
end
end

def ui
\$ui ||= ConsoleUi.new
end

questions = []
animals.each_value do |qs|
qs.each do |q|
q = q.abs;
if !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) ?
{ 'an elephant' => [] }

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

loop do
animals = db_animals.dup

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

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

animal = animals.keys[0]
ui.say "I win!"
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}, " +
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
# ...
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] << (response ? q : -q)
end

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

David Tran
Guest
Posts: n/a

 01-21-2005
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,
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

=end

require 'yaml'

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

# reuse Jim Weirich ConsoleUi class and modified
class ConsoleUi
print prompt + "\n"
end

end

def say(*msg)
puts msg
end
end

def ui
\$ui ||= ConsoleUi.new
end

questions = []
animals.each_value do |qs|
qs.each do |q|
q = q.abs;
if !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) ?
{ 'an elephant' => [] }

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

loop do
animals = db_animals.dup

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

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

animal = animals.keys[0]
ui.say "I win!"
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}, " +
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
# ...
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] << (response ? q : -q)
end

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