Passing a block variable to grep? - ruby

When I do:
["bar","quax"].grep(/bar/)
The output is:
=> ["bar"]
If I do:
["bar","quax"].grep(/fish/)
The output is:
=> []
I decided to take it further by attempting to pass a block to grep, but it did not work.
["foo", "bar", "baz", "quax"].each do |some_word|
["fish","jones"].grep(/some_word/)
end
The output is:
=> ["foo", "bar", "baz", "quax"]
I am curious why my extension doesn't work, as it seems fairly straightforward. Or, is it simply illegal to do?

each is iterating over your array but you aren't modifying it. So it's just looping a few times, calling grep which is effectively doing nothing (technically it's returning something, you're just not doing anything with it), then returning the original array.
You need to be doing something with the return value of grep - but it's not clear to me what that is supposed to be (I don't understand why you are using each at all?)
Also, some_word in this case is taken literally as "some_word". You should interpolate your regex like .grep(/#{some_word}/)

["foo", "bar", "baz", "quax"].each do |some_word|
["fish","jones"].grep(/some_word/)
end
isn't correct. some_word is the parameter to the block, but /some_word/ is a regular expression that matches the string 'some_word', whether that is the entire string or just a sub-string:
%w[some_word not_some_word_anymore].grep(/some_word/)
# => ["some_word", "not_some_word_anymore"]
If you want to use the variable/parameter some_word inside the regular expression, you have to substitute it in somehow. A simple way to do it is:
/#{ some_word }/
or:
Regexp.new( some_word )
For instance:
foo = 'some_word'
/#{ foo }/ # => /some_word/
Regexp.new(foo) # => /some_word/
The reason:
["foo", "bar", "baz", "quax"].each do |some_word|
end
returns the same array, is that is how each behaves. We don't care usually. If you want to transform that array, then use map:
["foo", "bar", "baz", "quax"].map { |some_word| some_word.size }
# => [3, 3, 3, 4]
If you're trying to reduce the array use something like grep or select or reject:
["foo", "bar", "baz", "quax"].reject { |some_word| some_word['oo'] }
# => ["bar", "baz", "quax"]
["foo", "bar", "baz", "quax"].select { |some_word| some_word['oo'] }
# => ["foo"]

Related

Initializing an array with identical elements: `==` operator says arrays are the same, but they behave differently. Why?

Here's my code:
a=["foo","foo","foo"]
b=["foo"]*3
a==b # => true
a.each{|i| i<<"bar"}
b.each{|i| i<<"bar"}
a==b # => false
I get what I expect for a:
["foobar", "foobar", "foobar"]
but for b, I get this:
["foobarbarbar", "foobarbarbar", "foobarbarbar"]
Why is this happening? Is it a bug?
Is there a way of filling an array with many identical strings that avoids this problem?
It's not a bug, it's just that the lines
a=["foo","foo","foo"]
b=["foo"]*3
Are not the same. The second is inserting the SAME OBJECT three times into the b array. In the a array, you have three different objects. In all cases, the objects are strings with the text "foo".
You can confirm this by examining the object ids.
a[0].object_id == a[1].object_id
=> false
b[0].object_id == b[1].object_id
=> true
So when you mutate the object with << "bar" you are mutating the same object three times (in the case of array b)
To populate an array with separate instances of the same string, do...
Array.new(3) { "foo" }
Like SteveTurczyn said, ["foo"]*3 create an array concatenating three times a copie of the same object ("foo").
If you what to aplied some function or do something on the elements of the array. Use the method map!
Like:
a = ["foo", "foo", "foo"]
=> ["foo", "foo", "foo"]
a.map! { |x| x + "bar" }
=> ["foobar", "foobar", "foobar"]
b = ["foo"]*3
=> ["foo", "foo", "foo"]
b.map! { |x| x + "bar" }
=> ["foobar", "foobar", "foobar"]

Take any hash keys and flatten into mixed array

I have a mixed array like: ["foo", "bar", {test: "stuff"}, "foobar", {just: "examples}]
I want to take any hashes, remove the key, and push the value into the array. Taking my example above, I want to return:
["foo", "bar", "stuff", "foobar", "examples"]
Simplest way to do that?
a = ["foo", "bar", {test: "stuff"}, "foobar", {just: "examples"}]
a.flat_map { |x| x.is_a?(Hash) ? x.values : x }
# => ["foo", "bar", "stuff", "foobar", "examples"]
Another way, without if (which, as #Matt pointed out, only works with the hashes have a single key, as in the example):
a = ["foo", "bar", {test: "stuff"}, "foobar", {just: "examples"}]
a.map { |e| [*e].flatten.last }
#=> ["foo", "bar", "stuff", "foobar", "examples"]

How to get index of value in anonymous array inside of iteration

I would like to be able to take an anonymous array, iterate through it and inside of the iterator block find out what the index is of the current element.
For instance, I am trying to output only every third element.
["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"].each do |elem|
if index_of(elem) % 3 == 0 then
puts elem
end
end
(where index_of is a nonexistent method being used as a placeholder here to demonstrate what I'm trying to do)
In theory the output should be:
foo
bang
Hello, Sailor!
This is pretty straightforward when I'm naming the array. But when it is anonymous, I can't very well refer to the array by name. I've tried using self.find_index(elem) as well as self.index(elem) but both fail with the error: NoMethodError: undefined method '(find_)index' for main:Object
What is the proper way to do this?
Use each_with_index:
arr = ["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"]
arr.each_with_index do |elem, index|
puts elem if index % 3 == 0
end
Another way:
arr = ["foo", "bar", "baz", "bang", "bamph", "foobar", "Hello, Sailor!"]
arr.each_slice(3) { |a| puts a.first }
#=> foo
# bang
# Hello, Sailor!

Is there a version of Ruby's Regexp.match that responds to the order of the matches within the string?

I want to use regexes to check if a given string is composed of certain substrings.
For example, given the regular expression
> regex = /(?:(foo)|(bar)|(baz))*/
I can determine whether a given string matches the pattern:
> regex === "bazbar"
=> true
> regex === "qux"
=> false
But I want to know how to break the string into substrings. I can almost do this with
> regex.match("barbazfoo").captures
=> ["foo", "bar", "baz"]
But here they appear in the order in which I specified them within the regex. I want to return
["bar", "baz", "foo"]
In the order in which they appeared in the string.
You can use String#scan with a modified regular expression:
regex = /foo|bar|baz/
"barbazfoo".scan(regex)
# => ["bar", "baz", "foo"]
UPDATE according to OP's comment.
If some of the strings I'm using are substrings of the others, you need to order the so that all the substrings go last.
"barfoo".scan(/ba|bar|foo/) # without ordering
# => ["ba", "foo"]
words = ['ba', 'bar', 'foo']
pattern = words.map { |word| Regexp.escape(word) }.sort_by { |x| -x.size }.join('|')
"barfoo".scan(Regexp.new(pattern))
# => ["bar", "foo"]

Dynamic menu with Mongoid in Sinatra

I'm building Sinatra/Mongoid app and I want to create a dynamic menu of array values ​​in documents from MongoDB. I guess the following algorithm:
Supose in Mongo stored some documents
{ "name": "doc1", "array": ["foo", "bar", "baz", "quux"] }
{ "name": "doc2", "array": ["foo", "baz"] }
{ "name": "doc3", "array": ["bar", "baz", "quux"] }
{ "name": "doc4", "array": ["quux"] }
{ "name": "doc5", "array": ["foo", "quux"] }
Now I guess it should
ask all docs for "array" field values,
then sort values in order of number of mentions,
delete duplicate values
and give me this new array to build %ul in my view, like so:
%ul
%li foo
%li bar
%li baz
%li quux
And I've got no idea how to implement this.
Thank you in advance for any help.
Suppose you already have an arrays variable with the "array" values of your documents:
arrays = [
["foo", "bar", "baz", "quux"],
["foo", "baz"],
["bar", "baz", "quux"],
["quux"],
["foo", "quux"]
]
In this case, I'd use a Hash to store the array value as key, and a counter a value. This allows to
eliminate double entries, and
create a handle for sorting.
The interesting bit is the inject method, which iterates over an enumerable and injects an accumolator (memo):
list = arrays.flatten.inject({}) do |memo, e|
memo[e] = (memo[e] || 0) + 1
memo
end
#=> [["foo", 3], ["bar", 2], ["baz", 3], ["quux", 4]]
This creates this list which then can be sorted:
list = list.sort{|a,b| a[1] <=> b[1] }
#=> [["bar", 2], ["foo", 3], ["baz", 3], ["quux", 4]]
and filtered:
list = list.map(&:first)
#=> ["bar", "foo", "baz", "quux"]
You then can iterate over that array in your view.
N.B. I usually try to compress the inject call a little more, like so
list = arrays.flatten.inject({}) do |memo, e|
(memo[e] ||= 0) += 1
memo
end
or
list = arrays.flatten.inject(Hash.new {|h,k| h[k]=0 }) do |memo, e|
memo[e] += 1
memo
end
… but for some reason, my Ruby currently thinks that's an syntax error. Maybe it's just a lack of coffee ;-)

Resources