how to put a local variable(sub) in an array in ruby - ruby

if File.exist?("restaurant.txt") then
newfile=File.open("restaurant.txt","r")
lines=newfile.readlines
i=0
while i<lines.size
item=lines[i]
i+=1
quantity=lines[i].to_i
i+=1
price=lines[i].to_i
i+=1
sub=quantity*price
puts sub
end
end
I am trying to move the sub values into an array

Just create an array and push the values.
array = Array.new
if File.exist?("restaurant.txt") then
newfile = File.open("restaurant.txt","r")
lines = newfile.readlines
i=0
while i < lines.size
item = lines[i]
i += 1
quantity = lines[i].to_i
i += 1
price = lines[i].to_i
i += 1
sub = quantity*price
#puts sub
array.push(sub)
end
puts array.to_s
end

Using File#readlines, array slicing, array deconstruction, and asserting a precondition instead of using an if statement, this code can be simplified as follows:
#!/usr/bin/env ruby
FILENAME = 'restaurant.txt'
raise "#{FILENAME} does not exist" unless File.file?(FILENAME)
lines = File.readlines(FILENAME).map(&:chomp)
subs = lines.each_slice(3).map do |slice|
_item, quantity, price = slice
_item = _item.to_i # _item is unused, but this is here for completeness
quantity = quantity.to_i
price = price.to_f
quantity * price
end
p subs
Also, price, and maybe even quantity should be a floating point number and not an integer.

Related

ruby - access array with hash key

I am struggling to understand how I can access an array with a hash key. In my code, I create a hash with keys and values. Now, I want to set the values in a Car class. Whenever I try to instantiate the Car, the argument expects Integer and not a String.
I am getting the following error: TypeError (no implicit conversion of String into Integer)
Here is my code:
class Car_maker
attr_accessor :car_maker
def initialize(car_maker)
#car_maker = car_maker
end
end
class Car_model < Car_maker
attr_accessor :km, :type, :transmission, :stock, :drivetrain, :status,
:fuel, :car_maker, :model, :year, :trim, :features
#total number of instances & array with car objects
##totalCars = 0
##catalogue = []
def initialize(km, type, transmission, stock, drivetrain, status, fuel, car_maker, model, year, trim, features)
super(car_maker)
#km = km
#type = type
#transmission = transmission
#stock = stock
#drivetrain = drivetrain
#status = status
#fuel = fuel
#model = model
#year = year
#trim = trim
#features = features
##totalCars += 1
end
def self.convertListings2Catalogue(line)
#Initialise arrays and use them to compare
type = ["Sedan", "coupe", "hatchback", "station", "SUV"]
transmission = ["auto", "manual", "steptronic"]
drivetrain = ["FWD", "RWD", "AWD"]
status = ["new", "used"]
car_maker = ["honda", "toyota", "mercedes", "bmw", "lexus"]
hash = Hash.new
#In this part, we hash the set of features using regex
copyOfLine = line
regex = Regexp.new(/{(.*?)}/)
match_array = copyOfLine.scan(regex)
match_array.each do |line|
hash["features"] = line
end
#Now, we split every comma and start matching fields
newStr = line[0...line.index('{')] + line[line.index('}')+1...line.length]
arrayOfElements = newStr.split(',')
arrayOfElements.each do |value|
if value.include?("km") and !value.include?("/")
hash["km"] = value
elsif type.include?(value)
hash["type"] = value
elsif transmission.include?(value.downcase)
hash["transmission"] = value
elsif value.include?("/") and value.include?("km")
hash["fuel economy"] = value
elsif drivetrain.include?(value)
hash["drivetrain"] = value
elsif status.include?(value.downcase)
hash["status"] = value
elsif /(?=.*[a-zA-Z])(?=.*[0-9])/.match(value) and !value.include?("km")
hash["stock"] = value
elsif car_maker.include?(value.downcase)
hash["carmaker"] = value
elsif /^\d{4}$/.match(value)
hash["year"] = value
elsif value.length == 2
hash["trim"] = value
else
if value.length > 2
hash["model"] = value
end
end
end
end
end
textFile = File.open('cars.txt', 'r')
textFile.each_line{|line|
if line.length > 2
result = Car_model.convertListings2Catalogue(line)
puts "Hash: #{result}"
carObj = Car_model.new(result["km"], result["type"], result["transmission"], result["stock"], result["drivetrain"],
result["status"], result["fuel"], result["carmaker"], result["model"], result["year"], result["trim"], result["features"])
###catalogue.push (carObj)
end
}
This line
result = Car_model.convertListings2Catalogue(line)
Doesn't return the hash object. It returns arrayOfElements since that's what the each method actually returns and the each method is the last method executed in the method (although there are hash assignments within it, it's only the last value that's returned unless you use an explicit return statement.
Just use the variable hash in the last line of the convertListing2Catalog method
if value.length > 2
hash["model"] = value
end
end
end
hash # < this is the last line of the method so it's the value that will be returned
end
end
If you think about it, there were several variables created in the method. There's no reason to expect that the contents of any specific variable such as hash would be returned, and ruby methods by default return the last executed command.

Merging Ranges using Sets - Error - Stack level too deep (SystemStackError)

I have a number of ranges that I want merge together if they overlap. The way I’m currently doing this is by using Sets.
This is working. However, when I attempt the same code with a larger ranges as follows, I get a `stack level too deep (SystemStackError).
require 'set'
ranges = [Range.new(73, 856), Range.new(82, 1145), Range.new(116, 2914), Range.new(3203, 3241)]
set = Set.new
ranges.each { |r| set << r.to_set }
set.flatten!
sets_subsets = set.divide { |i, j| (i - j).abs == 1 } # this line causes the error
puts sets_subsets
The line that is failing is taken directly from the Ruby Set Documentation.
I would appreciate it if anyone could suggest a fix or an alternative that works for the above example
EDIT
I have put the full code I’m using here:
Basically it is used to add html tags to an amino acid sequence according to some features.
require 'set'
def calculate_formatting_classes(hsps, signalp)
merged_hsps = merge_ranges(hsps)
sp = format_signalp(merged_hsps, signalp)
hsp_class = (merged_hsps - sp[1]) - sp[0]
rank_format_positions(sp, hsp_class)
end
def merge_ranges(ranges)
set = Set.new
ranges.each { |r| set << r.to_set }
set.flatten
end
def format_signalp(merged_hsps, sp)
sp_class = sp - merged_hsps
sp_hsp_class = sp & merged_hsps # overlap regions between sp & merged_hsp
[sp_class, sp_hsp_class]
end
def rank_format_positions(sp, hsp_class)
results = []
results += sets_to_hash(sp[0], 'sp')
results += sets_to_hash(sp[1], 'sphsp')
results += sets_to_hash(hsp_class, 'hsp')
results.sort_by { |s| s[:pos] }
end
def sets_to_hash(set = nil, cl)
return nil if set.nil?
hashes = []
merged_set = set.divide { |i, j| (i - j).abs == 1 }
merged_set.each do |s|
hashes << { pos: s.min.to_i - 1, insert: "<span class=#{cl}>" }
hashes << { pos: s.max.to_i - 0.1, insert: '</span>' } # for ordering
end
hashes
end
working_hsp = [Range.new(7, 136), Range.new(143, 178)]
not_working_hsp = [Range.new(73, 856), Range.new(82, 1145),
Range.new(116, 2914), Range.new(3203, 3241)]
sp = Range.new(1, 20).to_set
# working
results = calculate_formatting_classes(working_hsp, sp)
# Not Working
# results = calculate_formatting_classes(not_working_hsp, sp)
puts results
Here is one way to do this:
ranges = [Range.new(73, 856), Range.new(82, 1145),
Range.new(116, 2914), Range.new(3203, 3241)]
ranges.size.times do
ranges = ranges.sort_by(&:begin)
t = ranges.each_cons(2).to_a
t.each do |r1, r2|
if (r2.cover? r1.begin) || (r2.cover? r1.end) ||
(r1.cover? r2.begin) || (r1.cover? r2.end)
ranges << Range.new([r1.begin, r2.begin].min, [r1.end, r2.end].max)
ranges.delete(r1)
ranges.delete(r2)
t.delete [r1,r2]
end
end
end
p ranges
#=> [73..2914, 3203..3241]
The other answers aren't bad, but I prefer a simple recursive approach:
def merge_ranges(*ranges)
range, *rest = ranges
return if range.nil?
# Find the index of the first range in `rest` that overlaps this one
other_idx = rest.find_index do |other|
range.cover?(other.begin) || other.cover?(range.begin)
end
if other_idx
# An overlapping range was found; remove it from `rest` and merge
# it with this one
other = rest.slice!(other_idx)
merged = ([range.begin, other.begin].min)..([range.end, other.end].max)
# Try again with the merged range and the remaining `rest`
merge_ranges(merged, *rest)
else
# No overlapping range was found; move on
[ range, *merge_ranges(*rest) ]
end
end
Note: This code assumes each range is ascending (e.g. 10..5 will break it).
Usage:
ranges = [ 73..856, 82..1145, 116..2914, 3203..3241 ]
p merge_ranges(*ranges)
# => [73..2914, 3203..3241]
ranges = [ 0..10, 5..20, 30..50, 45..80, 50..90, 100..101, 101..200 ]
p merge_ranges(*ranges)
# => [0..20, 30..90, 100..200]
I believe your resulting set has too many items (2881) to be used with divide, which if I understood correctly, would require 2881^2881 iterations, which is such a big number (8,7927981983090337174360463368808e+9966) that running it would take nearly forever even if you didn't get stack level too deep error.
Without using sets, you can use this code to merge the ranges:
module RangeMerger
def merge(range_b)
if cover?(range_b.first) && cover?(range_b.last)
self
elsif cover?(range_b.first)
self.class.new(first, range_b.last)
elsif cover?(range_b.last)
self.class.new(range_b.first, last)
else
nil # Unmergable
end
end
end
module ArrayRangePusher
def <<(item)
if item.kind_of?(Range)
item.extend RangeMerger
each_with_index do |own_item, idx|
own_item.extend RangeMerger
if new_range = own_item.merge(item)
self[idx] = new_range
return self
end
end
end
super
end
end
ranges = [Range.new(73, 856), Range.new(82, 1145), Range.new(116, 2914), Range.new(3203, 3241)]
new_ranges = Array.new
new_ranges.extend ArrayRangePusher
ranges.each do |range|
new_ranges << range
end
puts ranges.inspect
puts new_ranges.inspect
This will output:
[73..856, 82..1145, 116..2914, 3203..3241]
[73..2914, 3203..3241]
which I believe is the intended output for your original problem. It's a bit ugly, but I'm a bit rusty at the moment.
Edit: I don't think this has anything to do with your original problem before the edits which was about merging ranges.

Add to an array from within a loop using Ruby

I am having a few problems adding to an array from within a loop.
It only adds the last results to the array and loses the previous 9 sets.
I think I have to create a new array inside of the loop and then add the new one to the previous. I'm just not sure how I go about doing that.
array = Array.new
10.times do
array2 = Array.new
pagenum = 0
results = Nokogiri::HTML(open("#{url}#{pagenum}"))
results.css("div").each do |div|
array.push div.inner_text
end
pagenum + 10
array.concat(array2)
end
You are fetching same page (0) 10 times.
10.times do
...
pagenum = 0 # <--------
results = Nokogiri::HTML(open("#{url}#{pagenum}"))
...
end
Try following:
array = Array.new
10.times do |pagenum|
results = Nokogiri::HTML(open("#{url}#{pagenum}"))
array += results.css("div").map(&:inner_text)
end

Fibonacci sequence: ruby each method - nil can't be coerced

I put together the following methods in order to (1) generate a Fibonacci sequence in an array up to a particular index value, and then (2) return the product of all values in that sequence for the given index.
My issues seems to come about when I increase the master_num value above 4. Prior to that both the sequence generating & product methods work. I'm pretty sure my problem is related to a mistake in my mind about how Ruby is indexing the sequence array...
require 'pry' #optional, i use pry to debug
master_num = 5
def gen_fib_sequence_up_to_index num
rec_gen_fib_values num, []
end
def rec_gen_fib_values num, full_array
start_array = [1,1]
count = 0
if num < start_array.size
num
end
until start_array.size == num
next_element = start_array[count] + start_array[count + 1]
start_array.push(next_element)
count += 1
end
return start_array
end
p gen_fib_sequence_up_to_index master_num
def fib_sequence_product sequence_array
product = 1
sequence_array.each do |i|
product = product * sequence_array[i]
#binding.pry
end
product
end
p fib_sequence_product gen_fib_sequence_up_to_index master_num
Thanks so much for your comments and suggestions.
def fib_sequence_product sequence_array
product = 1
sequence_array.each do |i|
product = product * sequence_array[i]
#binding.pry
end
product
end
In the block i is not an index, it's the actual value. This should work:
def fib_sequence_product sequence_array
product = 1
sequence_array.each do |i|
product = product * i # or product *= i or ...
end
product
end
Both generating an array with Fibonacci numbers and calculating the product can be done less verbose.

Count from list in ruby

Im thinking this is correct, but probably WAY off.
I have a string formatted as such
name, name1, name2, name3, name4, etc
admins_count = 0
if ["name2", "name3"].include?(player_list)
admins_count += 1
end
Is this the proper way to count matches in a list?
Essentially, its a list of players, I want to count how many of the names in the second list mtch against player_list.
this is what worked for me a combo of things
player_list = response[3].split(" ", 2)[1].chomp[1..-2]
admin_list = "Howard_Roark, Gerrit8500, fffizzz"
mod_list = "ZionRx, rodtang, fuzzamuzza, DJRedFlames, bingbong2715, ErebusAnima, Twentytenor2, zephyrnug, Tiberione, deadkill02, tTheoRyy, PyneApll, tercept, Hestehaven, Orjis87, Yaltar101"
mod_arr = mod_list.split(", ")
admin_arr = admin_list.split(", ")
player_arr = player_list.split(", ")
mods_count = 0
mod_arr.each do |s|
mods_count += 1 if player_arr.include? s
end
admins_count = 0
admin_arr.each do |s1|
admins_count += 1 if player_arr.include? s1
end
puts "players.value #{player_count}"
puts "mods.value #{mods_count}"
puts "admins.value #{admins_count}"
I think this is more of what you want:
formatted_string = "name,name1,name2,name3,name4";
string_arr = formatted_string.split(",")
string_arr.each do |s|
admin_count += 1 if player_list.include? s
end
If player_list is an Array, you could use the & operator. It takes two Arrays and returns a new Array of only the items the two Arrays have in common (with no duplicates).
# I'm assuming player_list becomes a string of 'name1, name2, name3...' after
# the 'chomp' method
player_list = response[3].split(" ", 2)[1].chomp[1..-2].split(",").map(&:strip)
admins_count = (["name2", "name3"] & player_list).size
So, if player_list contains "name2" then it will return ["name2"]. We then call .size on that and we would get 1 which would get assigned to admins_count.

Resources