How to append a hash to an array? - ruby

lines = ["title= flippers dippers track= 9", "title= beaner bounce house track= 3", "title= fruit jams live track= 12"]
songs_formatted = []
songs = {}
lines.each do |line|
line =~ /title=\s?(.*)\s+t/
title = "#$1".strip
songs[:title] = title
line =~ /track=\s?(.*)/
track = "#$1".strip
songs[:track] = track
songs_formatted << songs
end
p songs_formatted
#=> [{:title=>"flippers dippers", :track=>"9"}]
#=> [{:title=>"beaner bounce house", :track=>"3"}, {:title=>"beaner bounce house", :track=>"3"}]
#=> [{:title=>"fruit jams live", :track=>"12"}, {:title=>"fruit jams live", :track=>"12"}, {:title=>"fruit jams live", :track=>"12"}]
Each successive line is overwriting the line before it. Why isn't this just appending in order? Desired result is:
songs_formatted = [{:title=>"flippers dippers", :track=>"9"}, {:title=>"beaner bounce house", :track=>"3"}, {:title=>"fruit jams live", :track=>"12"}]

Need to place the songs hash inside of the each loop. Working code:
lines = ["title= flippers dippers track= 9", "title= beaner bounce house track= 3", "title= fruit jams live track= 12"]
songs_formatted = []
lines.each do |line|
songs = {}
line =~ /title=\s?(.*)\s+t/
title = "#$1".strip
songs[:title] = title
line =~ /track=\s?(.*)/
track = "#$1".strip
songs[:track] = track
songs_formatted << songs
end
p songs_formatted
Proper output:
#=> [{:title=>"flippers dippers", :track=>"9"}, {:title=>"beaner bounce house", :track=>"3"}, {:title=>"fruit jams live", :track=>"12"}]

Since you want one output per line, you can use map. Also, you can extract both with one regex.
lines.map do |line|
title, track = line.match(/title=\s?(.*?)\s*track=\s?(\d+)/)[1,2]
{title: title, track: track}
end
This gives you the output you want.

Related

How to run a loop on an array, combine the results and set them to a variable

I have this array: ['John', 'Michael', 'Siri']. How can I run an each loop on them and add a text to each "part" of the array and at the end "combine" the result of the loops and set/assign the result of all each to a variable?
By that I mean, I do:
array = ['John', 'Michael', 'Siri']
array.each do |a|
text = "#{a} here"
# Results would need be =>
# John is here
# Michael is here
# Siri is here
#new_string = text # => Which would need to be "John is here Michael is here Siri is here"
end
I have done the code above, but #new_string becomes only Siri is here and if I move the #string out of the loop, like below, it becomes John is here, so basically it takes only one of them and "assigns" it to #new_string.
array = ['John', 'Michael', 'Siri']
array.each do |a|
#text = "#{a} here"
end
#new_string = #text
I tested with [0]+[1]+[2] and it kind of worked, but the problem is that I would not know the size of my array. It can be 2 items or it can be 100 items.
This way
array = ["John", "Michael", "Siri"]
your_variable = array.map { |name| "#{name} is here" }.join(" ")
It's basically a transformation, you want to add something to each element of a collection (use map for that). Lastly, join them up.
Can be done by
array.map { |x| x + ' is here' }.join(' ')
More concisely:
%w{ John Michael Siri}.collect{|s| s+" is here"}.join(" ")
Given the array = ['John', 'Michael', 'Siri'], the problem with your code is that the variable has a scope inside of the loop so it is not accessible after the loop ends.
The solution is to declare the variable before.
#new_string = '' # initialize outside the loop
array.each do |a|
text = "#{a} here "
#new_string += text # note +=
end
#new_string #=> "John here Michael here Siri here "
For the second code, the problem is the same:
#new_string = '' # initialize outside the loop
array = ['John', 'Michael', 'Siri']
array.each do |a|
#new_string += "#{a} here " # note +=
end
#new_string #=> "John here Michael here Siri here "
As you can see string ends with a space, to avoid it populate an array then join as showed in previous answers:
#new_string = [] # initialize outside the loop
array = ['John', 'Michael', 'Siri']
array.each do |a|
#new_string << "#{a} here" # note +=
end
p #new_string = #new_string.join(' ') #=> "John here Michael here Siri here"
Side note:
# comments in ruby starts with #

How do I convert this from a while loop into a for loop?

How would I make this while loop into a for loop?
ten_things = "Apples Oranges Crows Telephone Light Sugar"
stuff = ten_things.split(' ')
puts "Wait there are not 10 things in that list. Let's fix that."
more_stuff = ["Day", "Night", "Song", "Frisbee", "Corn", "Banana", "Girl", "Boy"]
while stuff.length != 10
next_one = more_stuff.pop
puts "Adding: #{next_one}"
stuff.push(next_one)
puts "There are #{stuff.length} items now."
end
I tried and got an infinite loop
I don't see why you would bother making it a for loop when a while or until loop is meant for it.
However, this will work:
ten_things = "Apples Oranges Crows Telephone Light Sugar"
puts "Wait there are not 10 things in that list. Let's fix that."
stuff = ten_things.split(' ')
more_stuff = ["Day", "Night", "Song", "Frisbee", "Corn", "Banana", "Girl", "Boy"]
# using math to make sure there's 10 items
i = stuff.length
for i in i...10
next_one = more_stuff.pop
puts "Adding: #{next_one}"
stuff.push(next_one)
puts "There are #{stuff.length} items now."
end
You can use
array_name.each { |item|
break if stuff.length > 10
stuff.push(item)
}

Split string into chunks (of different size) without breaking words

I am trying to create a method that, given a string, returns three strings: title, description1, description2
This is a related question I found: Split a string into chunks of specified size without breaking words - But my chunks are of different size.
Title needs to be max 25 characters.
Description1 needs to be max 35 characters.
Description2 needs to be max 35 characters.
The question would be:
How can I split a string so that it creates a maximum of three entities (NOTE: If the string can fit in just the first entity that is OK, I don't need to return the three entities), where first entity has a maximum of 25 characters and the other two have a max of 35 characters each. Making the method clever enough to take into account words (and maybe punctuation), so that it doesn't return cut results.
I have done the following:
def split_text_to_entities(big_string)
title = big_string(0..24)
description1 = big_string(25..59)
description2 = big_string(60..94)
end
But the problem with this approach is that if that if the input is "Buy our new brand shoes from our store. Best discounts in town and 40% off for first purchase.", the results would be:
title = "Buy our new brand shoes f"
description1 = "rom our store. Best discounts in to"
description2 = "wn and 40% off for first purchase."
And ideally they would be:
title = "Buy our new brand shoes"
description1 = "from our store. Best discounts in"
description2 = "town and 40% off for first"
So, try to split by character size, taking into account the words.
To cover all the bases, I would do the following.
Code
def divide_text(str, max_chars)
max_chars.map do |n|
str.lstrip!
s = str[/^.{,#{n}}(?=\b)/] || ''
str = str[s.size..-1]
s
end
end
(?=\b) is a (zero-width) positive lookahead that matches a word break.
Examples
max_nbr_chars = [25,35,35]
str = "Buy our new brand shoes from our store. Best discounts in " +
"town and 40% off for first purchase."
divide_text(str, max_nbr_chars)
#=> ["Buy our new brand shoes",
# "from our store. Best discounts in",
# "town and 40% off for first"]
str = "Buy our new brand shoes from our store."
divide_text(str, max_nbr_chars)
#=> ["Buy our new brand shoes", "from our store.", ""]
str = "Buy our new"
divide_text(str, max_nbr_chars)
#=> ["Buy our new", "", ""]
str = ""
divide_text(str, max_nbr_chars)
#=> ["", "", ""]
str = "Buyournewbrandshoesfromourstore."
divide_text(str, max_nbr_chars)
#=> ["", "Buyournewbrandshoesfromourstore.", ""]
str = "Buyournewbrandshoesfromourstoreandshoesfromourstore."
divide_text(str, max_nbr_chars)
#=> ["", "", ""]
Note that if ^ were omitted from the regex:
str = "Buyournewbrandshoesfromourstore."
divide_text(str, max_nbr_chars)
#=> ["ewbrandshoesfromourstore.", "rstore.", ""]
This doesn't do the trick?:
def get_chunks(str, n = 3)
str.scan(/^.{1,25}\b|.{1,35}\b/).first(n).map(&:strip)
end
s = "Buy our new brand shoes from our store. Best discounts in town and 40% off for first purchase."
s =~ /\b(.{,25})\W+(.{,35})\W+(.{,35})\b/
[$1, $2, $3] # =>
# [
# "Buy our new brand shoes",
# "from our store. Best discounts in",
# "town and 40% off for first purchase"
# ]

ruby searching array for keywords

I am parsing a large CSV file in a ruby script and need to find the closest match for a title from some search keys. The search keys maybe one or more values and the values may not exactly match as per below (should be close)
search_keys = ["big", "bear"]
A large array containing data that I need to search through, only want to search on the title column:
array = [
["id", "title", "code", "description"],
["1", "once upon a time", "3241", "a classic story"],
["2", "a big bad wolf", "4235", "a little scary"],
["3", "three big bears", "2626", "a heart warmer"]
]
In this case I would want it to return the row ["3", "three big bears", "2626", "a heart warmer"] as this is the closest match to my search keys.
I want it to return the closest match from the search keys given.
Is there any helpers/libraries/gems I can use? Anyone done this before??
I am worried, this task should be handled to any search engine at db level or similar, no point fetching data in app and do searching across columns/rows etc, should be expensive. but for now here is the plain simple approach :)
array = [
["id", "title", "code", "description"],
["1", "once upon a time", "3241", "a classic story"],
["2", "a big bad wolf", "4235", "a little scary"],
["3", "three big bears", "2626", "a heart warmer"]
]
h = {}
search_keys = ["big", "bear"]
array[1..-1].each do |rec|
rec_id = rec[0].to_i
search_keys.each do |key|
if rec[1].include? key
h[rec_id] = h[rec_id] ? (h[rec_id]+1) : 1
end
end
end
closest = h.keys.first
h.each do |rec, count|
closest = rec if h[closest] < h[rec]
end
array[closest] # => desired output :)
I think you can do it by your self and no need to use any gems!
This may be close to what you need; searching in the array for the keys and set a rank for each found element.
result = []
array.each do |ar|
rank = 0
search_keys.each do |key|
if ar[1].include?(key)
rank += 1
end
end
if rank > 0
result << [rank, ar]
end
end
This code can be written better than the above, but i wanted to show you the details.
This works. Will find and return an array of matched* rows as result.
*matched rows = a row where the id, title, code or description match ANY of the provided seach_keys. incl partial searches such as 'bear' in 'bears'
result = []
array.each do |a|
a.each do |i|
search_keys.each do |k|
result << a if i.include?(k)
end
end
end
result.uniq!
You could probably write it in a more succinct way...
array = [
["id", "title", "code", "description"],
["1", "once upon a time", "3241", "a classic story"],
["2", "a big bad wolf", "4235", "a little scary"],
["3", "three big bears", "2626", "a heart warmer"]
]
search_keys = ["big", "bear"]
def sift(records, target_field, search_keys)
# find target_field index
target_field_index = nil
records.first.each_with_index do |e, i|
if e == target_field
target_field_index = i
break
end
end
if target_field_index.nil?
raise "Target field was not found"
end
# sums up which records have a match and how many keys they match
# key => val = record => number of keys matched
counter = Hash.new(0) # each new hash key is init'd with value of 0
records.each do |record| # look at all our given records
search_keys.each do |key| # check each search key on the field
if record[target_field_index].include?(key)
counter[record] += 1 # found a key, init to 0 if required and increment count
end
end
end
# find the result with the most search key matches
top_result = counter.to_a.reduce do |top, record|
if record[1] > top[1] # [0] = record, [1] = key hit count
top = record # set to new top
end
top # continue with reduce
end.first # only care about the record (not the key hit count)
end
puts "Top result: #{sift array, 'title', search_keys}"
# => Top result: ["3", "three big bears", "2626", "a heart warmer"]
Here is my one-line shot
p array.find_all {|a|a.join.scan(/#{search_keys.join("|")}/).length==search_keys.length}
=>[["3", "three big bears", "2626", "a heart warmer"]]
to get all the rows in order of number of matches
p array.drop(1).sort_by {|a|a.join.scan(/#{search_keys.join("|")}/).length}.reverse
Anyone knows how to combine the last solution so that the rows that contain none of the keys are dropped and to keep it concise as is ?

How to store and retrieve values using Ruby?

I am trying to build a "train game" based loosely on the old video game "Drug Wars." I am currently working my way through LRTHW, and I believe that I should be using OOP, but I'm not to that lesson yet.
The premise is that you have a set number of cars on your train and you can see what products are for sale in other cities (no limit on the amount you can buy or sale presuming you can fit them in your train). This code isn't complete, but I'm wondering if I'm even approaching this half way sanely in regard to creating and accessing the product prices in a reasonable manner.
#Initializing variables. Current_location should be changed to random
#in the future.
current_location = 'omaha'
train = []
new_york = []
chicago = []
omaha = []
dallas = []
seattle = []
def prompt()
print "> "
end
#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.
def selection()
puts "Do you want to travel, buy, sell or get info?"
prompt; selection = gets.chomp
if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end
#This generates a new cost for each good at the start of each turn.
def generate_costs(new_york, chicago, omaha, dallas, seattle)
new_york[0] = rand(10)
new_york[1] = rand(10) + 25
new_york[2] = rand(5) + 10
omaha[0] = rand(10)
omaha[1] = rand(10) + 25
omaha[2] = rand(5) + 10
chicago[0] = rand(25) + 5
chicago[1] = rand(5) + 10
chicago[2] = rand(4)
dallas[0] = rand(6) + 11
dallas[1] = rand(3) + 10
dallas[2] = rand(8)
seattle[0] = rand(6)
seattle[1] = rand(10) + 24
seattle[2] = rand(14) + 13
return new_york, chicago, omaha, dallas, seattle
end
# This is my main() loop. It drives the game forward.
for i in (0..5)
new_york, chicago, omaha, dallas, seattle = generate_costs(new_york, chicago, omaha, dallas, seattle)
turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."
puts "{ ___________________________ }"
#Code Here evaluates and accesses pricing based on current_location.
#Is this the correct way to do this?
fish = eval("#{current_location}[0]")
coal = eval("#{current_location}[1]")
cattle = eval("#{current_location}[2]")
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"
change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end
end
eval is a nasty way of accessing data ( When is `eval` in Ruby justified? ). You should consider moving things into an object.
I have improved the code slightly, storing the cities in a hash, which gets rid of the evals. I have stubbed out the generate_costs logic but you can assign it by doing:
cities[:new_york][0] = rand(10)
Ideally, the code should be re-written in an object-oriented syntax. If I get some time then I'll knock up an example for you.
Here is the code:
#Initializing variables. Current_location should be changed to random
#in the future.
current_location = :omaha
train = []
cities = {
:new_york => [],
:chicago => [],
:omaha => [],
:dallas => [],
:seattle => []
}
def prompt()
print "> "
end
#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.
def selection()
puts "Do you want to travel, buy, sell or get info?"
prompt; selection = gets.chomp
if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end
#This generates a new cost for each good at the start of each turn.
def generate_costs(cities)
cities.each do |key,city|
0.upto(2) do |i|
city[i] = rand(10)
end
end
end
# This is my main() loop. It drives the game forward.
for i in (0..5)
generate_costs(cities)
turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."
p cities
puts "{ ___________________________ }"
fish = cities[current_location][0]
coal = cities[current_location][1]
cattle = cities[current_location][2]
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"
change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end
end

Resources