Take any hash keys and flatten into mixed array - ruby

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"]

Related

How to convert a string into a nested hash

I have a string foo.bar.baz with a value 'qux' that I want to convert to a nested hash like so:
{
foo: {
bar: {
baz: 'qux'
}
}
}
How would I do this in a way that would allow for different string lengths? e.g. foo.bar or foo.bar.baz.qux
First of all, you have to split the string. You can then use inject to build the structure. Because the hash is built from the inside out, we have to reverse the initial array:
'foo.bar.baz'.split('.') #=> ["foo", "bar", "baz"]
.reverse #=> ["baz", "bar", "foo"]
.inject('qux') { |memo, key| { key.to_sym => memo } }
#=> {:foo=>{:bar=>{:baz=>"qux"}}}
It might not be obvious how this works. On the first invocation, memo is 'qux' and key is 'baz'. The block turns this into {:baz=>'qux'} which then becomes the new memo.
Step by step:
memo key result
--------------------------------------------------------------
'qux' 'baz' {:baz=>'qux'}
{:baz=>'qux'} 'bar' {:bar=>{:baz=>'qux'}}
{:bar=>{:baz=>'qux'}} 'foo' {:foo=>{:bar=>{:baz=>'qux'}}}

Cut array after key in Ruby

I need to cut array after key in Ruby, for example:
=> ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"] # Cut after "Baz"
=> ["Baz", "ooF", "raB", "zaB"] # result
It's possible to do? How can I do that?
Yes, just specify the range from index to -1(last element):
arr = ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"]
arr[arr.index("Baz")..-1] # => ["Baz", "ooF", "raB", "zaB"]
Here is one more way to do this:
a = ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"]
a.drop_while {|e| e != "Baz"}
#=> ["Baz", "ooF", "raB", "zaB"]
a.drop_while {|e| e != "Bazzzzzzz"}
#=> []
arr = ["Foo", "Bar", "Baz", "ooF", "raB", "zaB"]
arr.select { |s| s=='Baz'..nil ? 'Baz' : nil }
#=> ["Baz", "ooF", "raB", "zaB"]
Look odd? What's going on here?

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"]

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 ;-)

Passing a block variable to grep?

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"]

Resources