Array select with multiple conditions ruby - ruby

I can do:
#items = #items.select {|i| i.color == 'blue'}
#items = #items.select {|i| i.color == 'blue' || i.color == 'red'}
What if I am given an unknown amount of colors and I want to select them all? i.e.
['red','blue','green','purple']
# or
['blue','red']
I've been working on a mess of code that creates several temporary arrays and then merges or flattens them into one, but I'm really unhappy with it.

Try this:
colors = ['red','blue','green','purple']
#items = #items.select { |i| colors.include?(i.color) }
You might also want to consider this instead, for in-place changes:
#items.reject! { |i| !colors.include?(i.color) }

not sure I fully understand your question but would work for you?
colors_array = ['blue','red','whatever']
#items = #items.select {|i| colors_array.include?(i)}

Related

Selecting data from structured data, but the elegant functional way

Apparently, my ability to think functional withered over time. I have problems to select a sub-dataset from a dataset. I can solve the problem the hacky imperative style, but I believe, there is a sweet functional solution, which I am unfortunately not able to find.
Consider this data structure (tried to not simplify it beyond usability):
class C
attr_reader :attrC
def initialize(base)
#attrC = { "c1" => base+10 , "c2" => base+20, "c3" => base+30}
end
end
class B
attr_reader :attrB
##counter = 0
def initialize
#attrB = Hash.new
#attrB["b#{##counter}"] = C.new(##counter)
##counter += 1
end
end
class A
attr_reader :attrA
def initialize
#attrA = { "a1" => B.new, "a2" => B.new, "a3" => B.new}
end
end
which is created as a = A.new. The complete data set then would be
#<A: #attrA={"a1"=>#<B: #attrB={"b0"=>#<C: #attrC={"c1"=>10, "c2"=>20, "c3"=>30}>}>,
"a2"=>#<B: #attrB={"b1"=>#<C: #attrC={"c1"=>11, "c2"=>21, "c3"=>31}>}>,
"a3"=>#<B: #attrB={"b2"=>#<C: #attrC={"c1"=>12, "c2"=>22, "c3"=>32}>}>}>
which is subject to a selection. I want to retrieve only those instances of B where attrB's key is "b2".
My hacky way would is:
result = Array.new
A.new.attrA.each do |_,va|
result << va.attrB.select { |kb,_| kb == "b2" }
end
p result.reject { |a| a.empty?} [0]
which results in exactly what I intended:
{"b2"=>#<C: #attrC={"c1"=>12, "c2"=>22, "c3"=>32}>}
but I believe there would be a one-liner using map, fold, zip and reduce.
If you want a one-liner:
a.attrA.values.select { |b| b.attrB.keys == %w(b2) }
This returns instances of B. In your question, you're getting attrB values rather than instances of B. If that's what you want, there's this ugly reduce:
a.attrA.values.reduce([]) { |memo, b| memo << b.attrB if b.attrB.keys == %w(b2) ; memo }
I'm not sure what you're trying to do here, though?

Ruby: Pair individual values of two arrays into third array

I have two arrays: ['x','y','z'] and [1,2]. How would I go about creating pairs of values (as a string) in a third array?
So I would end up with this:
['x:1', 'x:2', 'y:1', 'y:2', 'z:1', 'z:2']
Thanks for any help!
You can use the product method to create the pairs and then join them:
a1 = ['x','y','z']
a2 = [1,2]
a1.product(a2).map {|p| p.join(':') }
Here's a way that's short and efficient, and reads well.
a1 = ['x','y','z']
a2 = [1,2]
a1.flat_map { |e| a2.map { |f| "#{e}:#{f}" } }
#=> ['x:1', 'x:2', 'y:1', 'y:2', 'z:1', 'z:2']
I originally had a2.map { |f| e.to_s+?:+f.to_s }. I replaced flat_map with map and e.to_s+?:+f.to_s with "#{e}:#{f}", as suggested by #PhilRoss and #Stefan, respectively. Thanks to both of you.
My suggestion would be to split the problem into two separate problems. In this case, the following:
iterate through each element on both arrays at once. pushing in the values as we go.
place the string into a branch new array.
While messy, I would perhaps do something along this path:
def foo
#declare our known values as arrays, and initialize the container of the final result.
combined_array = []
array_letters = ['x', 'y', 'z']
array_numbers = ['1', '2']
array_letters.each do |letter|
array_numbers.each do |number|
combined_array << "#{letter}:#{number}"
end
end
#return our new array
combined_array
end
Now mind you, I am sure there is a better way to do this. But in consideration, I am fairly certain this should work.

Comparing two arrays ignoring element order in Ruby

I need to check whether two arrays contain the same data in any order.
Using the imaginary compare method, I would like to do:
arr1 = [1,2,3,5,4]
arr2 = [3,4,2,1,5]
arr3 = [3,4,2,1,5,5]
arr1.compare(arr2) #true
arr1.compare(arr3) #false
I used arr1.sort == arr2.sort, which appears to work, but is there a better way of doing this?
The easiest way is to use intersections:
#array1 = [1,2,3,4,5]
#array2 = [2,3,4,5,1]
So the statement
#array2 & #array1 == #array2
Will be true. This is the best solution if you want to check whether array1 contains array2 or the opposite (that is different). You're also not fiddling with your arrays or changing the order of the items.
You can also compare the length of both arrays if you want them to be identical in size:
#array1.size == #array2.size && #array1 & #array2 == #array1
It's also the fastest way to do it (correct me if I'm wrong)
Sorting the arrays prior to comparing them is O(n log n). Moreover, as Victor points out, you'll run into trouble if the array contains non-sortable objects. It's faster to compare histograms, O(n).
You'll find Enumerable#frequency in Facets, but implement it yourself, which is pretty straightforward, if you prefer to avoid adding more dependencies:
require 'facets'
[1, 2, 1].frequency == [2, 1, 1].frequency
#=> true
If you know that there are no repetitions in any of the arrays (i.e., all the elements are unique or you don't care), using sets is straight forward and readable:
Set.new(array1) == Set.new(array2)
You can actually implement this #compare method by monkey patching the Array class like this:
class Array
def compare(other)
sort == other.sort
end
end
Keep in mind that monkey patching is rarely considered a good practice and you should be cautious when using it.
There's probably is a better way to do this, but that's what came to mind. Hope it helps!
The most elegant way I have found:
arr1 = [1,2,3,5,4]
arr2 = [3,4,2,1,5]
arr3 = [3,4,2,1,5,5]
(arr1 - arr2).empty?
=> true
(arr3 - arr2).empty?
=> false
You can open array class and define a method like this.
class Array
def compare(comparate)
to_set == comparate.to_set
end
end
arr1.compare(arr2)
irb => true
OR use simply
arr1.to_set == arr2.to_set
irb => true
Here is a version that will work on unsortable arrays
class Array
def unordered_hash
unless #_compare_o && #_compare_o == hash
p = Hash.new(0)
each{ |v| p[v] += 1 }
#_compare_p = p.hash
#_compare_o = hash
end
#_compare_p
end
def compare(b)
unordered_hash == b.unordered_hash
end
end
a = [ 1, 2, 3, 2, nil ]
b = [ nil, 2, 1, 3, 2 ]
puts a.compare(b)
Use difference method if length of arrays are the same
https://ruby-doc.org/core-2.7.0/Array.html#method-i-difference
arr1 = [1,2,3]
arr2 = [1,2,4]
arr1.difference(arr2) # => [3]
arr2.difference(arr1) # => [4]
# to check that arrays are equal:
arr2.difference(arr1).empty?
Otherwise you could use
# to check that arrays are equal:
arr1.sort == arr2.sort

difficulty modifying two dimensional ruby array

Excuse the newbie question. I'm trying to create a two dimensional array in ruby, and initialise all its values to 1. My code is creating the two dimensional array just fine, but fails to modify any of its values.
Can anyone explain what I'm doing wrong?
def mda(width,height)
#make a two dimensional array
a = Array.new(width)
a.map! { Array.new(height) }
#init all its values to 1
a.each do |row|
row.each do |column|
column = 1
end
end
return a
end
It the line row.each do |column| the variable column is the copy of the value in row. You can't edit its value in such way. You must do:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.each do |row|
row.map!{1}
end
return a
end
Or better:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
a = Array.new(width){ Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
Array.new(width) { Array.new(height){1} }
end
each passes into the block parameter the value of each element, not the element itself, so column = 1 doesn't actually modify the array.
You can do this in one step, though - see the API docs for details on the various forms of Array#new. Try a = Array.new(width) {|i| Array.new(height) {|j| 1 } }
you can create it like this?
a=Array.new(width) { Array.new(height,1) }
column in your nested each loop is a copy of the value at that place in the array, not a pointer/reference to it, so when you change its value you're only changing the value of the copy (which ceases to exist outside the block).
If you just want a two-dimensional array populated with 1s something as simple as this will work:
def mda(width,height)
[ [1] * width ] * height
end
Pretty simple.
By the way, if you want to know how to modify the elements of a two-dimensional array as you're iterating over it, here's one way (starting from line 6 in your code):
#init all its values to 1
a.length.times do |i|
a[i].length.times do |j|
a[i][j] = 1
end
end

In Ruby, is there an Array method that combines 'select' and 'map'?

I have a Ruby array containing some string values. I need to:
Find all elements that match some predicate
Run the matching elements through a transformation
Return the results as an array
Right now my solution looks like this:
def example
matchingLines = #lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Is there an Array or Enumerable method that combines select and map into a single logical statement?
I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the nils.
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}
=> [3, 3, 3, nil, nil, nil]
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]
Ruby 2.7+
There is now!
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Here's a good read on the subject.
Hope that's useful to someone!
You can use reduce for this, which requires only one pass:
[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3]
In other words, initialize the state to be what you want (in our case, an empty list to fill: []), then always make sure to return this value with modifications for each element in the original list (in our case, the modified element pushed to the list).
This is the most efficient since it only loops over the list with one pass (map + select or compact requires two passes).
In your case:
def example
results = #lines.reduce([]) do |lines, line|
lines.push( ...(line) ) if ...
lines
end
return results.uniq.sort
end
Another different way of approaching this is using the new (relative to this question) Enumerator::Lazy:
def example
#lines.lazy
.select { |line| line.property == requirement }
.map { |line| transforming_method(line) }
.uniq
.sort
end
The .lazy method returns a lazy enumerator. Calling .select or .map on a lazy enumerator returns another lazy enumerator. Only once you call .uniq does it actually force the enumerator and return an array. So what effectively happens is your .select and .map calls are combined into one - you only iterate over #lines once to do both .select and .map.
My instinct is that Adam's reduce method will be a little faster, but I think this is far more readable.
The primary consequence of this is that no intermediate array objects are created for each subsequent method call. In a normal #lines.select.map situation, select returns an array which is then modified by map, again returning an array. By comparison, the lazy evaluation only creates an array once. This is useful when your initial collection object is large. It also empowers you to work with infinite enumerators - e.g. random_number_generator.lazy.select(&:odd?).take(10).
If you have a select that can use the case operator (===), grep is a good alternative:
p [1,2,'not_a_number',3].grep(Integer){|x| -x } #=> [-1, -2, -3]
p ['1','2','not_a_number','3'].grep(/\D/, &:upcase) #=> ["NOT_A_NUMBER"]
If we need more complex logic we can create lambdas:
my_favourite_numbers = [1,4,6]
is_a_favourite_number = -> x { my_favourite_numbers.include? x }
make_awesome = -> x { "***#{x}***" }
my_data = [1,2,3,4]
p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"]
I'm not sure there is one. The Enumerable module, which adds select and map, doesn't show one.
You'd be required to pass in two blocks to the select_and_transform method, which would be a bit unintuitive IMHO.
Obviously, you could just chain them together, which is more readable:
transformed_list = lines.select{|line| ...}.map{|line| ... }
Simple Answer:
If you have n records, and you want to select and map based on condition then
records.map { |record| record.attribute if condition }.compact
Here, attribute is whatever you want from the record and condition you can put any check.
compact is to flush the unnecessary nil's which came out of that if condition
No, but you can do it like this:
lines.map { |line| do_some_action if check_some_property }.reject(&:nil?)
Or even better:
lines.inject([]) { |all, line| all << line if check_some_property; all }
I think that this way is more readable, because splits the filter conditions and mapped value while remaining clear that the actions are connected:
results = #lines.select { |line|
line.should_include?
}.map do |line|
line.value_to_map
end
And, in your specific case, eliminate the result variable all together:
def example
#lines.select { |line|
line.should_include?
}.map { |line|
line.value_to_map
}.uniq.sort
end
def example
#lines.select {|line| ... }.map {|line| ... }.uniq.sort
end
In Ruby 1.9 and 1.8.7, you can also chain and wrap iterators by simply not passing a block to them:
enum.select.map {|bla| ... }
But it's not really possible in this case, since the types of the block return values of select and map don't match up. It makes more sense for something like this:
enum.inject.with_index {|(acc, el), idx| ... }
AFAICS, the best you can do is the first example.
Here's a small example:
%w[a b 1 2 c d].map.select {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["a", "b", "c", "d"]
%w[a b 1 2 c d].select.map {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["A", "B", false, false, "C", "D"]
But what you really want is ["A", "B", "C", "D"].
You should try using my library Rearmed Ruby in which I have added the method Enumerable#select_map. Heres an example:
items = [{version: "1.1"}, {version: nil}, {version: false}]
items.select_map{|x| x[:version]} #=> [{version: "1.1"}]
# or without enumerable monkey patch
Rearmed.select_map(items){|x| x[:version]}
If you want to not create two different arrays, you can use compact! but be careful about it.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}
new_array.compact!
Interestingly, compact! does an in place removal of nil. The return value of compact! is the same array if there were changes but nil if there were no nils.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! }
Would be a one liner.
Your version:
def example
matchingLines = #lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
My version:
def example
results = {}
#lines.each{ |line| results[line] = true if ... }
return results.keys.sort
end
This will do 1 iteration (except the sort), and has the added bonus of keeping uniqueness (if you don't care about uniq, then just make results an array and results.push(line) if ...
Here is a example. It is not the same as your problem, but may be what you want, or can give a clue to your solution:
def example
lines.each do |x|
new_value = do_transform(x)
if new_value == some_thing
return new_value # here jump out example method directly.
else
next # continue next iterate.
end
end
end

Resources