parsing numbers in a string - ruby

I'm trying to get my code to pass this test. If you've ever played magic the gathering, this might look familiar to you.
Test.assert_equals(can_cast("11RB","10B","1R"), true)
Test.assert_equals(can_cast("13BBRR","10BR","2R","B"), true)
But I can't seem to parse the correct numbers out of the elements correctly. Does anyone see a flaw in my code that's keeping me from passing these test?
def can_cast(hand, *spell_cost)
colored_mana_hand = Array.new
colored_mana_cost_aggregate = Array.new
colored_mana_spent = Array.new
colorless_mana_hand_array = []
colorless_mana_hand = 0
colorless_mana_cost_array = []
colorless_mana_cost_aggregate_array = []
colorless_mana_cost_aggregate = 0
hand.split("").each do |i|
if i.to_i != 0 # extracting existing colorless mana from hand
colorless_mana_hand_array << i
else
colored_mana_hand << i
end
end
colorless_mana_hand = colorless_mana_hand_array.join.to_i
spell_cost.each do |i| # extracting existing colorless mana from cost
i.split("").each do |j|
if j.to_i != 0
colorless_mana_cost_array << j
else
colored_mana_cost_aggregate << j
end
end
colorless_mana_cost_aggregate_array << colorless_mana_cost_array.join
colorless_mana_cost_array.clear
end
colorless_mana_cost_aggregate_array.each do |i|
colorless_mana_cost_aggregate += i.to_i
end
colored_mana_cost_aggregate.each do |i| # pay colored mana first
if colored_mana_hand.include?(i)
colored_mana_spent << i
colored_mana_hand.rotate(colored_mana_hand.index(i)).shift
end
end
(colored_mana_spent.sort == colored_mana_cost_aggregate.sort) && (colored_mana_hand.length + colorless_mana_hand) >= colorless_mana_cost_aggregate
end

This is a funny way to pull numbers out of the string. It would probably be easier to use scan, here is how you can use it:
# extracting existing colorless
colorless_mana_hand_array = hand.scan(/\d/).join.to_i
This will extract the digits from the string into an array, join them, and then convert to an integer.

Does anyone see a flaw in my code that's keeping me from passing these test?
Yes, Indeed.
You split your spell_cost strings via:
i.split("").each do |j|
# ...
end
The code will yield each single character to the block. For "10BR" this gives:
"10BR".split("").each do |j|
p j: j
end
Output:
{:j=>"1"}
{:j=>"0"}
{:j=>"B"}
{:j=>"R"}
Furthermore you have this check:
if j.to_i != 0
colorless_mana_cost_array << j
else
colored_mana_cost_aggregate << j
end
For the above input, you get
colorless_mana_cost_array #=> ["1"]
colored_mana_cost_aggregate #=> ["0", "B", "R"]
Because "0".to_i != 0 evaluate to false, just like "B" and "R".
If I understand your code correctly, "0" should go into the colorless_mana_cost_array.

Related

`[]': no implicit conversion of String into Integer (TypeError)

I seem to be getting a type error in the following code:
def can_cast(hand, *spell_cost)
colored_mana_hand = Array.new
colored_mana_cost_aggregate = Array.new
colored_mana_spent = Array.new
colorless_mana_hand = 0
colorless_mana_cost_aggregate = 0
hand_array = hand.split("").sort
total_cost = spell_cost.join.split("").sort
hand_array.each do |i|
if hand_array[i].to_i != 0
colorless_mana_hand += hand_array[i].to_i
else
colored_mana_hand << hand_array[i]
end
end
total_cost.each do |i|
if total_cost[i].to_i != 0
colorless_mana_cost_aggregate += total_cost[i].to_i
else
colored_mana_cost_aggregate << total_cost[i]
end
end
colored_mana_cost_aggregate.each do |i|
if colored_mana_hand.include?(colored_mana_cost_aggregate[i])
colored_mana_spent << colored_mana_cost_aggregate[i]
colored_mana_hand.rotate(colored_mana_hand.index(colored_mana_cost_aggregate[i])).shift
end
end
colored_mana_spent == colored_mana_cost_aggregate && (colored_mana_hand.length + colorless_mana_hand) >= colorless_mana_cost_aggregate
end
It looks like this
`[]': no implicit conversion of String into Integer (TypeError)
Could Anyone help me?
I think I'm using an array as an integer, but I can't see where that might be possible.
As Brian says, you're misinterpreting how the each works with the block parameter
Instead of
hand_array.each do |i|
if hand_array[i].to_i != 0
colorless_mana_hand += hand_array[i].to_i
else
colored_mana_hand << hand_array[i]
end
end
You just reference the element
hand_array.each do |hand|
if hand.to_i != 0
colorless_mana_hand += hand.to_i
else
colored_mana_hand << hand
end
end
If you actually want the index of the array, you can use each_with_index
hand_array.each_with_index do |_hand, i|
if hand_array[i].to_i != 0
colorless_mana_hand += hand_array[i].to_i
else
colored_mana_hand << hand_array[i]
end
end
or just use a range of index values
(0...hand_array.count).each do |i|
if hand_array[i].to_i != 0
colorless_mana_hand += hand_array[i].to_i
else
colored_mana_hand << hand_array[i]
end
end
As to exactly what the error means... if the hand_array contains strings such as "7" then i contains "7" and you're trying to access hand_array["7"] ... in other words trying to access an array element using a string instead of an integer. And Ruby won't automatically (implicitly) convert a string into an integer for an array index.
This line seems suspicious:
if colored_mana_hand.include?(colored_mana_cost_aggregate[i])
In this context, i is an element of colored_mana_cost_aggregate (from the each iterator on the line before) and you're trying to use it as an array index.

String compressor (Ruby)

Here is my code in ruby for a word compression.
For any given word (e.g. abbbcca) the compressed word/output should be in the format as "letter+repetition" (for above example, output: a1b3c2a1).
Here I'm so close to the completion but my result isn't in the expected format. It's counting the whole letters in string.chars.each thus resulting output as a2b3c2a2.
Any help?
def string_compressor(string)
new_string = []
puts string.squeeze
string.squeeze.chars.each { |s|
count = 0
string.chars.each { |w|
if [s] == [w]
count += 1
end
}
new_string << "#{s}#{count}"
puts "#{new_string}"
}
if new_string.length > string.length
return string
elsif new_string.length < string.length
return new_string
else "Equal"
end
end
string_compressor("abbbcca")
'abbbcca'.chars.chunk{|c| c}.map{|c, a| [c, a.size]}.flatten.join
Adapted from a similar question.
Similar:
'abbbcca'.chars.chunk{|c| c}.map{|c, a| "#{c}#{a.size}"}.join
See chunk documentation
You can use a regular expression for that.
'abbbcca'.gsub(/(.)\1*/) { |m| "%s%d" % [m[0], m.size] }
#=> "a1b3c2a1"
The regular expression reads, "match any character, capturing it in group 1. Then match the contents of capture group 1 zero or more times".
As you said, your code counts every letter in the string, not just the one grouped next to one another.
Here's a modified version :
def display_count(count)
if count == 1
""
else
count.to_s
end
end
def string_compressor(string)
new_string = ''
last_char = nil
count = 0
string.chars.each do |char|
if char == last_char
count += 1
else
new_string << "#{last_char}#{display_count(count)}" if last_char
last_char = char
count = 1
end
end
new_string << "#{last_char}#{display_count(count)}" if last_char
new_string
end
p string_compressor('abbbcca') #=> "ab3c2a"
p string_compressor('aaaabbb') #=> "a4b3"
p string_compressor('aabb') #=> "a2b2"
p string_compressor('abc') #=> "abc"
Note that with display_count removing 1s from the string, new_string can never be longer than string. It also probably isn't a good idea to return Equal as a supposedly compressed string.
To decompress the string :
def string_decompressor(string)
string.gsub(/([a-z])(\d+)/i){$1*$2.to_i}
end
p string_decompressor("a5b11") #=> "aaaaabbbbbbbbbbb"
p string_decompressor("ab3c2a") #=> "abbbcca"

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.

What is the best way to split a string to get all the substrings by Ruby?

For example, the words "stack", I want to get an array like:
['s', 'st', 'sta', ... 'stack', 't', 'ta', ... , 'c', 'ck', 'k']
I did this by such code:
def split_word(str)
result = []
chas = str.split("")
len = chas.size
(0..len-1).each do |i|
(i..len-1).each do |j|
result.push(chas[i..j].join)
end
end
result.uniq
end
Is there better and clean way to do that? Thanks.
def split_word s
(0..s.length).inject([]){|ai,i|
(1..s.length - i).inject(ai){|aj,j|
aj << s[i,j]
}
}.uniq
end
And you can also consider using Set instead of Array for the result.
PS: Here's another idea, based on array product:
def split_word s
indices = (0...s.length).to_a
indices.product(indices).reject{|i,j| i > j}.map{|i,j| s[i..j]}.uniq
end
I'd write:
def split_word(s)
0.upto(s.length - 1).flat_map do |start|
1.upto(s.length - start).map do |length|
s[start, length]
end
end.uniq
end
groups = split_word("stack")
# ["s", "st", "sta", "stac", "stack", "t", "ta", "tac", "tack", "a", "ac", "ack", "c", "ck", "k"]
It's usually more clear and more compact to use map (functional) instead of the pattern init empty + each + append + return (imperative).
def substrings(str)
output = []
(0...str.length).each do |i|
(i...str.length).each do |j|
output << str[i..j]
end
end
output
end
this is just a cleaned up version of your method and it works with less steps =)
Don't think so.
Here's my attempted version:
def split_word(str)
length = str.length - 1
[].tap do |result|
0.upto(length) do |i|
length.downto(i) do |j|
substring = str[i..j]
result << substring unless result.include?(substring)
end
end
end
end
def substrings(str)
(0...str.length).map do |i|
(i...str.length).each { |j| str[i..j]}
end
end
Just another way to do it, that reads a little clearer to me.
Here is the recursive way to get all the possible sub strings.
def substrings str
return [] if str.size < 1
((0..str.size-1).map do |pos|
str[0..pos]
end) + substrings(str[1..])
end
Way later, but this is what I got from reformatting your code a bit.
def substrings(string)
siz = string.length
answer = []
(0..siz-1).each do |n|
(n..siz-1).each do |i|
answer << string[n..i]
end
end
answer
end

Recursively merge multidimensional arrays, hashes and symbols

I need a chunk of Ruby code to combine an array of contents like such:
[{:dim_location=>[{:dim_city=>:dim_state}]},
:dim_marital_status,
{:dim_location=>[:dim_zip, :dim_business]}]
into:
[{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]},
:dim_marital_status]
It needs to support an arbitrary level of depth, though the depth will rarely be beyond 8 levels deep.
Revised after comment:
source = [{:dim_location=>[{:dim_city=>:dim_state}]}, :dim_marital_status, {:dim_location=>[:dim_zip, :dim_business]}]
expected = [{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]}, :dim_marital_status]
source2 = [{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}]
def merge_dim_locations(array)
return array unless array.is_a?(Array)
values = array.dup
dim_locations = values.select {|x| x.is_a?(Hash) && x.has_key?(:dim_location)}
old_index = values.index(dim_locations[0]) unless dim_locations.empty?
merged = dim_locations.inject({}) do |memo, obj|
values.delete(obj)
x = merge_dim_locations(obj[:dim_location])
if x.is_a?(Array)
memo[:dim_location] = (memo[:dim_location] || []) + x
else
memo[:dim_location] ||= []
memo[:dim_location] << x
end
memo
end
unless merged.empty?
values.insert(old_index, merged)
end
values
end
puts "source1:"
puts source.inspect
puts "result1:"
puts merge_dim_locations(source).inspect
puts "expected1:"
puts expected.inspect
puts "\nsource2:"
puts source2.inspect
puts "result2:"
puts merge_dim_locations(source2).inspect
I don't think there's enough detail in your question to give you a complete answer, but this might get you started:
class Hash
def recursive_merge!(other)
other.keys.each do |k|
if self[k].is_a?(Array) && other[k].is_a?(Array)
self[k] += other[k]
elsif self[k].is_a?(Hash) && other[k].is_a?(Hash)
self[k].recursive_merge!(other[k])
else
self[k] = other[k]
end
end
self
end
end

Resources