How to read/write a hash from a file in ruby? [closed] - ruby

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I want to store data of countries in a file and read that data. I tried to write it in filename.yml file but I am not able to get each hash separately to perform specific operation. Have a look what I tried
require 'yaml'
def addData
puts 'Enter country name :'
name = gets.chop.to_s
puts 'Enter population in billion :'
population = gets.chop.to_s
puts 'Enter GDP in lakh crore USD:'
gdp = gets.chop.to_s
puts 'Enter army strength:'
army_strength = gets.chop.to_s
puts 'Enter state of country:'
state = gets.chop.to_s
country = Hash.new
country = { "name" => name , "population" => population , "gdp" => gdp , "army_strength" => army_strength , "state" => state }
arr_country.push(country)
# Writing to the file
File.open('filename.yml','a') { |f| YAML.dump(country, f) }
end
def readData
File.open('filename.yml') {
|f|.each {
arr = YAML.load(f)
if arr
puts arr
puts arr.class
end
}
}
end
while true
puts '1. Write','2. Read','3.Exit'
opt = gets.chop.to_i
if (opt>2)
return
end
case opt
when 1
addData
when 2
readData
end
end
But it's not working, what would be the best way ?

Apart from the minor mistakes, that are mentioned above, you might have most success using the load_stream function from YAML, rather than load
def readData
File.open('filename.yml') do |f|
arr = YAML.load_stream(f)
if arr
puts arr
puts arr.class
end
end
end
While this is not how I would do it, it will allow you to append to the file, from any source, as long as it remains compatible with YAML.
My preference would be to use a database.
the resulting full code, on my system (run through rubocop) is:
require 'yaml'
def add_data
puts 'Enter country name :'
name = gets.chop.to_s
puts 'Enter population in billion :'
population = gets.chop.to_s
puts 'Enter GDP in lakh crore USD:'
gdp = gets.chop.to_s
puts 'Enter army strength:'
army_strength = gets.chop.to_s
puts 'Enter state of country:'
state = gets.chop.to_s
country = {}
country = { 'name' => name, 'population' => population, 'gdp' => gdp, 'army_strength' => army_strength, 'state' => state }
# Writing to the file
File.open('filename.yml', 'a') { |f| YAML.dump(country, f) }
end
def read_data
arr = []
File.open('filename.yml') do |f|
arr = YAML.load_stream(f)
end
return unless arr
puts arr
puts arr.class
end
loop do
puts '1. Write', '2. Read', '3.Exit'
opt = gets.chop.to_i
break if opt > 2
case opt
when 1
add_data
when 2
read_data
end
end

The f.each does not make sense. f is the file object, and each processes each line and tries to interpret each line as a separate YAML expression. However, your whole file consists of a single YAML expression.
You could do something like:
country = YAML.load(File.read('filename.yaml'))

Related

Find the name and age of the oldest person in a txt file using ruby

"Attached is a file with people's names and ages.
There will always be a First name and Last name followed by a colon then the age.
So each line with look something like this.
FirstName LastName: Age
Your job is write a ruby program that can read this file and figure out who the oldest person/people are on this list. Your program should print out their name(s) and age(s)."
This is the code I have so far:
File.open('nameage.txt') do |f|
f.each_line do |line|
line.split(":").last.to_i
puts line.split(":").last.to_i
end
end
With this, I am able to separate the name from the age but I don't know how to get the highest value and print out the highest value with name and age.
Please help!
"figure out who the oldest person/people are on this list", so multiple results are possible. Ruby has a group_by method, which groups an enumerable by a common property. What property? The property you specify in the block.
grouped = File.open('nameage.txt') do |f|
f.group_by do |line|
line.split(":").last.to_i # using OP's line
end
end
p grouped # just to see what it looks like
puts grouped.max.last # end result
You could push all the ages into an array. Do array.max or sort the array and do array[-1].
Here's how I would approach it:
oldest_name = nil
oldest_age = 0
For each line in file do
split line at the colon and store the age inside age variable
split line at the colon and store the age inside name variable
if age is greater than oldest_age then
oldest_age = age
oldest_name = name
end
end
finally print the oldest_name and oldest_age
If you're in to one-liners try this
$ cat nameage.txt
John Doe: 34
Tom Jones: 50
Jane Doe: 32
Citizen Kane: 29
$ irb
1.9.3-p551 :001 > IO.read("nameage.txt").split("\n").sort_by { |a| a.split(":")[1].to_i }.last
=> "Tom Jones: 50"
You can try using hash also,
hash = {}
File.open('nameage.txt') do |f|
f.each_line do |line|
data = line.split(":")
hash[data.first] = data.last.strip
end
hash.max_by{|k,v| v}.join(" : ")
end
File.open('nameage.txt') do |handle|
people = handle.each_line.map { |line| line.split(":") }
oldest_age = people.map { |_, age| age.to_i }.max
people.select { |_, age| age.to_i == oldest_age }.each do |name, age|
puts "#{name}, #{age}"
end
end
You are going the right way. Now you just need to store the right things in the right places. I just merged your code and the code proposed by #oystersauce14.
oldest_name = nil
oldest_age = 0
File.open('nameage.txt') do |f|
f.each_line do |line|
data = line.split(":")
curr_name = data[0]
curr_age = data[1].strip.to_i
if (curr_age > oldest_age) then
oldest_name = curr_name
oldest_age = curr_age
end
end
end
puts "The oldest person is #{oldest_name} and he/she is #{oldest_age} years old."
Notice the use of String#strip when acquiring the age. According to the format of the file, this piece of data (the age) has a space before the first number and you need to strip this before converting it using String#to_i.
EDIT:
Since you may have more than one person with the maximum age in the list, you may do it in two passes:
oldest_age = 0
File.open('nameage.txt') do |f|
f.each_line do |line|
curr_age = line.split(":")[1].strip.to_i
if (curr_age > oldest_age) then
oldest_age = curr_age
end
end
end
oldest_people = Array.new
File.open('nameage.txt') do |f|
f.each_line do |line|
data = line.split(":")
curr_name = data[0]
curr_age = data[1].strip.to_i
oldest_people.push(curr_name) if (curr_age == oldest_age)
end
end
oldest_people.each { |person| p "#{person} is #{oldest_age}" }
I believe that now this will give you what you need.

How to use Hash values in ruby

I wrote a small Ruby program, but can't access the hash value stored in the parent class.
Here is the code:
class College
##dep = ["cs" => 60000, "mat" => 20000, "che" => 30000]
end
class Student < College
def get_det
puts "Enter name... \n"
#name = gets
puts "Enter department...\n"
#dpt = gets
end
def set_fee
case #dpt
when "cs"
#fee = (##dep["cs"]).to_i
when "mat"
#fee = ##dep["mat"].to_i
when "che"
#fee = ##dep["che"].to_i
else
puts "Eror!!!"
end
end
def print_det
puts "Name : #{#name}"
puts "Department : #{#dpt}"
puts "Course fee : #{#fee}"
end
end
det = Student.new
det.get_det
det.set_fee
det.print_det
I got the output as:
Output:
You've defined your ##dep variable as an array, not as a hash. You need to replace [ ] with { }, like so:
##dep = {"cs" => 60000, "mat" => 20000, "che" => 30000}
Then you'll be able to access your hash values via the string keys:
##dep['cs'] # Will return 6000
And just an FYI, your set_fee method could be refactored to just be this:
def set_fee
#fee = ##dep[#dpt] || 'Error!'
puts #fee
end
Since you're simply passing in the value you're checking against for each of your when statements, you can just pass the value directly to your ##dep object. And you don't need to_i, because the values in your hash are already integers.

How to count how many line are between a specific part of a file?

So, I'm trying to parse a Cucumber file (*.feature), in order to identify how many lines each Scenario has.
Example of file:
Scenario: Add two numbers
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 120 on the screen
Scenario: Add many numbers
Given I have entered 50 into the calculator
And I have entered 20 into the calculator
And I have entered 20 into the calculator
And I have entered 30 into the calculator
When I press add
Then the result should be 120 on the screen
So, I'm expecting to parse this file and get results like:
Scenario: Add two numbers ---> it has 4 lines!
Scenario: Add many numbers ---> it has 6 lines!
What's the best approach to do that?
Enumerable#slice_before is pretty much tailor-made for this.
File.open('your cuke scenario') do |f|
f.slice_before(/^\s*Scenario:/) do |scenario|
title = scenario.shift.chomp
ct = scenario.map(&:strip).reject(&:empty?).size
puts "#{title} --> has #{ct} lines"
end
end
Why don't you start simple? Like #FeRtoll suggested, going line by line might be the easiest solution. Something as simple as the following might be what you are looking for :
scenario = nil
scenarios = Hash.new{ |h,k| h[k] = 0 }
File.open("file_or_argv[0]_or_whatever.features").each do |line|
next if line.strip.empty?
if line[/^Scenario/]
scenario = line
else
scenarios[scenario] += 1
end
end
p scenarios
Output :
{"Scenario: Add two numbers \n"=>4, "Scenario: Add many numbers\n"=>6}
This is the current piece of code I'm working on (based on Kyle Burton approach):
def get_scenarios_info
#scenarios_info = [:scenario_name => "", :quantity_of_steps => []]
#all_files.each do |file|
line_counter = 0
File.open(file).each_line do |line|
line.chomp!
next if line.empty?
line_counter = line_counter + 1
if line.include? "Scenario:"
#scenarios_info << {:scenario_name => line, :scenario_line => line_counter, :feature_file => file, :quantity_of_steps => []}
next
end
#scenarios_info.last[:quantity_of_steps] << line
end
end
#TODO: fix me here!
#scenarios_info.each do |scenario|
if scenario[:scenario_name] == ""
#scenarios_info.delete(scenario)
end
scenario[:quantity_of_steps] = scenario[:quantity_of_steps].size
end
puts #scenarios_info
end
FeRtoll suggested a good approach: accumulating by section. The simplest way to parse it for me was to scrub out parts that I can ignore (i.e. comments) and then split into sections:
file = ARGV[0] or raise "Please supply a file name to parse"
def preprocess file
data = File.read(file)
data.gsub! /#.+$/, '' # strip (ignore) comments
data.gsub! /#.+$/, '' # strip (ignore) tags
data.gsub! /[ \t]+$/, '' # trim trailing whitespace
data.gsub! /^[ \t]+/, '' # trim leading whitespace
data.split /\n\n+/ # multiple blanks separate sections
end
sections = {
:scenarios => [],
:background => nil,
:feature => nil,
:examples => nil
}
parts = preprocess file
parts.each do |part|
first_line, *lines = part.split /\n/
if first_line.include? "Scenario:"
sections[:scenarios] << {
:name => first_line.strip,
:lines => lines
}
end
if first_line.include? "Feature:"
sections[:feature] = {
:name => first_line.strip,
:lines => lines
}
end
if first_line.include? "Background:"
sections[:background] = {
:name => first_line.strip,
:lines => lines
}
end
if first_line.include? "Examples:"
sections[:examples] = {
:name => first_line.strip,
:lines => lines
}
end
end
if sections[:feature]
puts "Feature has #{sections[:feature][:lines].size} lines."
end
if sections[:background]
puts "Background has #{sections[:background][:lines].size} steps."
end
puts "There are #{sections[:scenarios].size} scenarios:"
sections[:scenarios].each do |scenario|
puts " #{scenario[:name]} has #{scenario[:lines].size} steps"
end
if sections[:examples]
puts "Examples has #{sections[:examples][:lines].size} lines."
end
HTH

*RUBY* Extreme beginner stuck [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I can't figure out the guidance for this text-based RPG. I want the player's input to choose between one of four classes, then save that class and assign stats to it. For now, it only works if I choose "Warrior". What am I doing wrong?
stats = Hash.new
stats["Strength"] = 10
stats["Dexterity"] = 10
stats["Charisma"] = 10
stats["Stamina"] = 10
puts "Hello, brave adventurer. What is your name?"
player_name = gets.chomp.capitalize
puts "Well, #{player_name}, you are certainly brave! Choose your profession. (Choose from Warrior, Wizard, Archer, or Thief)."
player_class = gets.chomp.downcase
while player_class != ("warrior" || "thief" || "archer" || "wizard")
puts "I do not recognize #{player_class} as a valid class. Please choose between Warrior, Wizard, Archer, or Thief."
player_class = gets.chomp.downcase
end
if player_class == "warrior"
puts "Yay a warrior!"
stats["Strength"] = 20
elsif player_class == "thief"
puts "yay a thief!"
stats["Dexterity"] = 20
elsif player_class == "archer"
puts "yay an archer!"
elsif player_class == "wizard"
puts "Sweet a wizard!"
end
It's very simple.
1.9.3p194 :001 > ("warrior" || "thief" || "archer" || "wizard")
=> "warrior"
The logical OR of several strings evaluates to the first one.
You could replace that line by something like:
while player_class != "warrior" and player_class != "thief" and player_class != "archer" and player_class != "wizard"
Try setting up the classes as an array...
player_classes = ["warrior", "thief", "archer", "wizard"]
And then when you want to check if the player has entered a valid class...
while ! player_classes.include? player_class
instead.
You can use an even nicer idiom for single words...
%w(warrior thief archer wizard)
Generates
["warrior", "thief", "archer", "wizard"]
Moving forward
You could take this approach a step forward by putting the player classes into a hash.
For example:
player_classes = {
'warrior' => {:message => "Yay a warrior!", :stats => {:strength => 20} },
'thief' => {:message => "Ooh a thief!", :stats => {:dexterity => 20} },
'archer' => {:message => "Cool! an archer" },
'wizard' => {:message => "Sweet! a wizard" }
}
You can then do things like this:
while ! player_classes.key? player_class
Once you've got a match you can then pull the values out of the hash, like this:
selected_class = player_classes[player_class]
stats.merge selected_class[:stats] if selected_class[:stats]
If there's no stats in the hash for that player class nothing will happen, if there is, it'll be merged in.
e.g. to test this...
selected_class = player_classes['warrior']
stats.merge selected_class[:stats] if selected_class[:stats]
# stats is now {:strength=>20, :dexterity=>10, :charisma=>10, :stamina=>10}
selected_class = player_classes['wizard']
stats.merge selected_class[:stats] if selected_class[:stats]
# stats is now {:strength=>10, :dexterity=>10, :charisma=>10, :stamina=>10}
We can then show the message with:
puts player_classes[player_class][:message]
This would reduce your logic down to capturing the player class input and then processing the hash.
Revisting your original code
Using a hash to act as a simple data-model.
You'd end up with code like this:
#!/usr/bin/env ruby
stats = { :strength => 10, :dexterity => 10, :charisma => 10, :stamina => 10 }
player_classes = {
'warrior' => {:message => "Yay a warrior!", :stats => {:strength => 20} },
'thief' => {:message => "Ooh a thief!", :stats => {:dexterity => 20} },
'archer' => {:message => "Cool! an archer" },
'wizard' => {:message => "Sweet! a wizard" }
}
puts "Welcome brave adventurer, what is your name?"
player_name = gets.chomp.capitalize
puts "Well, #{player_name}, you are certainly brave! Choose your profession. (Choose from Warrior, Wizard, Archer, or Thief)."
player_class = gets.chomp.downcase
while ! player_classes.key? player_class
puts "I do not recognize #{player_class} as a valid class. Please choose between Warrior, Wizard, Archer, or Thief."
player_class = gets.chomp.downcase
end
selected_class = player_classes[player_class]
stats.merge selected_class[:stats] if selected_class[:stats]
puts selected_class[:message]
You should also find this more readable, however, as you extend your game, you'll find that you can't easily work with code like this. You should next learn about using functions to break up your code into different routines. There are also more things you can do with arrays, hashes and collections.
Also, as soon as possible, you should start learning about programming Ruby in an Object Oriented style, which is how it should be used, ideally.
Tutorials Point is a pretty decent site for learning more about Ruby
For what you did wrong, see Diego's answer.
This is a typical case where you should use the case statement. You can cut out the routine to get the player class like this:
public
def get_player_class
case player_class = gets.chomp.downcase
when "warrior" then puts "Yay a warrior!"; stats["Strength"] = 20
when "thief" then puts "yay a thief!"; stats["Dexterity"] = 20
when "archer" then puts "yay an archer!"; true
when "wizard" then puts "Sweet a wizard!"; true
else puts "I do not recognize #{player_class} as a valid class. "\
"Please choose between Warrior, Wizard, Archer, or Thief."; false
end
end
stats = {"Strength" => 10, "Dexterity" => 10, "Charisma" => 10, "Stamina" => 10,}
puts "Hello, brave adventurer. What is your name?"
player_name = gets.chomp.capitalize
puts "Well, #{player_name}, you are certainly brave! "\
"Choose your profession. (Choose from Warrior, Wizard, Archer, or Thief)."
nil until get_player_class

Ruby 1.9: turn these 4 arrays into hash of key/value pairs

I have four arrays that are coming in from the client. Let's say that there is an array of names, birth dates, favorite color and location. The idea is I want a hash later where each name will have a hash with respective attributes:
Example date coming from the client:
[name0, name1, name2, name3]
[loc0, loc1]
[favcololor0, favcolor1]
[bd0, bd1, bd2, bd3, bd4, bd5]
Output I'd like to achieve:
name0 => { location => loc0, favcolor => favcolor0, bd => bd0 }
name1 => { location => loc1, favcolor => favcolor1, bd => bd1 }
name2 => { location => nil, favcolor => nil, bd => bd2 }
name3 => { location => nil, favcolor => nil, bd => bd3 }
I want to have an array at the end of the day where I can iterate and work on each particular person hash.
There need not be an equivalent number of values in each array. Meaning, names are required.. and I might receive 5 of them, but I only might receive 3 birth dates, 2 favorite colors and 1 location. Every missing value will result in a nil.
How does one make that kind of data structure with Ruby 1.9?
I would probably do it like this
# assuming names, fav_colors, birth_dates, and locations are your arrays
name_collection = {}
names.zip(birth_dates, fav_colors, locations) do |name, birth_date, fav_color, location|
name_collection[name] = { :birth_date => birth_date,
:fav_color => fav_color,
:location => location }
end
# usage
puts name_collection['jack'][:fav_color] # => 'blue'
A small class to represent a person
class Person
attr_accessor :name, :color, :loc, :bd
def initialize(args = {})
#name = args[:name]
#color = args[:color]
#loc = args[:loc]
#bd = args[:bd]
end
def pp()
puts "*********"
puts "Name: #{#name}"
puts "Location: #{#loc}"
puts "Birthday: #{#bd}"
puts "Fav. Color: #{#color}"
puts "*********"
end
end
another to represent people, which is mainly just a listing of Persons.
class People
attr_accessor :list_of_people
def initialize()
#list_of_people = {}
end
def load_people(names, locations, favcolors, birthdates)
names.each_with_index do |name, index|
#list_of_people[name] = Person.new(:name => name, :color => favcolors[index], :loc => locations[index], :bd => birthdates[index])
end
end
def pp()
#list_of_people.each_pair do |key, value|
value.pp()
end
end
end
I threw in a pretty print function for each so you can see their data. With a starting point like this it will be really easy to modify and add methods that do all sorts of useful things.
if __FILE__ == $0
names = ["name0", "name1", "name2", "name3"]
locs = ["loc0","loc1"]
favcolors = ["favcolor0", "favcolor1"]
bds = ["bd0","bd1","bd2","bd3","bd4"]
a = People.new()
a.load_people(names,locs,favcolors,bds)
a.pp()
end
I think the kind of data structure you're looking for is -ahem- a Struct.
# setup data
names = %w(name0 name1 name2 name3)
locations = %w(loc0 loc1)
colors = %w(favcololor0 favcolor1)
bd = %w(bd0 bd1 bd2 bd3 bd4 bd5)
# let's go
Person = Struct.new( :name, :location, :fav_color, :bd )
all_persons = names.zip( locations, colors, bd ).map{|p| Person.new( *p)}
# done
puts all_persons
someone= all_persons.find{|p| p.name == "name1"}
puts someone.location unless someone.nil?

Resources