Class-Array Interaction Ruby - arrays

I'm trying to set up a program to help me take care of grading for students in a class. I've set it up to make a class of student then to read in from the file (something I'm not very familiar with in Ruby) via an array. My programming experience is in java so if there are errors that can be explained by that I apologize. Thank you in advance for your help.
class Student
def initialize(str_LastName, str_FirstName, arr_Score)
#str_LastName = str_LastName
#str_FirstName = str_FirstName
#arr_Score = arr_Score
str_Grade = ""
int_OutOf = 415
end
def get_LastName
str_LastName
end
def get_FirstName
str_FirstName
end
def get_Grade
str_Grade
end
def set_TotalScore()
sum = 0
arr_Score.each do |item|
sum += item
end
arr_Score[12] = sum
end
def set_Grade
if arr_Score[12]/int_OutOf >= 0.9
str_Grade = "A"
elsif arr_Score[12]/int_OutOf >= 0.8
str_Grade = "B"
elsif arr_Score[12]/int_OutOf >= 0.7
str_Grade = "C"
elsif arr_Score[12]/int_OutOf >= 0.6
str_Grade = "D"
else
str_Grade = "F"
end
end
end
def main
file_name = "Grades"
arr_students = Array.new(31)
arr_scores = Array.new(12)
int_i = 0
file_io = open(file_name).readlines.each do |line|
array = line.split(",").map(&:strip)
student = Student.new(array[0],array[1],array[2..-2]) #the final element in the array is for the final score
arr_students[int_i] = student
puts "read #{arr_students[int_i]}"
end
file_name = "Graded"
file_io = open(file_name,"a+")
arr_students.each do |student|
set_TotalScore
set_Grade
file.io_write(student)
puts "write #{student}"
end
end
main if __FILE__==$0

Here is my run at it. I tried to stay true in general to the original intent of your code while introducing more Rubyish ways of doing things.
class Student
def initialize(firstname, lastname, *scores)
#firstname, #lastname, #scores = firstname, lastname, scores
end
def total_score
#scores.map(&:to_i).inject(:+)
end
def grade
raise "TOO HIGH!" if total_score > MAX_SCORE
case total_score / MAX_SCORE
when 0.9..1.0; "A"
when 0.8...0.9; "B"
when 0.7...0.8; "C"
when 0.6...0.7; "D"
else "F"
end
end
def to_s
"#{#lastname}, #{#firstname}: #{total_score}, #{grade}"
end
end
MAX_SCORE = 415.0
DATA.each_line do |line|
arr = line.split(",").map(&:strip)
student = Student.new *arr
puts student
end
__END__
Herb,Goldberg,22,99,44,22,88,88
Mark,Sullivan,77,88,88,44,33
You can read and write to files like this(not tested):
outfile = File.open("Graded", "a+")
File.open("Grades").each_line do |line|
...
outfile.puts student
end
outfile.close

We can not easily reproduce your code because you open a file called "Grades" and we do not have or know of its content.
You should also add some code to first check whether your file exists, before continuing - right now your script exits with a Errno::ENOENT.
I would also suggest putting the logic in main into your class instead - let your class handle everything.
In the part:
if __FILE__ == $PROGRAM_NAME
end
You can then simply initialize your class with a simple call such as:
Foobar.new(ARGV)
You described the "Grades" file but I did not understand what you wrote - it would be easier if you could link in to a sample, like via a pastie or gist, then link it in; and to also say what the part is that is not working, which is also unclear.
The style issues are secondary, I consider your code ok - the other poster here does not.

You should go through codecademy to get your ruby syntax down.
To access your initialized instance variables (#str_LastName (which should be #last_name), etc) you need to use "attr_reader :str_LastName", preferably at the top of the class. That'll definite you getter (setter is attr_writer, both is attr_accessor).
You can also do a sum on an array like this: [1,4,6,7].inject(:+).
Does Java not allow case statements? You should use that in set_grade. You also don't need to initialize str_Grade. In set grade, you could do #grade_letter ||= "A", and then calling set_grade will return that value on each call.
I didn't look through your main method. It's ugly though. Ruby methods probably shouldn't be more than 5 lines long.

Related

Having Trouble printing out .txt in ruby, ruby file handling

the problem is it cannot print all the text of the from a .txt file. I am able to print the first 3 lines of the txt file but not the rest. So far, I am getting an error which is in print_album': undefined local variable or methodtracks' for main:Object (NameError).
Here's the code:
*I know using global variable is no good in Ruby but this exercise ask me to do it.
module Genre
POP, CLASSIC, JAZZ, ROCK = *1..4
end
$genre_names = ['Null', 'Pop', 'Classic', 'Jazz', 'Rock']
class Album
# NB: you will need to add tracks to the following and the initialize()
attr_accessor :title, :artist, :genre, :tracks
# complete the missing code:
def initialize (atitle, aartist, agenre, arrtrk)
# insert lines here
#genre = agenre
#tracks = arrtrk
#title = atitle
#artist = aartist
end
end
class Track
attr_accessor :ttitle, :tlocation
def initialize (tname, tloc)
#ttitle = tname
#tlocation = tloc
end
end
# Reads in and returns a single track from the given file
def read_track music_file
mytrk_name = music_file.gets
mytrk_location = music_file.gets
mytrk = Track.new(mytrk_name, mytrk_location)
mytrk
end
# Returns an array of tracks read from the given file
def read_tracks music_file
count = music_file.gets().to_i
tracks = Array.new
$i = 0
# Put a loop here which increments an index to read the tracks
while $i < count do
track = read_track(music_file)
tracks << track
$i += 1
end
tracks
end
# Takes an array of tracks and prints them to the terminal
def print_tracks tracks
# print all the tracks use: tracks[x] to access each track.
$i = 0
while $i < tracks.length do
print_track(tracks[$i])
$i +=1
end
tracks
end
# Reads in and returns a single album from the given file, with all its tracks
def read_album music_file
# read in all the Album's fields/attributes including all the tracks
# complete the missing code
album_title = music_file.gets
album_artist = music_file.gets
album_genre = music_file.gets.to_i
tracks = read_tracks(music_file)
album = Album.new(album_title, album_artist, album_genre, tracks)
album
end
# Takes a single album and prints it to the terminal along with all its tracks
def print_album album
# print out all the albums fields/attributes
# Complete the missing code.
puts 'Album title is '+ album.title
puts 'Artist is ' + album.artist
puts 'Genre is ' + album.genre.to_s
puts $genre_names[album.genre]
# print out the tracks
print_tracks(tracks)
end
# Takes a single track and prints it to the terminal
def print_track track
puts('Track title is: ' + track.ttitle)
puts('Track file location is: ' + track.tlocation)
end
# Reads in an album from a file and then print the album to the terminal
def main
music_file = File.new("album.txt", "r")
album = read_album(music_file)
music_file.close()
print_album(album)
end
main
Here's is the album.txt
Greatest Hits
Neil Diamond
1
3
Crackling Rose
sounds/01-Cracklin-rose.wav
Soolaimon
sounds/06-Soolaimon.wav
Sweet Caroline
sounds/20-Sweet_Caroline.wav
Currently my output is :
Album title is Greatest Hits
Artist is Neil Diamond
Genre is 1
Pop
Expected output is :
Album title is Greatest Hits
Artist is Neil Diamond
Genre is 1
Pop
Track title is: Crackling Rose
Track file location is: sounds/01-Cracklin-rose.wav
Track title is: Soolaimon
Track file location is: sounds/06-Soolaimon.wav
Track title is: Sweet Caroline
Track file location is: sounds/20-Sweet_Caroline.wav
The problem is inside your def print_album album method. On the last line of the method it uses print_tracks(tracks), but tracks variable is undefined (that's exactly what error tells you).
You need to call print_tracks(album.tracks)

Iterate over array of objects. Then access object method if correct one is found. Otherwise create a new object in the array

I start with an empty array, and a Hash of key, values.
I would like to iterate over the Hash and compare it against the empty array. If the value for each k,v pair doesn't already exist in the array, I would like to create an object with that value and then access an object method to append the key to an array inside the object.
This is my code
class Test
def initialize(name)
#name = name
#values = []
end
attr_accessor :name
def values=(value)
#values << value
end
def add(value)
#values.push(value)
end
end
l = []
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z', 'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do |key, value|
l.any? do |a|
if a.name == value
a.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
end
p l
I would expect to see this:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
Instead I just get an empty array.
I think that the condition if a.name == value is not being met and then the add method isn't being called.
#Cyzanfar gave me a clue as to what to look for, and I found the answer here
https://stackoverflow.com/a/34904864/5006720
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
#ARL you're almost there! The last thing you need to consider is when found actually returns an object since detect will find a matching one at some point.
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
You actually only want to add a new instance of Test when found return nil. This code should yield your desired output:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
I observe two things in your code :
def values=(value)
#values << value
def add(value)
#values.push(value)
two methods do the same thing, pushing a value, as << is a kind of syntactic sugar meaning push
you have changed the meaning of values=, which is usually reserved for a setter method, equivalent to attire_writer :values.
Just to illustrate that there are many ways to do things in Ruby, I propose the following :
class Test
def initialize(name, value)
#name = name
#values = [value]
end
def add(value)
#values << value
end
end
h_cluster = {} # intermediate hash whose key is the cluster name
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z',
'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do | server, cluster |
puts "server=#{server}, cluster=#{cluster}"
cluster_found = h_cluster[cluster] # does the key exist ? => nil or Test
# instance with servers list
puts "cluster_found=#{cluster_found.inspect}"
if cluster_found
then # add server to existing cluster
cluster_found.add(server)
else # create a new cluster
h_cluster[cluster] = Test.new(cluster, server)
end
end
p h_cluster.collect { | cluster, servers | servers }
Execution :
$ ruby -w t.rb
server=server_1, cluster=cluster_x
cluster_found=nil
server=server_2, cluster=cluster_y
cluster_found=nil
server=server_3, cluster=cluster_z
cluster_found=nil
server=server_4, cluster=cluster_x
cluster_found=#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1]>
server=server_5, cluster=cluster_y
cluster_found=#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2]>
[#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1, :server_4]>,
#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2, :server_5]>,
#<Test:0x007fa7a619aac8 #name="cluster_z", #values=[:server_3]>]

Finding an element in an array inside a hash

#members = {
approved: ["Jill"],
unapproved: ["Daniel"],
removed: ["John"],
banned: ["Daniel", "Jane"]
}
Very simply: making a program to track membership. In the above hash you can see the four membership status keys each with an array containing names.
I'm trying to create a find_member method which allows the user to enter a name and then searches each array for the name and tells the user which key the name was found in.
I'm not very good with hashes and in attempting to do this I've created a mess of loops and I imagine there's a very easy solution, I just haven't found it so far. Is there a really simple way to do this?
I've tried a few things and don't have all my past efforts still, but this is the latest mess I've ended up with, which is probably worse than what I had previously:
def find_member
puts "==Find Member=="
puts "Name: "
#name = gets.chomp
#members.each do |key|
key.values.each do |array|
array.each do |element|
if #name == element
puts "#{#name} found in #{key}"
else
puts "#{#name} not found in #{key}"
end
end
end
end
end
Thanks.
The most efficient way to do this is to create a one-to-many mapping of names to keys, and update that mapping only when #members changes.
def find_member(name)
update_names_to_keys_if_necessary
#member_to_keys[name]
end
def update_names_to_keys_if_necessary
new_hashcode = #members.hash
return if #old_members.hashcode == new_hashcode
#member_to_keys = #members.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h|
v.each { |name| h[name] << k } }
#old_members_hashcode = new_hashcode
end
Note that #old_members_hashcode evaluates to nil the first time update_names_to_keys_if_necessary is called, so #member_to_keys will be created at that time.
Initially we obtain
#member_to_keys
#=> {"Jill"=>[:approved], "Daniel"=>[:unapproved, :banned],
# "John"=>[:removed], "Jane"=>[:banned]}
Try it.
find_member("Jill")
#=> [:approved]
find_member("Daniel")
#=> [:unapproved, :banned]
find_member("John")
#=> [:removed]
find_member("Jane")
#=> [:banned]
find_member("Billy-Bob")
#=> []
You can use this itteration with include? method.
#members = {
approved: ["Jill"],
unapproved: ["Daniel"],
removed: ["John"],
banned: ["Daniel", "Jane"]
}
def find_member_group(name)
#members.each { |group, names| return group if names.include?(name) }
nil
end
#name = 'Jane'
group_name = find_member_group(#name)
puts group_name ? "#{#name} found in #{group_name}." : "#{#name} not found."
# => Jane found in banned.
Hash#select is the method to use here:
def find_member(name)
#members.select {|k,v| v.include? name }.keys
end
find_member("Jill") #=> [:approved]
find_member("Daniel") #=> [:unapproved, :banned]
find_member("John") #=> [:removed]
find_member("Jane") #=> [:banned]
Explanation:
select as the name suggests selects and maps only those elements that satisfy the condition in the corresponding code-block. The code-block negates the need for an if statement. Within the code-block we check each key-value pair and if its value includes the name argument, then that key-value pair is selected and mapped to the final output. Finally we're only interested in the memberships (namely the keys), so we apply the keys method to get these in the form of an array.

rails 3.2 searching action. how to pass results to another action

I should create a search page in which i have to save in an Array all the results of the searching. I had two problems:
1) I used the following statement:
Company.joins(:references).where(sql_string)
that returns an ActiveRecord:Relation and it's not good for me cause i have to display these results in the index action , in which i use an each statement. So , to overcame this problem i used the to_a.
I checked the .class of my variable and with the to_a it passed from ActiveRecord:Relation to Array. So , it seems that this solve the problem.
Company.joins(:references).where(sql_string).to_a
2) Now, i have to pass this variable (Array) into my index action.
I executed the search in the action called search:
def search
...
#companies = Company.joins(:references).where(sql_string).to_a
end
Now, i want to pass this to index:
def index
#companies ||= Company.all
end
I used #companies ||= Company.all cause i think that the #companies is and istance variable and it should be available in all the actions of the class. Isn't it? By the way, it doesn't workl. I mean , the results are not shared through the two methods.
Also , in the search action i don't know how to call index action. I used the redirect_to but this bring me to another problem.
def search
...
#companies = Company.joins(:references).where(sql_string).to_a
redirect_to companies_index_path
end
The second time i call the search action it brings me into the index action.As i insered the searching value. At really he still had the past searching in memory, and i don't want this behavior.
So , in other words, i want to:
passing #companies searching result to index action.
avoid the loop between search-index. So in every new request resets
the old searching.
i want to know if it's correct the casting with the to_a to bring
an ActiveRecord:Relation to Array.
Thank You.
EDIT:
def search
stringa_sql = ""
ragione_sociale = ""
riferimento = ""
note = ""
min_date = ""
max_date = ""
company_type = ""
sector = ""
country = ""
certification = ""
contact = ""
state = ""
manage = ""
consultation = ""
formation = ""
software = ""
if params[:ragione_sociale]
ragione_sociale = params[:ragione_sociale]
stringa_sql = "ragione_sociale like "+"'%"+ragione_sociale+"%'"
end
if params[:riferimento]
riferimento = params[:riferimento]
stringa_sql += " AND nome like "+"'%"+riferimento+"%'"
end
if params[:note]
note = params[:note]
stringa_sql += " AND note like "+"'%"+note+"%'"
end
if params[:min_date] && params[:min_date]!= ""
if params[:max_date] && params[:max_date]!= ""
min_date = params[:min_date]
max_date = params[:max_date]
stringa_sql += " AND richiamare >="+min_date+" AND richiamare <="+max_date
end
end
if params[:company_type] #se inviamo la richesta senza scrivere params[:category] viene passato vuoto
if params[:company_type][:id] != ""
company_type = params[:company_type][:id]
stringa_sql += " AND id_forma_giuridica = "+company_type
end
end
if params[:sector]
if params[:sector][:id] != ""
sector = params[:sector][:id]
stringa_sql += " AND id_settore = "+sector
end
end
if params[:country]
if params[:country][:id] != ""
country = params[:country][:id]
stringa_sql += " AND id_provincia = "+country
end
end
if params[:certification]
if params[:certification][:id] != ""
certification = params[:certification][:id]
stringa_sql += " AND id_certificazione = "+certification
end
end
if params[:contact]
if params[:contact][:id] != ""
contact = params[:contact][:id]
stringa_sql += " AND id_contattato = "+contact
end
end
if params[:state]
if params[:state][:id] != ""
state = params[:state][:id]
stringa_sql += " AND id_stato = "+state
end
end
if params[:manage]
if params[:manage][:id] != ""
manage = params[:manage][:id]
stringa_sql += " AND id_gestito = "+manage
end
end
if params[:consultation]
if params[:consultation][:id] != ""
consultation = params[:consultation][:id]
stringa_sql += " AND id_consulenza = "+consultation
end
end
if params[:formation]
if params[:formation][:id] != ""
formation = params[:formation][:id]
#formazione DA METTERE
end
end
if params[:software]
if params[:software][:id] != ""
software = params[:software][:id]
stringa_sql += " AND id_software = "+software
end
end
#companies = Company.search(stringa_sql).to_a
if not #companies.empty?
redirect_to companies_index_path
end
end
index:
def index
#companies ||= Company.all
end
I used #companies ||= Company.all cause i think that the #companies
is and istance variable and it should be available in all the actions
of the class. Isn't it?
Not really, it depends on from where you want to access the #companies instance variable. e.g. from which view, you need #companies instance variable in the corresponding action method of the controller.
The second time i call the search action it brings me into the index
action
You are using redirect_to companies_index_path in your search method which brings you to the index action.
To implement search in your application, you can follow this somewhat standard process:
In your application_controller.rb which will have the #search_query.
# Returns string
def search_query
#search_query ||= params[:query] || params[:search]
end
Then, in your searches_controller.rb, you can have:
def search
# in the method build your search results based on
# search_query param
#search_results = Company.joins(:references).where(sql_string(search_query)).to_a
end
In your routes.rb, you can have:
get '/search(/:query)' => 'searches#search', query: /.+/, as: 'search'
Which will take you to the searches_controller's search action where you are building the search results #search_results.
Finally, you need to have a app/views/searches/search.html.erb view file where you have access to your #search_results instance variable and you can just loop through them and display them in this view.
Answers to your last 3 questions:
passing #companies searching result to index action.
avoid the loop between search-index. So in every new request resets
the old searching.
You can overcome these problems by following the request/response flow that I have mentioned above. You should not share your index view with your search and you should not have any loop between search and index. Both of them are separate actions of the controller and can be handled separately as I showed above.
i want to know if it's correct the casting with the to_a to bring an
ActiveRecord:Relation to Array.
You can do that if you want. But, you don't really need it in this use case. You can store the ActiveRecord:Relation in your search_results and when you access this instance variable from inside your search.html.erb view, you can easily loop through using a .each do block. So, you don't have to worry about ActiveRecord:Relation and Array.

How do I check for pangrams in a line in ruby?

Some of you may notice I'm already back with the same painful code already. I'm not sure if the other question is still open or not once I accept an answer.
Now the problem is a little simpler. I found some code that checked for pangrams. It use to be def pangram?('sentence') but I needed line to go in there so I tried changing it to def pangram?(line). It doesn't seem to mesh well with my coding style and doesn't work. I tried to use .contain('a' . . 'z') to check for a pangram but someone I know tried that and it didn't work. Also google isn't much help either.
Any ideas for how I could check for pangrams in an if stmt?
# To change this template, choose Tools | Templates
# and open the template in the editor
# This program reads a file line by line,
#separating lines by writing into certain text files.
#PPQ - Pangrams, Palindromes, and Quotes
class PPQ
def pangram?(line)
unused_letters = ('a'..'z').to_a - line.downcase.chars.to_a
unused_letters.empty?
end
def categorize
file_pangram = File.new('pangram.txt', 'w')
file_palindrome = File.new('palindrome.txt', 'w')
file_quotes = File.new('quotes.txt','w')
File.open('ruby1.txt','r') do |file|
while line = file.gets
if(line.reverse == line)
file_palindrome.write line
elsif(pangram?(line)== true)
file_pangram.write line
else
file_quotes.write line
end
end
end
file.close
file_pangram.close
file_palindrome.close
file_quotes.close
end
end
my_ruby_assignment = PPQ.new
my_ruby_assignment.categorize
I'm partial to simpler syntax, something like
def pangram?(line)
('a'..'z').all? { |word| line.downcase.include? (word) }
end
if pangram?(line) then file_pangram.write line end
def pangram?(string)
str = string.chars.map(&:downcase)
letters =('a'..'z').to_a
result = true
letters.each do |l|
if !(str.include? l.downcase)
result = false
break
end
end
result
end

Resources