reset a count in ruby when putting to screen from parallel arrays - ruby

This is home work so I would prefer not to put up my code. I have 2 parallel arrays, 1.names 2. ages. The idea is to puts all ages less than 21. I can do this. The problem is that when I puts "#{count}. #{names[count]}, #{ages[count]}" <---The beginning count prints out the index number or position of element in array. Obviously what I want is for it to start at 1. if there are three names...
name, age
name, age
name, age
NOT
5, name, age
6, name, age
I am using a while loop with an if statement. I don't need code, just would like some feedback to trigger more ideas. Thanks for your time, much appreciated.
names[name1, name2, name3]
ages[age1, age2, age3]
#view all younger than 21
count = 0
while count < names.length
if ages[count] < 21
puts "#{count}. #{names[count]}, #{ages[count]}" #works
end
count += 1
end
pause

You shouldn't have "parallel arrays" in the first place! Data that belongs together should be manipulated together, not separately.
Instead of something like
names = %w[john bob alice liz]
ages = [16, 22, 18, 23 ]
You could, for example, have a map (called Hash in Ruby):
people = { 'john' => 16, 'bob' => 22, 'alice' => 18, 'liz' => 23 }
Then you would have something like:
puts people.select {|_name, age| age > 21 }.map.
with_index(1) {|(name, age), i| "#{i}. #{name}, #{age}" }
# 1. bob, 22
# 2. liz, 23
If you have no control over the creation of those parallel arrays, then it is still better to convert them to a sensible data structure first, and avoid the pain of having to juggle them in your algorithm:
people = Hash[names.zip(ages)]
Even better yet: you should have Person objects. After all, Ruby is object-oriented, not array-oriented or hash-oriented:
class Person < Struct.new(:name, :age)
def to_s
"#{name}, #{age}"
end
def adult?
age > 21
end
end
people = [
Person.new('john', 16),
Person.new('bob', 22),
Person.new('alice', 18),
Person.new('liz', 23)]
puts people.select(&:adult?).map.with_index(1) {|p, i| "#{i}. #{p}" }
Again, if you don't have control of the creation of those two parallel arrays, you can still convert them first:
people = names.zip(ages).map {|name, age| Person.new(name, age) }

Related

Student Marks Hash

I've seen solutions posted in other languages but not Ruby so I'm asking here.
I'm trying to create a student marks system using Ruby, this should take the student’s name and two marks from the user for that particular student.
I decided to try and store these in a hash so it would end up looking something like:
student_marks = {
"Steve" => 45, 65,
"James" => 20, 75,
"Scott" => 30, 90
}
My code attempt is as follows:
continue = "y"
student_grades = Hash.new
while continue == "y"
puts "Please enter student name"
name = gets.chomp
puts "Please enter the first grade for #{name}"
grade_one = gets.chomp.to_i
puts "Please enter the second grade for #{name}"
grade_two = gets.chomp.to_i
student_grades.each do |key, value|
student_grades[key] = name
student_grades[value] = grade_one
student_grades[value][1] = grade_two
end
puts "Do you want to continue? y or n"
continue = gets.chomp
end
puts student_grades
Obviously there is a problem with my logic in trying to populate the hash using each/iteration because I keep getting nil return. I guess I could use arrays and populate the hash from them, but is there a way to populate the hash both the keys and values using iteration from user input?
You seem to be misunderstanding how hashes work in Ruby. I would suggest reading up on it a bit.
In the meantime, try this:
student_grades = Hash.new {|h,k| h[k] = [] }
Now every time you get a student grade, you can push it into the array value for that student's hash key.
For example:
student_grades
=> {}
student_grades['Mark'] << 95
student_grades['Mark'] << 86
student_grades
=> {'Mark' => [95, 86]}
You don't need to iterate over the hash at all in each run of your loop, you should only be doing that if you need to extract some information from it.

Assign hash key and value from string using split

I have a few strings that I am retrieving from a file birthdays.txt. An example of a string is below:
Christopher Alexander, Oct 4, 1936
I would like to separate the strings and let variable name be a hash key and birthdate the hash value. Here is my code:
birthdays = {}
File.read('birthdays.txt').each_line do |line|
line = line.chomp
name, birthdate = line.split(/\s*,\s*/).first
birthdays = {"#{name}" => "#{birthdate}"}
puts birthdays
end
I managed to assign name to the key. However, birthdate returns "".
File.new('birthdays.txt').each.with_object({}) do
|line, birthdays|
birthdays.store(*line.chomp.split(/\s*,\s*/, 2))
puts birthdays
end
I feel like some of the other solutions are overthinking this a bit. All you need to do is split each line into two parts, the part before the first comma and the part after, which you can do with line.split(/,\s*/, 2), then call to_h on the resulting array of arrays:
data = <<END
Christopher Alexander, Oct 4, 1936
Winston Churchill, Nov 30, 1874
Max Headroom, Apr 4, 1985
END
data.each_line.map do |line|
line.chomp.split(/,\s*/, 2)
end.to_h
# => { "Christopher Alexander" => "Oct 4, 1936",
# "Winston Churchill" => "Nov 30, 1874",
# "Max Headroom" => "April 4, 1985" }
(You will, of course, want to replace data with your File object.)
birthdays = Hash.new
File.read('birthdays.txt').each_line do |line|
line = line.chomp
name, birthdate = line.split(/\s*,\s*/, 2)
birthdays[name]= birthdate
puts birthdays
end
Using #Jordan's data:
data.each_line.with_object({}) do |line, h|
name, _, bdate = line.chomp.partition(/,\s*/)
h[name] = bdate
end
#=> {"Christopher Alexander"=>"Oct 4, 1936",
# "Winston Churchill"=>"Nov 30, 1874",
# "Max Headroom"=>"Apr 4, 1985"}

Counting the number of times a value is repeated in a Hash

I am pulling a hash from mashable.com, and I need to count instances of author names (author is the key, and the value is the author name). mashable's api
{
new: [
{
other_keys: 'other_values'...
author: 'Author's Name'
}
]
I want to iterate over the hash and pull out the author's name, and then count the amount of times it is repeated in the entire list from the mashable api.
Here is what I have; it turns the hash into an array, iterates over it, adding the count to each author name as the key, and then adds the number of repeats as the value.
This would be great, but I can't get it back into my original hash from mashable to add all of the other hash items I want to display.
all_authors = []
all_stories.each do |story|
authors = story['author']
all_authors << authors
end
counts = Hash.new(0)
all_authors.each do |name|
counts[name] += 1
end
counts.each do |key, val|
puts "#{key}: " "#{val}"
end
That does what it is supposed to, but I try to put it back into the original hash from mashable:
all_stories.each do |com|
plorf = com['comments_count'].to_i
if plorf < 1
all_stories.each do |story|
puts "Title:\n"
puts story['title']
puts "URL:\n"
puts story['short_url']
puts "Total Shares:\n"
puts story['shares']['total']
end
end
end
When I drop the code back in to that iteration, all it does is iterate of the initial has, and after each entry, I get a list of all authors and the number of stories they have written, instead of listing each author connected to the other information about each story and the number of stories they have written.
Any help is greatly appreciated.
Here's a simplified version:
h = { a: 1, b: 2, c: 1, d: 1 }
h.count { |_, v| v == 1 } #=> 3
h.values.count(1) #=> 3
Alternatively you can also group by key and then count:
h.group_by(&:last).map { |v, a| [v, a.count] }.to_h #=> {1=>3, 2=>1}
This groups the hash by its values, the counts the times elements in the array of key/value pairs. Here's a more explicit version:
grouped = h.group_by(&:last) #=> {1=>[[:a, 1], [:c, 1], [:d, 1]], 2=>[[:b, 2]]}
grouped.map { |v, a| [v, a.count] #=> [[1, 3], [2, 1]]
Then the final to_h turns the array of 2 element arrays into a hash.
#Michael Kohl it was a good answer, I think I was asking the question wrong. I wound up doing this:
author = story['author']
puts "Number of stories by #{story['author']}: #{author_count['author']}"
inside my "all_stories" loop...
yeah I am pretty sure I was trying to "re-inject" the values to the original hash, and that was way wrong...
Thanks so much for your help though

Find max value in a class in ruby

I've tried for several hours making this work.
i have this code where i have around 20 different persons, with different ages and names.
how can i make ruby searching through all the ages and show the highest age as and integer
i've been searching a lot, but cant seem to make it work. i've even tried to make it sort the numbers and the print the last age, which must be the highest number
def initialize(firstname, familyname, age)
#firstname = firstname
#familyname = familyname
#age = age
Best regards
If you have a class like this:
class Person
attr_accessor :age
def initialize(age)
#age = age
end
end
And an array like this:
people = [Person.new(10), Person.new(20), Person.new(30)]
Finding the maximum age
You can get the ages with Array#map:
people.map { |person| person.age }
#=> [10, 20, 30]
# or shorter
people.map(&:age)
#=> [10, 20, 30]
And the maximum value with Enumerable#max:
people.map(&:age).max
#=> 30
Finding the oldest person
Or you could find the oldest person with Enumerable#max_by:
oldest = people.max_by { |person| person.age }
#=> #<Person:0x007fef4991d0a8 #age=30>
# or shorter
oldest = people.max_by(&:age)
#=> #<Person:0x007fef4991d0a8 #age=30>
And his or her age with:
oldest.age
#=> 30
Say for example your class looks like this:
class Person
attr_reader :firstname, :familyname, :age
def initialize(firstname, familyname, age)
#firstname = firstname
#familyname = familyname
#age = age
end
end
And say that you have an array of these objects called people. Then you could do something like:
puts people.max_by { |p| p.age }.age
Use max
e.g
class C
attr_accessor :age
def initialize(age)
#age = age
end
end
a,b,c,d = C.new(10), C.new(2), C.new(22), C.new(15)
[a,b,c,d].map(&:age).max #=> 22
[a.age,b.age,c.age,d.age].max #=> 22
Once you've collected all the instances into an array, using one of the techniques shown in How to get class instances in Ruby?, you can use the technique shown in Finding the element of a Ruby array with the maximum value for a particular attribute to find the maximum age. For example, if Foo is your class and age is an attribute of your class, the following should work:
ObjectSpace.each_object.select{|foo| obj.class == Foo}.max_by {|foo| foo.age}

Somehow not assigning a class with 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.

Resources