ruby check if any item in array exists in another array - arrays

Currently I have this code:
if !(allowed_params & password_protected_params).empty?
Which means "if anything in allowed_params is also in password_protected_params". This code works, but I find it a bit obfuscated and not friendly to the next developer that looks at this code.
Is there another, more readable way to check if anything in one array exists within another array?
EDIT
The block in question is this:
if !(allowed_params & password_protected_params).empty?
result = #user.update_with_password(allowed_params)
else
#only update trivial attributes
result = #user.update_without_password(allowed_params)
end
In the end I just added a variable to make it more readable (but still open to better suggestions):
needs_password = !(allowed_params & password_protected_params).empty?
if needs_password
result = #user.update_with_password(allowed_params)
else
#only update trivial attributes
result = #user.update_without_password(allowed_params)
end

There is not official synonym for Array#&. You can refactor your code into another method, and add a comment:
def intersects?(a, b)
!(a & b).empty?
end
# ...
if intersects? allowed_params, password_protected_params
# ...
Otherwise, you may want to extend the Array class, and define a method or add an alias_method to it:
class Array
alias_method :intersection, :&
def intersects?(array)
!(self.intersection(array)).empty?
end
end
[1, 2, 3].intersects? [3, 4, 5] # true
Bear in mind that changing core-classes is not a good-practice.

One ruby way could be
foo = [1, 2]
=> [1, 2]
bar = [1, 3]
=> [1, 3]
baz = [5, 3]
=> [5, 3]
bar.any?{|element| foo.include?(element)}
=> true
baz.any?{|element| foo.include?(element)}
=> false

Related

Idiomatic way to receive an object or array of objects, transform them, and add to existing array

I have a method that can receive either a single object, or an array of those objects. (The YAML file I'm pulling from sometimes has foo: bar and sometimes a list of foo: [bar1, bar2].)
Each time this method is called, it should transform the value or values, and add them to an internal array.
I originally had this:
class YamlEater
def initialize
#foos = []
end
def add(o)
if o.is_a?(Array)
o.each{ self.add(_1) }
else
#foos << FooItem.new(o)
end
end
end
After I needed to use that pattern in many different methods, I changed it to a non-recursive, more terse version that's splat-heavy and perhaps only a golfer could love:
def add(o)
#foos.push(*[*o].map{ FooItem.new(_1) })
end
I'm thinking there's a more elegant way to accomplish the same goal, and hoping someone will share it.
I'd probably use Kernel#Array and Array#concat:
def add(o)
#foos.concat(Array(o).map { FooItem.new(_1) })
end
That nicely handles o.nil? as a bonus.
As I'm sure you know, you could eliminate one splat by using Array#concat:
[1,2].concat([*[4,5]].map { |e| 2*e })
#=> [1, 2, 8, 10]
[1,2].concat([*4].map { |e| 2*e })
#=> [1, 2, 8]
or Array#+:
[1,2] + [*[4,5]].map { |e| 2*e }
#=> [1, 2, 8, 10]
[1,2] + [*4].map { |e| 2*e }
#=> [1, 2, 8]

How to implement Ruby max_by to return all elements which have the maximum value?

Ruby max_by method finds the maximal element form an array. Sometimes the maximal elements are with multipicity, in this case max_by chooses only one of them, seemingly arbitrarily. When I need all of them, I use this approach currently, to find the maximal values in an array of arrays:
sorted=ary.sort_by{|a,b| b}.reverse
max_score=sorted.first[1]
t=sorted.take_while{|z| z[1]==max_score}
But how could I monkey-patch the Array class with a "maxes_by" method, which accept a block, similarly to max_by, and returns an array of the maximal values?
Without writing a new, optimized method that returns the expected output you can simply combine max_by and select:
maximum = array.max_by { |element| element[1] }
t = array.select { |element| element[1] == maximum[1] }
Another option might be to group all elements by the value in question (with group_by) and then just pick the list with the max value.
lists = array.group_by { |element| element[1] }
lists[lists.keys.maximum]
It's worth a mention that the task could be done in a single pass through the array, or more generally, in a single pass through any collection whose class includes Enumerable.
module Enumerable
def max_by_all
return each unless block_given?
last_yield = nil
each_with_object([]) do |e,a|
ye = yield(e)
case last_yield.nil? ? -1 : last_yield <=> ye
when -1
a.replace([e])
last_yield = ye
when 0
a << e
end
end
end
end
arr = [2, 4, 3, 4, 1, 2, 5, 3, 5, 1]
arr.max_by_all(&:itself)
#=> [5, 5]
arr = ["style", "assets", "misty", "assist", "corgi", "bossy", "bosses", "chess"]
arr.max_by_all { |s| s.count('s') }
#=> ["assets", "assist", "bosses"]
h = { a: 1, b: 3, c: 2, d: 3, e: 1 }
h.max_by_all(&:last)
#=> [[:b, 3], [:d, 3]]
arr = [1, 2, 3]
arr.max_by_all.map { |n| 2*n }
#=> [2, 4, 6]
In the last example max_by_all has no block and therefore returns an enumerator which merely enumerates the elements of self. This behaviour may seem pointless but I've provided for it (the line return each unless block_given?) to mimic the behaviour of Enumerable#max_by when no block is provided.
Using Monkey-Patch
class Array
def maxes_by
maximal = max_by { |x| yield(x) }
select { |x| yield(x) == yield(maximal) }
end
end
Usage
> ['house', 'car', 'mouse'].maxes_by { |x| x.length }
=> ['house', 'mouse']
But I don't recommend to monkey patch the Array class, this practice is dangerous and can potentially lead to undesirable effects on your system.
For our good, ruby language provides a nice feature to overcome this problem, the Refinements, which is a safe way for monkey patching on ruby.
To simplify, with the Refinements you can monkey patch the Array class and the changes will only be available inside the scope of the class that is using the refinement! :)
You can use the refinement inside the class you are working on and you are ready to go.
Using Refinements
module MaxesByRefinement
refine Array do
def maxes_by
maximal = max_by { |x| yield(x) }
select { |x| yield(x) == yield(maximal) }
end
end
end
class MyClass
using MaxesByRefinement
def test
a = %w(house car mouse)
a.maxes_by { |x| x.length } # maxes_by is available here!
end
end
Usage
> MyClass.new.test
=> ['house', 'mouse']

What's the cleanest way to construct a Ruby array using a while loop?

Ruby has lots of nice ways of iterating and directly returning that result. This mostly involve array methods. For example:
def ten_times_tables
(1..5).map { |i| i * 10 }
end
ten_times_tables # => [10, 20, 30, 40, 50]
However, I sometimes want to iterate using while and directly return the resulting array. For example, the contents of the array may depend on the expected final value or some accumulator, or even on conditions outside of our control.
A (contrived) example might look like:
def fibonacci_up_to(max_number)
sequence = [1, 1]
while sequence.last < max_number
sequence << sequence[-2..-1].reduce(:+)
end
sequence
end
fibonacci_up_to(5) # => [1, 1, 2, 3, 5]
To me, this sort of approach feels quite "un-Ruby". The fact that I construct, name, and later return an array feels like an anti-pattern. So far, the best I can come up with is using tap, but it still feels quite icky (and quite nested):
def fibonacci_up_to(max_number)
[1, 1].tap do |sequence|
while sequence.last < max_number
sequence << sequence[-2..-1].reduce(:+)
end
end
end
Does anyone else have any cleverer solutions to this sort of problem?
Something you might want to look into for situations like this (though maybe your contrived example fits this a lot better than your actual use case) is creating an Enumerator, so your contrived example becomes:
From the docs for initialize:
fib = Enumerator.new do |y|
a = b = 1
loop do
y << a
a, b = b, a + b
end
end
and then call it:
p fib.take_while { |elem| elem <= 5 }
#=> [1, 1, 2, 3, 5]
So, you create an enumerator which iterates all your values and then once you have that, you can iterate through it and collect the values you want for your array in any of the usual Ruby-ish ways
Similar to Simple Lime's Enumerator solution, you can write a method that wraps itself in an Enumerator:
def fibonacci_up_to(max_number)
return enum_for(__callee__, max_number) unless block_given?
a = b = 1
while a <= max_number
yield a
a, b = b, a + b
end
end
fibonacci_up_to(5).to_a # => [1, 1, 2, 3, 5]
This achieves the same result as returning an Enumerator instance from a method, but it looks a bit nicer and you can use the yield keyword instead of a yielder block variable. It also lets you do neat things like:
fibonacci_up_to(5) do |i|
# ..
end

arr.delete() vs arr.delete_at() in Ruby

I wrote the function below which accepts an array and returns a randomized version of it.
I've noticed that I sometimes end up with a nil element in randomizedArr when using list.delete(element) to remove an element from the array, but this does not happen when using list.delete_at(index) -- note that the latter is commented out in the below snippet. Am I missing something?
If there's a better way to do what I'm trying to achieve with this function then I would appreciate any suggestion. Thanks!
The array I'm passing to this function is a string array with ~2k elements. I'm passing in a clone of the original array so it doesn't become empty when the function is called. I'm using Ruby 2.1 on Windows 7.
def getRandomList(list)
randomizedArr = Array.new()
cnt = list.length
while (cnt >= 1) do
index = rand(cnt)
prod = list[index]
randomizedArr.push(prod)
list.delete(prod)
#list.delete_at(index)
cnt = cnt - 1
end
if randomizedArr.include?(nil)
puts "found nil element"
end
return randomizedArr
end #getRandomList()
I am not sure why you need to put all that logic when you can randomize the list by list.shuffle.
Refering to the Ruby documentation this is what I found to answer your question...
#To delete an element at a particular index:
arr = [2, 3, 4, 5] #I added this bit
arr.delete_at(2) #=> 4
arr #=> [2, 3, 5]
#To delete a particular element anywhere in an array, use delete:
arr = [1, 2, 2, 3]
arr.delete(2) #=> 2
arr #=> [1,3]
All of that can be found here https://ruby-doc.org/core-2.4.1/Array.html
arr.delete(2) will remove any instance of 2 in an array while delete_at(2) only removes the third value in the array.

Ruby algorithm solving

So I have a basic problem that needs to be solved. I need to take the following nested array [1, 2, ["str", 1], [[2]]] and return [1,2,"str",1,2]. Ultimately removing all nested arrays. The following is my incomplete solution:
test = [1, 2, ["str", 1], [[2]]]
def remove_array(array)
new_array = []
array.each do |item|
if item != Array
puts "you are here"
new_array << item
else
remove_array(item)
end
end
end
remove_array(test)
This is a classic recursion method. However, I cant seem to think of how to maintain new_array. Every time I pass the item that is an array to it, new_array doesn't maintain its state. I would also like to point out that I am aware of the built in method flatten as I am trying to solve the solution without said method. Any advice would be greatly appreciated.
def my_flatten(arr)
result = []
arr.each do |el|
if el.is_a?(Array)
result.concat(my_flatten(el))
else
result << el
end
end
result
end
You generally had the right idea, but you were never adding the inner array elements into your result.
def remove_array(array)
new_array = []
array.each do |e|
if e.is_a?(Array)
new_array += remove_array(e)
else
new_array << e
end
end
new_array
end
You can call flatten on you array.
a = [1, 2, ["str", 1], [[2]]]
=> [1, 2, ["str", 1], [[2]]]
a.flatten
=> [1, 2, "str", 1, 2]
In Ruby, there is a convenient built-in method called flatten
It will "flatten" your array so that all the inside arrays disappear, and it returns one array with all elements inside
Edit:
Sorry editing this. Forgot that you were using recursion for your solution. Since you're essentially creating a new array each recursion, I'm not exactly sure how I would fix your solution at this moment.
just use the flatten! :) keep it simple..
test = [1, 2, ["str", 1], [[2]]]
def remove_array(array)
array.flatten!
return array
end
remove_array(test)
2.2.1 :024 > remove_array(test)
=> [1, 2, "str", 1, 2]
I hope this helps everybody! Thanks

Resources