How to grep elements in array that match patterns from another array? - ruby

I have two arrays:
a = ["X2", "X3/X4", "X5/X6/X7", "X8/X9/X10/X11"]
b = ["X9/X10", "X3/X4"]
Now I need to select entries from 'a' array which regexp with any of entries from array 'b'.
Expected result is:
["X3/X4", "X8/X9/X10/X11"]
How can I do this in Ruby?

I'd do:
a.grep(Regexp.union(b))
# => ["X3/X4", "X8/X9/X10/X11"]

This should work:
a.grep(/#{b.join('|')}/)
# => ["X3/X4", "X8/X9/X10/X11"]

Try the below:
a = ["X2", "X3/X4", "X5/X6/X7", "X8/X9/X10/X11"]
b = ["X9/X10", "X3/X4"]
p a.select{|i| b.any?{|j| i.include? j }}
#>> ["X3/X4", "X8/X9/X10/X11"]

The safest way is to build a regular expression and then select elements of your array matching this expression:
Regexp.union("a", "b", "c")
# => /a|b|c/
Regexp.union(["a", "b", "c"])
# => /a|b|c/
("b".."e").to_a.grep(Regexp.union("a", "b", "c"))
# => ["b", "c"]

Related

How do I split a string without keeping the delimiter?

In Ruby, how do I split a string and not keep the delimiter in the resulting split array? I though tthis was the default, but when I try
2.4.0 :016 > str = "a b c"
=> "a b c"
2.4.0 :017 > str.split(/([[:space:]]|,)+/)
=> ["a", " ", "b", " ", "c"]
I see the spaces included in my result. I would like the result to simply be
["a", "b", "c"]
From the String#split documentation:
If pattern contains groups, the respective matches will be returned in the array as well.
Answering your explicitly stated question: do not match the group:
# ⇓⇓ HERE
str.split(/(?:[[:space:]]|,)+/)
or, even without groups:
str.split(/[[:space:],]+/)
or, in more Rubyish way:
'a b, c,d e'.split(/[\p{Space},]+/)
#⇒ ["a", "b", "c", "d", "e"]
String#splitsplits on white-space by default, so don 't bother with a regex:
"a b c".split # => ["a", "b", "c"]
Try this please
str.split(' ')

How to check if a key exists in an array of arrays?

Is there a straightforward way to do something like the following without excessive looping?
myArray = [["a","b"],["c","d"],["e","f"]]
if myArray.includes?("c")
...
I know this works fine if it's just a normal array of chars... but I would like something equally as elegant for an array of an array of chars (bonus points for helping convert this to an array of tuples).
If you only need a true/false answer you can flatten the array and call include on that:
>> myArray.flatten.include?("c")
=> true
You can use assoc:
my_array = [['a', 'b'], ['c', 'd'], ['e', 'f']]
if my_array.assoc('c')
# ...
It actually returns the whole subarray:
my_array.assoc('c') #=> ["c", "d"]
or nil if there is no match:
my_array.assoc('g') #=> nil
There's also rassoc to search for the second element:
my_array.rassoc('d') #=> ["c", "d"]
my_array = [["a","b"],["c","d"],["e","f"]]
p my_hash = my_array.to_h # => {"a"=>"b", "c"=>"d", "e"=>"f"}
p my_hash.key?("c") # => true
You can use Array#any?
myArray = [["a","b"],["c","d"],["e","f"]]
if myArray.any? { |x| x.includes?("c") }
# some code here
The find_index method works well for this:
myArray = [["a","b"],["c","d"],["e","f"]]
puts "found!" if myArray.find_index {|a| a[0] == "c" }
The return value is the array index of the pair or nil if the pair is not found.
You can capture the pair's value (or nil if not found) this way:
myArray.find_index {|a| a[0] == "c" } || [nil, nil])[1]
# => "d"

Set multiple keys to the same value at once for a Ruby hash

I'm trying to create this huge hash, where there are many keys but only a few values.
So far I have it like so...
du_factor = {
"A" => 1,
"B" => 1,
"C" => 1,
"D" => 2,
"E" => 2,
"F" => 2,
...etc., etc., etc., on and on and on for longer than you even want to know. What's a shorter and more elegant way of creating this hash without flipping its structure entirely?
Edit: Hey so, I realized there was a waaaay easier and more elegant way to do this than the answers given. Just declare an empty hash, then declare some arrays with the keys you want, then use a for statement to insert them into the array, like so:
du1 = ["A", "B", "C"]
du2 = ["D", "E", "F"]
dufactor = {}
for i in du1
dufactor[i] = 1
end
for i in du740
dufactor[i] = 2
end
...but the fact that nobody suggested that makes me, the extreme Ruby n00b, think that there must be a reason why I shouldn't do it this way. Performance issues?
Combining Ranges with a case block might be another option (depending on the problem you are trying to solve):
case foo
when ('A'..'C') then 1
when ('D'..'E') then 2
# ...
end
Especially if you focus on your source code's readability.
How about:
vals_to_keys = {
1 => [*'A'..'C'],
2 => [*'D'..'F'],
3 => [*'G'..'L'],
4 => ['dog', 'cat', 'pig'],
5 => [1,2,3,4]
}
vals_to_keys.each_with_object({}) { |(v,arr),h| arr.each { |k| h[k] = v } }
#=> {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2, "F"=>2, "G"=>3, "H"=>3, "I"=>3,
# "J"=>3, "K"=>3, "L"=>3, "dog"=>4, "cat"=>4, "pig"=>4, 1=>5, 2=>5, 3=>5, 4=>5}
What about something like this:
du_factor = Hash.new
["A", "B", "C"].each {|ltr| du_factor[ltr] = 1}
["D", "E", "F"].each {|ltr| du_factor[ltr] = 2}
# Result:
du_factor # => {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2, "F"=>2}
Create an empty hash, then for each group of keys that share a value, create an array literal containing the keys, and use the array's '.each' method to batch enter them into the hash. Basically the same thing you did above with for loops, but it gets it done in three lines.
keys = %w(A B C D E F)
values = [1, 1, 1, 2, 2, 2]
du_factor = Hash[*[keys, values].transpose.flatten]
If these will be more than 100, writing them down to a CSV file might be better.
keys = [%w(A B C), %w(D E F)]
values = [1,2]
values.map!.with_index{ |value, idx| Array(value) * keys[idx].size }.flatten!
keys.flatten!
du_factor = Hash[keys.zip(values)]
Notice here that I used destructive methods (methods ending with !). this is important for performance and memory usage optimization.

Ruby array of strings: split into smallest pieces

I would like to know if there is a method in Ruby that splits an Array of String in smallest pieces. Consider:
['Cheese crayon', 'horse', 'elephant a b c']
Is there a method that turns this into:
['Cheese', 'crayon', 'horse', 'elephant', 'a', 'b', 'c']
p ['Cheese crayon', 'horse', 'elephant a b c'].flat_map(&:split)
# => ["Cheese", "crayon", "horse", "elephant", "a", "b", "c"]
None that I know of. But you can split each string individually and then flatten the results into a single array:
p ['Cheese crayon', 'horse', 'elephant a b c'].map(&:split).flatten
You can do it this way:
array.map { |s| s.split(/\s+/) }.flatten
This splits your string by any number of whitespace characters. As far as I know, it's the default behavior of split without any arguments, so you can shorten it to:
array.map(&:split).flatten
['Cheese crayon', 'horse', 'elephant a b c'].join(' ').split
# => ["Cheese", "crayon", "horse", "elephant", "a", "b", "c"]

Why is this Ruby 1.9 code resulting in an empty hash?

I'm trying to zip 3 arrays into a hash. The hash is coming up empty, though. Here's sample code to reproduce using Ruby 1.9:
>> foo0 = ["a","b"]
=> ["a", "b"]
>> foo1 = ["c","d"]
=> ["c", "d"]
>> foo2 = ["e", "f"]
=> ["e", "f"]
>> h = Hash[foo0.zip(foo1, foo2)]
=> {}
I'd like to zip these and then do something like:
h.each_pair do |letter0, letter1, letter2|
# process letter0, letter1
end
It's not clear what you expect the output to be but the [] operator of the Hash class is intended to take an even number of arguments and return a new hash where each even numbered argument is the key for the corresponding odd numbered value.
For example, if you introduce foo3 = ["d"] and you want to get a hash like {"a"=>"b", "c"=>"d"} you could do the following:
>> Hash[*foo0.zip(foo1, foo2, foo3).flatten]
=> {"a"=>"b", "c"=>"d"}
Hash[] doesn't work quite like you're assuming. Instead, try this:
>> Hash[*foo0, *foo1, *foo2]
=> {"a"=>"b", "c"=>"d", "e"=>"f"}
or, my preferred approach:
>> Hash[*[foo0, foo1, foo2].flatten]
=> {"a"=>"b", "c"=>"d", "e"=>"f"}
Basically, Hash[] is expecting an even number of arguments as in Hash[key1, val1, ...]. The splat operator * is applying the arrays as arguments.
It looks like foo0.zip(foo1,foo2) generates:
[["a", "b", "c"]]
Which is not an acceptable input for Hash[]. You need to pass it a flat array.
you don't need Hash for what you are trying to accomplish, zip does it for you
foo0.zip(foo1, foo2) do |f0, f1, f2|
#process stuff here
end

Resources