Trouble when using match on an array - ruby

What i'm trying to do is create a method that can be given an array as an argument. The array should have some numbers in it. The method will return the number of times the array includes each number inside of it. I understand that there are probably many ways to do this, but I'd appreciate it if folks could help me understand why my way is not working rather than just advising me to do something completely different.
So I start by trying this method out
def score (dice)
dice.each do |die|
x = /(die)/.match(dice.to_s).length
end
x
end
and calling it with score ([5])expecting to get an output of 1. However, I get
NoMethodError: undefined method `length' for nil:NilClass
from t2.rb:22:in `block in score'
from t2.rb:21:in `each'
from t2.rb:21:in `score'
from (irb):2
from /home/macs/.rvm/rubies/ruby-2.0.0-p247/bin/irb:13:in `<main>'
I have also tried changing the match statement slightly (getting rid of the to_s) so it is
def score (dice)
dice.each do |die|
x = /(die)/.match(dice).length
end
x
end
and calling it with score ([5]) I get
TypeError: no implicit conversion of Array into String
from t2.rb:22:in `match'
from t2.rb:22:in `block in score'
from t2.rb:21:in `each'
from t2.rb:21:in `score'
from (irb):2
from /home/macs/.rvm/rubies/ruby-2.0.0-p247/bin/irb:13:in `<main>'
Really not sure how I'm supposed to accomplish this matching.

In this line
/(die)/.match(dice.to_s).length
the method match returns nil if the argument you are passing doesn't match the regular expression, which leads to this error
nil.length
# => NoMethodError: undefined method `length' for nil:NilClass
The method will return the number of times the array includes each
number inside of it.
You can try this
a = [1,1,1,2,2,1,3]
a.uniq.map { |x| a.count(x) }
# => [4, 2, 1]
a.uniq.map { |x| {x => a.count(x)} }
# => [{1=>4}, {2=>2}, {3=>1}]

If you want to count the occurence of each elements in the array, then you can do something like this
def score (dice)
count_hash = {}
dice.uniq.each do |die|
count_hash[die] = dice.count(die)
end
count_hash
end

I'd appreciate it if folks could help me understand why my way is not working ...
/(die)/ creates a Regexp, a pattern that can be matched against a string. Your pattern matches and captures die.
Regexp#match returns a MatchData object if there was a match:
/(die)/.match('a string with die') #=> #<MatchData "die" 1:"die">
# here's the match: ^^^
or nil if there was no match:
/(die)/.match('a string with dice') #=> nil
You are not working with string but with an array of integers. You convert this array to a string using Array#to_s:
dice = [5]
dice.to_s #=> "[5]"
This string doesn't contain die and therefore match returns nil:
/(die)/.match("[5]") #=> nil
Calling nil.length then raises the NoMethodError.
Passing the array "as-is" doesn't work either, because match expects a string:
/(die)/.match([5]) #=> TypeError: no implicit conversion of Array into String
Using a Regexp is not going to work here, you'll have to approach this problem in another way.

This is probably the most rubyish way to solve the problem:
a = [1,1,1,2,2,1,3]
p Hash[a.group_by{|x|x}.map{|key, val| [key,val.size]}]
#=> {1=>4, 2=>2, 3=>1}

An example that might help you implement your logic
a = [2,3,2,8,3]
a.uniq.each {|i| print i, "=>", a.to_s.scan(/#{i}/).length, " times \n" } #=> this works but ugly.
a.uniq.each {|i| print i, "=>", a.count(i), " times \n" } #=> borrowed from one of the answers.
2=>2 times
3=>2 times
8=>1 times

You're getting errors because in both cases you are trying to match a string (5) with the wrong thing.
This tries to match die with the entire array dice converted to a string:
dice.each do |die|
x = /(die)/.match(dice.to_s).length
end
This tries to match die with the dice array itself:
dice.each do |die|
x = /(die)/.match(dice).length
end

Related

Recieving desired output but error message tags along

I am passing all of the tests for this kata yet still receiving some errors. The codewars website does not allow me to see all the test cases, so with my inexperienced eyes it is hard to see the issue. Any explanation for what I am seeing is greatly appreciated. My method is to parse for nil, split digits into an array, and evaluate the first one for even/odd and place it into the new array with/out "-" accordingly, and removing each first element as I iterate through.
Dashatize:
Given a number, return a string with dash'-'marks before and after each odd integer, but do not begin or end the string with a dash mark.
Ex:
dashatize(274) -> '2-7-4'
dashatize(6815) -> '68-1-5'
def dashatize(num)
if num.nil?
"nil"
else
arr2 = []
arr = num.digits.reverse
arr2 << arr[0]
arr.shift
until arr == [] do
if arr[0].even? && arr2[-1].to_i.even?
arr2 << arr[0].to_s
arr.shift
else
arr2 << "-"
arr2 << arr[0].to_s
arr.shift
end
end
arr2.join
end
end
I pass all tests but still fail the kata due to this:
#<Math::DomainError: out of domain> main.rb:10:in `digits'
main.rb:10:in `dashatize' main.rb:39:in `block (2 levels) in <main>'
/runner/frameworks/ruby/cw-2.rb:180:in `wrap_error'
/runner/frameworks/ruby/cw-2.rb:72:in `it'
/runner/frameworks/ruby/cw-2.rb:206:in `it' main.rb:36:in `block in <main>'
/runner/frameworks/ruby/cw-2.rb:55:in `block in describe'
/runner/frameworks/ruby/cw-2.rb:46:in `measure'
/runner/frameworks/ruby/cw-2.rb:51:in `describe'
/runner/frameworks/ruby/cw-2.rb:202:in `describe' main.rb:29:in
`<main>'
From the docs:
Math::DomainError Raised when a mathematical function is evaluated
outside of its domain of definition.
You're calling the #digits function on the input, which is probably negative for some example in the test cases and you get the error mentioned above.
So again, doing this:
-1.digits
Will give you an error like you got:
out of domain
():1:in `digits'
():1:in `<main>'
You have to use something other than #digits or make it positive first or find some other solution.
On a side note, here' my approach to the problem:
def dashatize(number)
number.to_s
.gsub(/[13579]/, '-\\0-')
.gsub('--','-')
.delete_suffix('-')
.delete_prefix('-')
end
dashatize(68145)
#=>"68-1-4-5"
dashatize(6815)
#=>"68-1-5"
dashatize(274)
#=> "2-7-4"
I guess #Viktor already caught the reason: maybe the test case uses negative numbers.
You can fix your code changing your line arr = num.digits.reverse using Integer#abs to:
arr = num.abs.digits.reverse
Side note, a shorter version using the array of digits (Array#slice_when):
num.abs.digits.reverse.slice_when { |a, b| a.odd? || b.odd? }.map(&:join).join('-')

Understanding strange output in multidimensional array

I am new to ruby and I was trying to iterate over a 2d array. I made a mistake in my code. From that mistake, I noticed some unexpected output.
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
i = 0;
s.each{
|array| so = array[i] # pin
puts so[i]
}
Due to #pin, if i = 0, output is h t r. i = 1 gives w h r. i > 1 gives an error:
C:/Ruby.rb in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from C:/Ruby.rb:3:in `each'
from C:/Ruby.rb:3:in `<main>'
If I use |array| so = array # pin, then my code does not produce strange output. I'll just fix the remaining stuff to make my code iterate for all values that 'i' can have.
Please explain this.
PS: Working code is here
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
s.each{
|array| so = array
array.each{
|str| puts str
}
}
For each type of sandwich, when i is 0, so is the 1st element, which is the meat. so[0] is the first letter of the meat.
When i is 1, which is the 2nd element, which is the cheese. so[1] is the second letter of the cheese.
When i is 3, there is no third component to the sandwich. so so is nil. so[2] is asking for the nil[2].
nil is a class, like everything in ruby. But nil does not implement the [] method, as arrays and other classes that implement the Enumerable module do.
Since nil does not support the [] method, then you get the undefined method error.
Even operations that are built into other languages, like +, [], and == are methods that can be overridden in Ruby.
To understand exactly what's happening, try this bit of code:
class NilClass
def [] (i)
nil
end
end
Executing that will open up the existing NilClass, and add a method called []. Then you can do nil[1] and it will return nil. Changing an existing class like this is known as monkey patching in the Ruby world.
When you ask for so[2] you are actually asking for the third element, and if it doesn't exist, you'll get an error.
I recommend structuring your blocks like so:
s.each do |pair|
puts pair
end
Note the do/end instead of {} and the placement of the iteration variable inline with the each. Also note that this is equivalent to your "working code" so you don't need the extra iterator in this case.

Ruby - Converting Strings to CamelCase

I'm working on an exercise from Codewars. The exercise is to convert a string into camel case. For example, if I had
the-stealth-warrior
I need to convert it to
theStealthWarrior
Here is my code
def to_camel_case(str)
words = str.split('-')
a = words.first
b = words[1..words.length - 1].map{|x|x.capitalize}
new_array = []
new_array << a << b
return new_array.flatten.join('')
end
I just tested it out in IRB and it works, but in codewars, it won't let me pass. I get this error message.
NoMethodError: undefined method 'map' for nil:NilClass
I don't understand this, my method was definitely right.
You need to think about edge cases. In particular in this situation if the input was an empty string, then words would be [], and so words[1..words.length - 1] would be nil.
Codewars is probably testing your code with a range of inputs, including the emplty string (and likely other odd cases).
The following works in Ruby 2 for me, even if there's only one word in the input string:
def to_camel_case(str)
str.split('-').map.with_index{|x,i| i > 0 ? x.capitalize : x}.join
end
to_camel_case("the-stealth-warrior") # => "theStealthWarrior"
to_camel_case("warrior") # => "warrior"
I know .with_index exists in 1.9.3, but I can't promise it will work with earlier versions of Ruby.
A simpler way to change the text is :
irb(main):022:0> 'the-stealth-warrior'.gsub('-', '_').camelize
=> "TheStealthWarrior"
This should do the trick:
str.gsub("_","-").split('-').map.with_index{|x,i| i > 0 ? x.capitalize : x}.join
It accounts for words with underscores
Maybe there's a test case with only one word?
In that case, you'd be trying to do a map on words[1..0], which is nil.
Add logic to handle that case and you should be fine.

Couldn't understand the difference between `puts{}.class` and `puts({}.class)`

As the anonymous block and hash block looks like approximately same. I was doing kind of playing with it. And doing do I reached to some serious observations as below:
{}.class
#=> Hash
Okay,It's cool. empty block is considered as Hash.
print{}.class
#=> NilClass
puts {}.class
#=> NilClass
Now why the above code showing the same as NilClass,but the below code shows the Hash again ?
puts ({}.class)
#Hash
#=> nil
print({}.class)
#Hash=> nil
Could anyone help me here to understand that what's going one above?
I completely disagree with the point of #Lindydancer
How would you explain the below lines:
print {}.class
#NilClass
print [].class
#Array=> nil
print (1..2).class
#Range=> nil
Why not the same with the below print [].class and print (1..2).class?
EDIT
When ambiguity happens with local variable and method call, Ruby throws an error about the fact as below :
name
#NameError: undefined local variable or method `name' for main:Object
# from (irb):1
# from C:/Ruby193/bin/irb:12:in `<main>'
Now not the same happens with {} (as there is also an ambiguity between empty code block or Hash block). As IRB also here not sure if it's a empty block or Hash. Then why the error didn't throw up when IRB encountered print {}.class or {}.class?
The precedence rules of ruby makes print{}.class interpreted as (print{}).class. As print apparently returns a nil the class method returns #NilClass.
EDIT: As been discussed on other answers and in the updates to the question, print{} it of course interpreted as calling print with a block, not a hash. However, this is still about precedence as {} binds stronger than [] and (1..2) (and stronger than do ... end for that matter).
{} in this case is recognized as block passed to print, while [] unambiguously means empty array.
print {}.class # => NilClass
print do;end.class # => NilClass
You are running into some nuances of Ruby, where characters mean different things depending on context. How the source code is interpreted follows rules, one of which is that {} is a closure block if it follows a method call, and otherwise a Hash constructor.
It's common throughout the language to see characters mean different things depending on context or position within the statement.
Examples:
Parens () used for method call or for precedence
print(1..5).class => NilClass
print (1..5).class => Range <returns nil>
Square brackets [] used to call :[] method or for Array
print[].class => NoMethodError: undefined method `[]' for nil:NilClass
print([].class) => Array <returns nil>
Asterisk * used for multiplication or splatting
1 * 5 => 5
[*1..5] => [1, 2, 3, 4, 5]
Ampersand & used for symbol -> proc or logical and
0 & 1 => 0
[1, 2, 3].map(&:to_s) => ["1", "2", "3"]
Or in your case, braces used for block closures or for a hash
... hope it makes sense now ...

TypeError: can't convert String into Integer

I have code:
class Scene
def initialize(number)
#number = number
end
attr_reader :number
end
scenes = [Scene.new("one"), Scene.new("one"), Scene.new("two"), Scene.new("one")]
groups = scenes.inject({}) do |new_hash, scene|
new_hash[scene.number] = [] if new_hash[scene.number].nil?
new_hash[scene.number] << scene
end
When I'm lauching it I get error:
freq.rb:11:in `[]': can't convert String into Integer (TypeError)
from freq.rb:11:in `block in <main>'
from freq.rb:10:in `each'
from freq.rb:10:in `inject'
from freq.rb:10:in `<main>'
If I change scenes to:
scenes = [Scene.new(1), Scene.new(1), Scene.new(2), Scene.new(1)]
the problem dissapear.
Why I get error message in the first case? Why Ruby decide to convert scene.number from String to Integer?
And one additional question about the 'inject' method. When Ruby initialize the 'new_hash' variable and how can Ruby know the type of this variable?
try:
groups = scenes.inject({}) do |new_hash, scene|
new_hash[scene.number] = [] if new_hash[scene.number].nil?
new_hash[scene.number] << scene
new_hash
end
Ruby takes the empty hash passed into inject() and sets new_hash to that. When the block ends the return value gets used to initialize new_hash the next time through, i.e., new_hash keeps accumulating the result of the block.
In your original code you were not returning the hash but an array (new_hash[scene.number] is an array) and the next loop through Ruby complained because new_hash[scene.number] was trying to do a lookup into the array with a string value, hence the error you got.
Z.E.D.'s right. See Jay Fields' Thoughts: Ruby: inject for a good explanation of inject by example.
As presented, your block returns an array. So the new_hash in |new_hash, scene| ends up being that array. When Ruby tries to find the array index 'one', it throws the error because 'one' is a String, not an Integer.
All you need to do is return new_hash as Z.E.D. showed, and you'll get something like this:
{
"two" => [
#<Scene:0x101836470 #number="two">
],
"one" => [
#<Scene:0x101836510 #number="one">,
#<Scene:0x1018364c0 #number="one">,
#<Scene:0x101836420 #number="one">
]
}
Why not use group_by which is probably exactly what you try to accomblish?
groups = scenes.group_by(&:number)
# => {"two"=>[#<Scene:0xb728ade0 #number="two">],
# "one"=>
# [#<Scene:0xb728ae30 #number="one">,
# #<Scene:0xb728ae08 #number="one">,
# #<Scene:0xb728ada4 #number="one">]}
inject is a folding operation and not exactly what you want. At least it's cumbersome to use in this way. merge with a block would probably be appropriate if you want to apply some algorithm during merging or grouping.
Also, to explain 'how can Ruby know the type of this variable' and why it tries to 'convert String into Integer' you might want to revise: Ruby variables and dynamic typing.
I know an answer is accepted for this question, but I can't help but post my answer.
groups = scenes.inject({}) { |nh, s| nh.tap {|h| (h[s.number] ||= []) << s } }

Resources