简体   繁体   中英

`map!': private method called for String (NoMethodError)

I tried to write a private function that will take in a single token and return the given token without white space or punctuation, for example:

normalize_token(" cat.")
# => "cat"

I then defined an array of tokens that have punctuation and white space:

["a.", "      b...."]

which I'd like to map over using my defined function. I expect a new object returned, which looks like:

["a", "b"]

Here is a small snippet of code:

private def normalize_token(token)
  token.gsub(/\W/, ' ') # does the same thing as [^A-Za-z0-9_]
end

["a.", "      b...."].map!(&:normalize_token)

I get:

map!': private method normalize_token' called for "a.":String (NoMethodError)

Any help or even an explanation of what I'm doing wrong would be greatly appreciated.

Instead of passing the method reference, pass a block:

private def normalize_token(token)
  token.gsub(/\W/, ' ') # does the same thing as [^A-Za-z0-9_]
end

["a.", "      b...."].map! {|token| normalize_token token}

This will leave white space in your strings, so you might want to chomp! them or change your gsub to gsub(/\\w/, '')

Explanation

The first problem is that normalize_token is private. You can get around that by making it public or by send ing it as @alex mentioned.

The second problem however is more subtle.

By passing the method reference, you essentially have the following

["a.", "      b...."].map! {|token| token.send :normalize_token }

Running this will result in the following error message:

ArgumentError (wrong number of arguments (given 0, expected 1))

map! will not pass the mapped value to normalize_token as an argument. (notice that the send has your mapped value as a receiver but not as an argument .) Instead, by using a block with a variable declared, we can get around this by passing the mapped value as an argument explicitly in the block.

Quick note on &: - it accomplishes the same thing as {|i| ...} {|i| ...} by using & to call to_proc on the :object, then passes it as a block to the method - so in order to use it, whatever follows &: needs to be defined on whatever object is being passed to map! .

Here's an example, cracking open the String class to define it there:

class String
  def normalize_token
    self.gsub(/\W/, ' ')
  end
end

Which will let you use your original ["a.", " b...."].map!(&:normalize_token) . Not a great idea to do stuff like this in real life, since you'd probably want normalize_token to be defined in whatever class is responsible for generating these strings. But it should get you started.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM