Subtracting one Array from another in Ruby - ruby

I've got two arrays of Tasks - created and assigned.
I want to remove all assigned tasks from the array of created tasks.
Here's my working, but messy, code:
#assigned_tasks = #user.assigned_tasks
#created_tasks = #user.created_tasks
#Do not show created tasks assigned to self
#created_not_doing_tasks = Array.new
#created_tasks.each do |task|
unless #assigned_tasks.include?(task)
#created_not_doing_tasks << task
end
end
I'm sure there's a better way. What is it?
Thanks :-)

You can subtract arrays in Ruby:
[1,2,3,4,5] - [1,3,4] #=> [2,5]
ary - other_ary → new_ary Array Difference
Returns a new array that is a copy of the original array, removing any
items that also appear in other_ary. The order is preserved from the
original array.
It compares elements using their hash and eql? methods for efficiency.
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
If you need
set-like behavior, see the library class Set.
See the Array documentation.

The above solution
a - b
deletes all instances of elements in array b from array a.
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
In some cases, you want the result to be [1, 2, 3, 3, 5]. That is, you don't want to delete all duplicates, but only the elements individually.
You could achieve this by
class Array
def delete_elements_in(ary)
ary.each do |x|
if index = index(x)
delete_at(index)
end
end
end
end
test
irb(main):198:0> a = [ 1, 1, 2, 2, 3, 3, 4, 5 ]
=> [1, 1, 2, 2, 3, 3, 4, 5]
irb(main):199:0> b = [ 1, 2, 4 ]
=> [1, 2, 4]
irb(main):200:0> a.delete_elements_in(b)
=> [1, 2, 4]
irb(main):201:0> a
=> [1, 2, 3, 3, 5]
The code works even when the two arrays are not sorted. In the example, the arrays are sorted, but this is not required.

Related

Delete one smallest element in an array while preserving order in Ruby

Simple question, but somehow I can't think of a solution. How can I delete a single smallest element in an array of random integers?
a = [7, 5, 3, 2, 1, 4]
b = [2, 2, 1, 1, 2]
This is what I come up with:
def remove_it(num)
num.delete(num.sort[0])
end
Code works with a, but not b. It deletes both 1's in b. I only need to delete one 1.
How can I delete one smallest number in an array and keep the order?
Easy-peasy. Use .delete_at + .index:
def remove_it(num)
num.delete_at(num.index(num.min))
num
end
a = [7, 5, 3, 2, 1, 4]
b = [2, 2, 1, 1, 2]
remove_it(a) # => [7, 5, 3, 2, 4]
remove_it(b) # => [2, 2, 1, 2]

Conditionally inserting data into an array

Alright, can someone help me how to properly iterate a dynamic size array?
Here's what I mean:
my_array = [1, 2, 3, 4, 2, 5, 15, 2] # <= there are three 2 inside my_array
Then I would like to add "good" everytime the iteration hit integer 2, I tried several method but not find the way, here's the best method I've tried(still not resulting in what I want)
# First Method
for i in 0..(my_array.length - 1)
my_array.insert(i + 1, "good") if my_array[i] == 2
end
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2]
# Second Method
for i in 0..(my_array.length - 1)
my_array[i + 1] = "good" if my_array[i] == 2
end
p my_array # => [1, 2, "good", 4, 2, "good", 15, 2, "good"]
The first method is not good because it's not showing "good" after the last 2, I guess this because the iteration could not reach the last integer 2(in the last array) and that is expected because the array size is changed bigger everytime "good" is inserted.
The second one is also bad, because I replace the data after every 2 with "good" string.
Now can someone point it out to me how can I doing this properly so I can produce it like this:
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
All "good" is added without replacing any data.
Any help is appreciated, thank you very much.
You'd have a better time transforming this into a new array than modifying in-place:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
def add_good(a)
a.flat_map do |value|
case (value)
when 2
[ 2, 'good' ]
else
value
end
end
end
puts add_good(my_array).inspect
# => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
The flat_map method is useful for situations where you want to create zero or more entries in the resulting array. map is a 1:1 mapping, flat_map is a 1:N.
It's also possible to make this much more generic:
def fancy_insert(a, insert)
a.flat_map do |value|
if (yield(value))
[ value, insert ]
else
value
end
end
end
result = fancy_insert(my_array, 'good') do |value|
value == 2
end
puts result.inspect
That way you can pass in an arbitrary block and value to be inserted.
Why not using the .each_with_index method:
arr = [1, 2, 3, 4, 5, 2, 3, 5]
arr.each_with_index {|e, i| arr.insert(i+1, "good") if e == 2}
Fast and furious.
Here's another way you can do it, using an Enumerator:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
enum = my_array.each
#=> #<Enumerator: [1, 2, 3, 4, 2, 5, 15, 2]:each>
my_array = []
loop do
x = enum.next
my_array << x
my_array << "good" if x == 2
end
my_array
#=> [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
Enumerator#next raises a StopInteration exception when the enumerator is already on the last element. Kernel#loop handles the exception by breaking the loop. That's why you will often see loop used when stepping through an enumerator.

Returning Multiple Values From Map

Is there a way to do:
a = b.map{ |e| #return multiple elements to be added to a }
Where rather than returning a single object for each iteration to be added to a, multiple objects can be returned.
I'm currently achieving this with:
a = []
b.map{ |e| a.concat([x,y,z]) }
Is there a way to this in a single line without having to declare a = [] up front?
Use Enumerable#flat_map
b = [0, 3, 6]
a = b.flat_map { |x| [x, x+1, x+2] }
a # => [0, 1, 2, 3, 4, 5, 6, 7, 8]
Use Enumerable#flat_map
Which is probably not much different than:
p [1, 2, 3].map{|num| [1, 2, 3]}.flatten
--output:-
[1, 2, 3, 1, 2, 3, 1, 2, 3]

delete ONE array element by value in ruby

How can I delete the first element by a value in an array?
arr = [ 1, 1, 2, 2, 3, 3, 4, 5 ]
#something like:
arr.delete_first(3)
#I would like a result like => [ 1, 1, 2, 2, 3, 4, 5]
Thanks in advance
Pass the result of Array#find_index into Array#delete_at:
>> arr.delete_at(arr.find_index(3))
>> arr
=> [1, 1, 2, 2, 3, 4, 5]
find_index() will return the Array index of the first element that matches its argument. delete_at() deletes the element from an Array at the specified index.
To prevent delete_at() raising a TypeError if the index isn't found, you may use a && construct to assign the result of find_index() to a variable and use that variable in delete_at() if it isn't nil. The right side of && won't execute at all if the left side is false or nil.
>> (i = arr.find_index(3)) && arr.delete_at(i)
=> 3
>> (i = arr.find_index(6)) && arr.delete_at(i)
=> nil
>> arr
=> [1, 1, 2, 2, 3, 4, 5]
You can also use :- operator to remove desired element from the array, e.g.:
$> [1, 2, 3, '4', 'foo'] - ['foo']
$> [1, 2, 3, '4']
Hope this helps.

Sort an array of numbers based on a given order

I have two arrays. The first array contains the sort order. The second array contains an arbitrary number of elements.
I have the property that all elements (value-wise) from the second array are guaranteed to be in the first array, and I am only working with numbers.
A = [1,3,4,4,4,5,2,1,1,1,3,3]
Order = [3,1,2,4,5]
When I sort A, I would like the elements to appear in the order specified by Order:
[3, 3, 3, 1, 1, 1, 1, 2, 4, 4, 4, 5]
Note that duplicates are fair game. The elements in A should not be altered, only re-ordered. How can I do this?
>> source = [1,3,4,4,4,5,2,1,1,1,3,3]
=> [1, 3, 4, 4, 4, 5, 2, 1, 1, 1, 3, 3]
>> target = [3,1,2,4,5]
=> [3, 1, 2, 4, 5]
>> source.sort_by { |i| target.index(i) }
=> [3, 3, 3, 1, 1, 1, 1, 2, 4, 4, 4, 5]
If (and only if!) #Gareth's answer turns out to be too slow, instead go with:
# Pre-create a hash mapping value to index once only…
index = Hash[ Order.map.with_index.to_a ] #=> {3=>0,1=>1,2=>2,4=>3,5=>4}
# …and then sort using this constant-lookup-time
sorted = A.sort_by{ |o| index[o] }
Benchmarked:
require 'benchmark'
order = (1..50).to_a.shuffle
items = 1000.times.map{ order.sample }
index = Hash[ order.map.with_index.to_a ]
Benchmark.bmbm do |x|
N = 10_000
x.report("Array#index"){ N.times{
items.sort_by{ |n| order.index(n) }
}}
x.report("Premade Hash"){ N.times{
items.sort_by{ |n| index[n] }
}}
x.report("Hash on Demand"){ N.times{
index = Hash[ order.map.with_index.to_a ]
items.sort_by{ |n| index[n] }
}}
end
#=> user system total real
#=> Array#index 12.690000 0.010000 12.700000 ( 12.704664)
#=> Premade Hash 4.140000 0.000000 4.140000 ( 4.141629)
#=> Hash on Demand 4.320000 0.000000 4.320000 ( 4.323060)
Another possible solution without explicit sorting:
source = [1,3,4,4,4,5,2,1,1,1,3,3]
target = [3,1,2,4,5]
source.group_by(&lambda{ |x| x }).values_at(*target).flatten(1)

Resources