0

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.

macsplean
  • 611
  • 3
  • 8
  • 22
  • 2
    I'm not sure where are you heading here. What is this `/die/` pattern and how is it related to array of integers? – Sergio Tulentsev Oct 28 '13 at 08:16
  • @SergioTulentsev when I call the method, I enter an argument for `dice` that is an array of integers. When I run an each loop on the `dice` array, I name each array item a `die`. Also, this is a quite succinct example, in my opinion. The code snippet is very short, and most of the rest of the code I include is just copy-paste of the error messages I received. – macsplean Oct 28 '13 at 08:27
  • Well, it maybe succinct, but it's not executable. A good SSCCE includes several examples of input along with expected output. – Sergio Tulentsev Oct 28 '13 at 08:29

6 Answers6

2

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}]
ncwrch
  • 571
  • 4
  • 6
  • hmm.... any idea why no matches are being produced in that case? I thought /(die)/ would functionally be equivalent to /5/ when I call score([5]) – macsplean Oct 28 '13 at 08:19
  • 3
    No `/(die)/` actually matches `"die"`, for interpolation something like `/#{die}/` would work, which would translate to `/(5)/`. Furthermore `match` returns only the first match and length equals to the length of the match, so in this case always `1`... – lwe Oct 28 '13 at 08:34
  • @lwe thank you. This is the kind of info I was hoping for. If match returns only the first match, how I can get an array that gets all matches? Is there another method that I simply replace match with? – macsplean Oct 28 '13 at 08:42
  • 1
    @macsplean, have a look at this answer http://stackoverflow.com/questions/80357/match-all-occurrences-of-a-regex – ncwrch Oct 28 '13 at 08:53
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
Amit Thawait
  • 4,862
  • 2
  • 31
  • 25
  • thanks a ton. Do you have any idea why I was getting those errors, though? – macsplean Oct 28 '13 at 08:16
  • The reason is you were doing `to_s` to each element beacuse of which it doesn't matches. When you match with regex then if the match is found then its index is returned otherwise `nil` that's why you were getting error as `undefined method `length' for nil:NilClass` – Amit Thawait Oct 28 '13 at 08:21
  • Foe example `[1,2,3,4] =~ /'1'/ ` will return `nil` – Amit Thawait Oct 28 '13 at 08:22
  • For second error : You were trying to match 1 element against all the elements like `x = /(die)/.match(dice).length` .This is similar to `/1/.match([1,2,3,4])` which itself is wrong – Amit Thawait Oct 28 '13 at 08:26
1

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.

Stefan
  • 109,145
  • 14
  • 143
  • 218
1

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}
hirolau
  • 13,451
  • 8
  • 35
  • 47
0

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
Bala
  • 11,068
  • 19
  • 67
  • 120
0

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
jcm
  • 1,781
  • 1
  • 15
  • 27