Somehow not assigning a class with Ruby - ruby

On runtime, my code often come into an undefined method error for the method mate. As far as I can figure, a Person somehow slips through the cracks sometime along the code's exucution, and manages not to have an allele assigned to it.
Code (disclaimer, not the best formatted):
class Allele
attr_accessor :c1, :c2
def initialize(c1, c2)
#c1 = c1
#c2 = c2
end
#formats it to be readable
def to_s
c1.to_s + c2.to_s
end
#given Allele a
def combine(a)
pick = rand(4)
case pick
when 0
Allele.new(c1,a.c1)
when 1
Allele.new(c1,a.c2)
when 2
Allele.new(c2,a.c1)
when 3
Allele.new(c2,a.c2)
end
end
end
class Person
attr_accessor :allele, :male
def initialize(allele,male)
#allele = allele
#male= male
end
#def self.num_people
###num_people
#end
def to_s
"Allele:" + allele.to_s + " | Male:" + male.to_s
end
def male
#male
end
def allele
#allele
end
def mate(p)
if rand(2) == 0
Person.new(allele.combine(p.allele),true)
else
Person.new(allele.combine(p.allele),false)
end
end
end
male_array = Array.new
female_array = Array.new
male_child_array = Array.new
female_child_array = Array.new
# EVENLY POPULATE THE ARRAY WITH 5 THAT PHENOTYPICALLY MANIFEST TRAIT, 5 THAT DON'T
# AND 5 GIRLS, 5 GUYS
pheno_dist = rand(5)
#make guys with phenotype
pheno_dist.times { male_array << Person.new(Allele.new(1,rand(2)),true) }
#guys w/o
(5-pheno_dist).times { male_array << Person.new(Allele.new(0,0),true) }
#girls w/ pheno
(5-pheno_dist).times { female_array << Person.new(Allele.new(1,rand(2)),false) }
#girls w/o
pheno_dist.times { female_array << Person.new(Allele.new(0,0),false) }
puts male_array
puts female_array
puts "----------------------"
4.times do
#mates male with females, adding children to children arrays. deletes partners as it iterates
male_array.each do
male_id = rand(male_array.length) #random selection function. adjust as needed
female_id = rand(female_array.length)
rand(8).times do
child = male_array[male_id].mate(female_array[female_id])
if child.male
male_child_array << child
else
female_child_array << child
end
end
male_array.delete_at(male_id)
female_array.delete_at(female_id)
end
#makes males male children, females female children, resets child arrays
male_array = male_child_array
female_array = female_child_array
male_child_array = []
female_child_array = []
puts male_array
puts female_array
puts "----------------------"
end
What immediately looks wrong?

As egosys says, you ought not to delete from an array over which you are iterating.
Another problem is in your loop that starts "4.times do". Sometimes the female array is empty, so returns size 0; rand(0) is a random float >= 0 and < 1. Using that as an array index on an empty female_array returns nil, which is then passed to mate.
But there's more than that wrong. You're iterating over male_array using each, but then picking a male at random. That allows some males to mate more than once; others not at all. Similarly, some females get to mate and reproduce more than once in each iteration, others not at all. Is that your intent?
Let's first consider keeping all of the males and females in the same array. This will simplify things. However, since you do need to sometimes find all the males and sometimes all the females, we'll make methods for that:
def males(population)
population.find_all do |person|
person.male?
end
end
def females(population)
population.find_all do |person|
person.female?
end
end
It would be more biologically accurate if males and females should be paired at random, but nobody gets to mate more than once. That's pretty simple:
def random_pairs(males, females)
males.shuffle[0...females.size].zip(females.shuffle)
end
Then reproduction of a population becomes, simply:
def make_children(male, female)
# return an array of children
end
def reproduce(population)
children = []
random_pairs(males(population), females(population)).each do |male, female|
children << make_children(male, female)
end
children
end
Having such functions, then doing 4 cycles of reproduction would be as simple as this:
people = # ... generate your initial list of people of all sexe.
4.times do
people = reproduce(people)
end
Since no function modifies the arguments passed to it, you will have no troubles with side-effects.
More could be done in an OO style, for example making Population a first-class object and moving the functions "males", "females", "random_pairs" and "reproduce" into it. I'll leave that as an exercise for the reader.

Deleting from an array which you are iterating with each has undefined behavior. Usually the advice is to use Array#delete_if, but I'm not sure how you would use it in this case.

Related

Trying to cull a set but the set keeps disappearing

I'm trying to program the AI for a Mastermind game in ruby using Donal Knuth's 5 guess algorithm. The game consists of a codemaker, who uses 8 different colored pegs to create a set of 4, and a codebreaker, who guesses at the code and receives feedback (a red square for a peg which is both the right color and in the right spot, and a white square for a peg which is the right color but in the wrong spot).
I've created a set for all possible codes. My goal is to compare feedback from the guess to feedback from all codes in the set, then delete the ones that don't match. It seems to delete the entire set though.
class ComputerPlayer < Player
def initialize(game)
super(game)
#all_possible_codes = create_codes
#turn = 1
end
def get_code
Array.new(4){rand(1..6)}
end
def get_guess
puts #all_possible_codes.length
if #turn == 0
#turn += 1
cull_set([1, 1, 2, 2])
#all_possible_codes.delete("1122")
return [1, 1, 2, 2]
else
random_sample = #all_possible_codes.to_a.sample.split('').map{|str| str.to_i}
#all_possible_codes.delete(random_sample.join(''))
cull_set(random_sample)
random_sample
end
end
def cull_set(guess)
feedback = #game.feedback_on_guess(guess)
puts feedback
#all_possible_codes.delete_if { |str| #game.feedback_on_guess(str.split.map{|num| num.to_i}) != feedback }
end
def create_codes
set = Set.new
(1..8).each do |i|
(1..8).each do |j|
(1..8).each do |k|
(1..8).each do |l|
set << [i, j, k, l].join('')
end
end
end
end
set
end
end
#this is the feedback_on_guess method used by the above class
def feedback_on_guess(code_guess)
code_duplicate = #code
feedback = []
code_duplicate.map.with_index do |entry, i|
if entry == code_guess[i]
feedback.push('r')
code_guess[i] = -1
-2
else
entry
end
end.each do |entry|
found_index = code_guess.find_index(entry)
if found_index
feedback.push('g')
code_guess[found_index] = -1
end
end
puts feedback
feedback
end
Try
copy = something.dup
because after just
copy = something
copy and something are pointing to the same object. You can confirm this by checking the object_id of the object referenced by the variable. If it is the same, then it is the same object.
When you dup an object, you will cretae a copy. Depending on what you want to dup you might need to implement/override the logic to create a copy. For built in Classes like String, Hash and so on it will work out of the box.
Be aware that nested constructs (eq. Hash containing other Hashes) are not duplicated.
h1 = {"a" => {"b" => 2}}
h2 = h1.dup
puts h1.object_id # 70199597610060
puts h2.object_id # 70199597627020
puts h1["a"].object_id # 70199597610080
puts h2["a"].object_id # 70199597610080

Trying to convert each to while loop, creates TypeError. Why is it having a problem with symbols?

I have a finished program, but now I need to convert an #Each loop to a #While loop. The loop should output almost the same information, but it throws me a 'directory.rb:24:in `print': no implicit conversion of Symbol into Integer (TypeError)' instead.
def input_students
puts "Please enter the names of the students"
puts "To finish, just hit return twice"
students = []
name = gets.chomp
while !name.empty? do
students << {name: name, cohort: :november}
puts "Now we have #{students.count} students"
name = gets.chomp
end
students
end
students = input_students
def print_header
puts "The students of Villains Academy"
puts "----------"
end
def print(students)
students.each.with_index(1) do |students, index|
puts "#{index} #{students[:name]}, #{students[:cohort]} cohort"
end
end
def print_footer(names)
puts "Overall we have #{names.count} great students"
end
print_header
print(students)
print_footer(students)
Works as expected. I'm trying:
def print(students)
i = 0
while i < students.length
puts "#{students[:name]}, #{students[:cohort]} cohort"
end
end
Why doesn't the #While loop work with similar input, and why is it trying to convert to an integer?
Because your #each loop was shadowing the students variable:
# v v
students.each.with_index(1) do |students, index|
puts "#{index} #{students[:name]}, #{students[:cohort]} cohort"
end
you iterate an array called students and then assign each element in the array to a variable named students. When you get rid of the each loop, you didn't change the block to stop looking at students, so it's now looking at the array. To get a single element try:
def print(students)
i = 0
while i < students.length
puts "#{students[i][:name]}, #{students[i][:cohort]} cohort"
end
end
while i < students.length
puts "#{students[:name]}, #{students[:cohort]} cohort"
end
students is an array. You can't address its elements with symbols. What you need to do is use i to fetch an element of students. You can call [:name] on that.
The mistake comes, I think, from poor naming in this snippet. And/or not understanding how each works.
students.each.with_index(1) do |students, index|
# ^^^^^^
# This here is called `students`, but its value is a single student,
# not a collection of students.

Values in the while loop do not modify outside values

I have a long code but I tried to copy and adapt my problem in as few lines as possible . I have a method which creates an array( 2D ) with 0 and 1
array1 = newValue(2) - the number 2 represents how many 1 the array has
array2 = newValue(3)
and this loop
(0..9).each do|i|
(0..9).each do|j|
while((array1[i][j] == array2[i][j]) && (array2[i][j] == 1)) do
array1 = newvalue(2)
array2 = newvalue(3)
end
end
end
I'm using the while loop so I won t have a 1 in the same position in both arrays . But what is inside the while loop doesn't modify the values of the array . I also tried using map!/collect! but I think I did something wrong because nothing happened. I hope you can understand what I was trying to do .
Edit:
def newValue(value)
value = value.to_i
array = Array.new(10) { Array.new(10 , 0) }
(a lot of conditions on how to position the items in the array)
return array
end
Here's my take... hopefully it'll help out. It seems that what you noticed was true. The arrays are not getting reset. Probably because inside the each blocks, the scope is lost. This is probably because the are arrays. I took a slightly different approach. Put everything in a class so you can have instance variables that you can control and you know where they are and that they are always the same.
I pulled out the compare_arrays function which just returns the coordinates of the match if there is one. If not it returns nil. Then, youre while loop is simplified in the reprocess method. If you found a match, reprocess until you don't have a match any more. I used a dummy newValue method that just returned another 2d array (as you suggested yours does). This seems to do the trick from what I can tell. Give it a whirl and see what you think. You can access the two arrays after all the processing with processor.array1 as you can see I did at the bottom.
# generate a random 2d array with 0's and val's
def generateRandomArray(val=1)
array = []
(0..9).each do |i|
(0..9).each do |j|
array[i] ||= []
array[i][j] = (rand > 0.1) ? 0 : val
end
end
array
end
array1 = generateRandomArray
array2 = generateRandomArray
def newValue(val)
generateRandomArray(val)
end
class Processor
attr_reader :array1, :array2
def initialize(array1, array2)
#array1 = array1
#array2 = array2
end
def compare_arrays
found = false
for ii in 0..9
break unless for jj in 0..9
if ((#array2[ii][jj] == 1) && (#array1[ii][jj] == 1))
found = true
break
end
end
end
[ii,jj] if found
end
def reprocess
while compare_arrays
puts "Reprocessing"
#array1 = newValue(2)
#array2 = newValue(3)
reprocess
end
end
end
processor = Processor.new(array1, array2)
processor.reprocess
puts processor.array1.inspect

Create An Array Class With Certain Operation To Implement

I am currently working on a basic Ruby programming project, that focuses on creating classes, and operations on those classes. I have very little experience, but understand the general idea of Ruby.
My task is making an Array2 class. Creating arrays from the class, perform operations on the arrays. The methods I attempted are a to-string method, and a is-reverse method that has two array parameters, and tests if the first array is the reverse of the second array.
Here is my attempt, I tried but I am having trouble passing the arrays properly into the class. Also I believe that I am having some calling complications.
class Array2
def initialize (a)
#array = Array.new(a)
end
def to_s
return #array
end
def isreverse (array1,array2)
reverasea = Array.new
reverasea = array1.reverse
if (reversea = array2) then
return "The First Array Is The Reverse Of The Second Array"
else
return "The First Array Is Not The Reverse Of The Second Array"
end
end
end
array1 = ["4","5","6","7"]
array2 = ["7","6","5","3"]
a1 = Array2.new(array1)
a2 = Array2.new(array2)
puts a1.to_s
puts a2.to_s
puts a1.isreverse(array1, array2)
You have an assignment where you probably meant equality test:
if (reversea = array2) then
you could dispense with reversea entirely and just test (this requires a reverse method in Array2)
if (array1.reverse == #array) then
I would personally make isreverse a boolean, and eliminate the need to pass in the same array again:
def isreverse? (array1)
return (#array.reverse == array1)
end
then use it like
puts "The First Array Is#{a1.isreverse?(a2)?"":" Not"} The Reverse Of The Second Array"
put it all together and it looks like:
class Array2
def initialize (a)
#array = Array.new(a)
end
def to_s
return #array
end
def reverse
#array.reverse
end
def isreverse? (array1)
return (array1.reverse == #array)
end
end
array1 = ["4","5","6","7"]
array2 = ["7","6","5","3"]
a1 = Array2.new(array1)
a2 = Array2.new(array2)
puts a1.to_s
puts a2.to_s
puts "The First Array Is#{a1.isreverse?(a2)?"":" Not"} The Reverse Of The Second Array"
fiddle
Here are some adjustments to your existing approach. I put in comments where I changed the original::
class Array2
def initialize (a)
#array = Array.new(a)
end
def to_array # to_s is a misnomer: it doesn't return a string
return #array
end
def isreverse (a)
#reverasea = Array.new NOTE this is not needed; the following .reverse creates a new array for you
reversea = a.to_array.reverse # get the reverse of the array represented
# NOTE = is assign, == is compare in this case
# The following compares the reversed of the array `a` with the array value of this class, #array
if (reversea == #array) then
return "The First Array Is The Reverse Of The Second Array"
else
return "The First Array Is Not The Reverse Of The Second Array"
end
end
end
array1 = ["4","5","6","7"]
array2 = ["7","6","5","3"]
a1 = Array2.new(array1)
a2 = Array2.new(array2)
puts a1.to_array # (renamed)
puts a2.to_array # (renamed)
#puts a1.isreverse(array1, array2) NOTE you don't need to pass array1 into class a1 since it is already made from array1
puts a1.isreverse(a2)
I would go for something simpler such as:
Filename: reverser.rb
class Reverser
def self.is_reverse_of(array1,array2)
array1_reversed=array1.reverse
is_or_isnt= (array1_reversed==array2)? 'Not ' : ''
return "The First Array Is #{is_or_isnt}The Reverse Of The Second Array"
end
end
puts Reverser.is_reverse_of(["4","5","6","7"], ["7","6","5","4"])
puts Reverser.is_reverse_of(["4","5","6","7"], ["7","6","5","3"])
ruby reverser.rb
The First Array Is Not The Reverse Of The Second Array
The First Array Is The Reverse Of The Second Array
The idea being to use a class level method and not instantiate as much and have less code.

Sort Array by Popularity and Time in Ruby

I am a Ruby Rails newbie.
Is there a way to know the popularity of elements in an Array over time?
For example lets say for the last 15 min..
The array has like ["abc", "ab", "abc", "a", "abc", "ab"........] being pushed into the array.. can we get "abc" and "ab" as the most popular ones.. just for the last 15 minutes?
If you take for an entire hour.. typical for the entire hour.."abcd" is the most popular.. it should return "abcd" as the most popular element in an array..
Is there a way to achieve this?
Create your own class which inherits from Array, or delegates all its functionality to an Array. For example:
class TimestampedArray
def initialize
#items = []
end
def <<(obj)
#items << [Time.now,obj]
end
# get all the items which were added in the last "seconds" seconds
# assumes that items are kept in order of add time
def all_from_last(seconds)
go_back_to = Time.now - seconds
result = []
#items.reverse_each do |(time,item)|
break if time < go_back_to
result.unshift(item)
end
result
end
end
If you have an old version of Ruby, which doesn't have reverse_each:
def all_from_last(seconds)
go_back_to = Time.now - seconds
result = []
(#items.length-1).downto(0) do |i|
time,item = #items[i]
break if time < go_back_to
result.unshift(item)
end
result
end
Then you need something to find the "most popular" item. I often use this utility function:
module Enumerable
def to_histogram
result = Hash.new(0)
each { |x| result[x] += 1 }
result
end
end
On which you could base:
module Enumerable
def most_popular
h = self.to_histogram
max_by { |x| h[x] }
end
end
So then you get:
timestamped_array.all_from_last(3600).most_popular # "most popular" in last 1 hour

Resources