Complete a Ruby Array - arrays

My application needs to deal with arrays of fixed size. The problem is that sometimes elements are nil but nil is a forbidden value. I think an easy way is to replace nil values with an the closest non-nil value (right before or right after).
The nil values can be first, last or even multiples. Here are some examples of what I'm looking for:
[1,2,3,nil,5] => [1,2,3,3,5]
[nil,2,3,4,5] => [2,2,3,4,5]
[1,nil,nil,4,5] => [1,1,4,4,5]
I am sure there is an elegant way to do this. Can you help?

My first idea was something like this, now fixed for the general case of arbitrary sequences of nil...
t = nil
p = lambda do |e|
if e.nil?
e,t = t,e
else
t = e
end
e
end
r = a
while r.any? && (r.include? nil)
t = nil; r = r.map(&p)
t = nil; r = r.reverse.map(&p).reverse
end
But I kind of like this one better. (API is arrayObj.merge_all)
module Enumerable
def merge_nil
t = nil
map do |e|
if e.nil?
e,t = t,e
e
else
t = e
end
end
end
end
class Array
def merge_all
return self unless any?
t = self
t = t.merge_nil.reverse.merge_nil.reverse while t.include? nil
t
end
end

You don't really mention what you use the array for, but maybe replacing nil by 0 would make more sense, since it wouldn't influence the result if you want to take averages or something...
[1,2,3,nil,5].map { |el| el ? el : 0 }

It all depends on what you want to do with the data later. It may make sense for you to put in average values but if you have relatively small arrays and are down for a little fun you can go all Bayesian with something like the following:
require 'classifier'
$c = Classifier::Bayes.new
perm = [1, 2, 3, 4, 5].permutation(5)
perm.each { |v| $c.add_category v * "," }
perm.each { |v| $c.train v*"," , v*"," }
def guess(arr)
s = $c.classify(arr*",")
a = s.split(',').map{|s| s.to_i}
end
tests = [
[1,2,3,4,5],
[1,2,3,nil,5],
[nil,2,3,4,5],
[1,nil,nil,4,5],
[1,nil,nil,nil,5],
[nil,nil,3,nil,nil],
[nil,nil,nil,nil,nil]
]
tests.each { |t| puts "Array #{t.inspect} became #{guess(t).inspect}" }
Output looks like the following:
Array [1, 2, 3, 4, 5] became [1, 2, 3, 4, 5]
Array [1, 2, 3, nil, 5] became [1, 2, 3, 4, 5]
Array [nil, 2, 3, 4, 5] became [1, 2, 3, 4, 5]
Array [1, nil, nil, 4, 5] became [1, 2, 3, 4, 5]
Array [1, nil, nil, nil, 5] became [1, 2, 3, 4, 5]
Array [nil, nil, 3, nil, nil] became [1, 2, 3, 4, 5]
Array [nil, nil, nil, nil, nil] became [1, 2, 3, 4, 5]

First, pair up each element with the next and previous elements
triples = array.zip([nil]+array.take(array.length-1), array.drop(1))
Then map over the array of triples like so:
triples.map {|triple|
if triple[0].nil? then
if !triple[1].nil? then triple[1] else triple[2] end
else
triple[0]
end
}
If there are more than 2 nils in a row, this won't work, so put it in a loop and keep calling it until there are no more nils in the array.
EDIT (Jörg W Mittag): You can make this more concise and readable by using destructuring bind and guard clauses:
ary.zip([nil] + ary.take(ary.length-1), ary.drop(1)).map {|prv, cur, nxt|
next prv unless prv.nil?
next cur unless cur.nil?
nxt
}
If you refactor it this way, it becomes easy to see that all the block is doing is looking for the first non-nil element in the previous-current-next triple, which can be more succinctly expressed like this:
ary.zip([nil] + ary.take(ary.length-1), ary.drop(1)).map {|triple|
triple.find {|el| !el.nil? }
}
This, in turn, can be further simplified by using Array#compact.

This is a direct copy of DigitalRoss solution but handling the edge cases of more than two nils in a row. I'm sure DigitalRoss would be able to do this more elegantly, and without the non-idomatic ruby while loop but this works for all the tested cases
def un_nil(arr)
return arr if arr.compact.size == 0 || ! arr.include?(nil)
while arr.include?(nil)
t = nil
p = lambda do |e|
if e.nil?
e,t = t,e
else
t = e
end
e
end
t = nil; r = arr.map(&p)
t = nil; r = r.reverse.map(&p).reverse
arr = r
end
arr
end
tests = [
[1,2,3,4,5],
[1,2,3,nil,5],
[nil,2,3,4,5],
[1,nil,nil,4,5],
[1,nil,nil,nil,5],
[nil,nil,3,nil,nil],
[nil,nil,nil,nil,nil]
]
tests.each {|a| puts "Array #{a.inspect} became #{un_nil(a).inspect}" }
This produces the following output
Array [1, 2, 3, 4, 5] became [1, 2, 3, 4, 5]
Array [1, 2, 3, nil, 5] became [1, 2, 3, 3, 5]
Array [nil, 2, 3, 4, 5] became [2, 2, 3, 4, 5]
Array [1, nil, nil, 4, 5] became [1, 1, 4, 4, 5]
Array [1, nil, nil, nil, 5] became [1, 1, 1, 5, 5]
Array [nil, nil, 3, nil, nil] became [3, 3, 3, 3, 3]
Array [nil, nil, nil, nil, nil] became [nil, nil, nil, nil, nil]

Here's my solution. It will work for any number of nils in the array and gracefully fail if each element in the array is nil. If a nil in the array has a non-nil before and a non-nil after it will randomly pick either before or after.
init and safety check:
arr = [1,nil,nil,4,5]
if arr.nitems == 0
raise "all nil! don't know what to do!"
else
The meat of the solution:
while (arr.index(nil))
arr.each_index do |i|
arr[i] = [arr[i-1], arr[i+1]] [rand 2] if arr[i].nil?
end
end
The wrap-up:
end
arr #print result for review
This has been tested with each of your example instances (nil at start, nil at end, double nil in the middle) and should work for any array size.
Cautions:
The item that comes "before" the first element in the array is the last element

it strikes me that it would be less surprising to propagate the last non-nil value rather than to look ahead for a non-nil value:
def fill_in_array(ary)
last_known = ary.find {|elem| elem} # find first non-nil
ary.inject([]) do |new, elem|
if elem.nil?
new << last_known
else
new << elem
last_known = elem
end
new
end
end
p fill_in_array [1,2,3,nil,5] # => [1,2,3,4,5]
p fill_in_array [1,nil,nil,4,5] # => [1,1,1,4,5]
p fill_in_array [nil,nil,nil,4,5] # => [4,4,4,4,5]

This is a variant of #Callum's solution:
require 'test/unit'
class TestArrayCompletion < Test::Unit::TestCase
def test_that_the_array_gets_completed_correctly
ary = [nil,1,2,nil,nil,3,4,nil,nil,nil,5,6,nil]
expected = [1,1,2,2,3,3,4,4,nil,5,5,6,6]
actual = ary.zip([nil]+ary.take(ary.length-1), ary.drop(1)).
map(&:compact).map(&:first)
assert_equal expected, actual
end
end

Related

A proper way to do this array comparrison in Ruby?

if array a contains any numbers also found in array b, i want them gone from a.
if b.any?
b.each do |asdf|
if a.include?(asdf)
a = a - [asdf]
end
end
end
Is this the most efficient way to do it?
UPDATE:
The answers are nice, but I've realized that the search brings back an array of People instances, #search_return. My problem is i've got 3 arrays of ids (integers): #event.a, #event.b, and #event.c which are ids of people who have already registered with the event in some capactity.
I want to take out the people who are already registered at the event from the search return, but unfortunately event.a event.b and event.c are just ids of the people, not the actual instances of the people. hence all this:
if instance.a.any?
instance.a.each do |asdf|
qwer = Qwer.find(asdf)
if #search_return.include?(qwer)
#search_return = #search_return - [qwer]
end
end
end
if instance.b.any?
instance.b.each do |asdf|
qwer = Qwer.find(asdf)
if #search_return.include?(qwer)
#search_return = #search_return - [qwer]
end
end
end
if instance.c.any?
instance.c.each do |asdf|
qwer = Qwer.find(asdf)
if #search_return.include?(qwer)
#search_return = #search_return - [qwer]
end
end
end
which may be quite heavy on the database but it's a search that won't be performed too often
If your task is "remove all elements in a that also exist in b"
then you can just subtract them:
2.6.3 :002 > a = [1, 2, 3, 4]
=> [1, 2, 3, 4]
2.6.3 :003 > b = [2, 3]
=> [2, 3]
2.6.3 :004 > c = a - b
=> [1, 4]
2.6.3 :005 >
You want do this?
irb(main):001:0> b = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> a = [4,5,3]
=> [4, 5, 3]
irb(main):003:0> a - b
=> [4, 5]
Or maybe this?
a | b
=> [4, 5, 3, 1, 2]

Does ruby support an enumerable map_cons method or its equivalent?

Ruby has a handy function for enumerables called each_cons. Which "Iterates the given block for each array of consecutive elements." This is really nice. Except that this is definitely an each method, which returns nil upon completion and not an array of the values you've looped over like map would.
However, if I have a situation where I need to iterate over an enumerable type, take an element and its cons, then perform some operation on them and return them back into an array what can I do? Normally, I'd use map for this sort of behavior. But map_cons doesn't exist.
An example:
Given a list of integers, I need to see which ones of those integers repeat and return a list of just those integers
[1, 1, 4, 5, 6, 2, 2] ## I need some function that will get me [1, 2]
I can say:
[1, 1, 4, 5, 6, 2, 2].each_cons(2) {|e| e[0] if e[0] == e[1]}
But, since it eachs over the array, it will complete successfully and return nil at the end. I need it to behave like map and not like each.
Is this behavior something that ruby supports? Am I coming at it from the wrong direction entirely?
The documentation of each_cons ends with this innocent phrase: "If no block is given, returns an enumerator." Most methods of Enumerable do this. What can you do with an Enumerator? Nothing truly impressive . But Enumerators include Enumerable, which does provide a large amount of powerful methods, map being one of them. So, as Stefan Pochmann does:
[1, 1, 4, 5, 6, 2, 2].each_cons(2).map { |e| e[0] if e[0] == e[1] }
each_consis called without a block, so it returns an Enumerator. mapis simply one of its methods.
Just add map?
[1, 1, 4, 5, 6, 2, 2].each_cons(2).map { |e| e[0] if e[0] == e[1] }
=> [1, nil, nil, nil, nil, 2]
Ruby 2.7 added the method filter_map which makes this even easier:
p [1, 1, 4, 5, 6, 2, 2].each_cons(2).filter_map{|a,b| a if a == b} # => [1, 2]

Why does each_cons yield arrays instead of multiple values?

I just wanted to apply a binary operation to consecutive elements in an array, e.g.:
[1, 2, 3, 4].each_cons(2).map { |a, b| a.quo(b) }
#=> [(1/2), (2/3), (3/4)]
This is a contrived example, the operation doesn't really matter.
I was surprised, that I couldn't just write:
[1, 2, 3, 4].each_cons(2).map(&:quo)
#=> NoMethodError: undefined method `quo' for [1, 2]:Array
This is because each_cons doesn't yield multiple values, but an array containing the values.
It works like this:
def each_cons_arrays
return enum_for(__method__) unless block_given?
yield [1, 2]
yield [2, 3]
yield [3, 4]
end
each_cons_arrays.map(&:quo)
#=> NoMethodError: undefined method `quo' for [1, 2]:Array
And I was hoping for:
def each_cons_values
return enum_for(__method__) unless block_given?
yield 1, 2
yield 2, 3
yield 3, 4
end
each_cons_values.map(&:quo)
#=> [(1/2), (2/3), (3/4)]
What's the rationale behind this? Why could it be preferable to always have an array?
And by the way, with_index on the other hand does yield multiple values:
[1, 1, 1].each.with_index(2).map(&:quo)
#=> [(1/2), (1/3), (1/4)]
From my experience, it helps to think of multiple values in ruby as an array.
It think of it at as
[1,2,3].each_cons(2) do |iter|
a,b = iter
do_stuff(a,b)
end
If you want to do it like that, I'd add the quo method to a custom class, and do
class Foobar
def initialize(a,b)
#a = a
#b = b
end
def quo
do_stuff
end
end
[1,2,3]
.each_cons(2)
.map { |a,b| Foobar.new(a,b) }
.map(:quo)
Would that work for your usecase?

Destructive reject from an array returning the values rejected

Is there a sensible way to do the following:
I want to take an array and select specific items from the array according to conditions, removing them from the array as they go.
(I basically want to split the contents of an array into categories).
array = [1,2,3,4,5,6,7,8]
less_than_three = array.reject_destructively{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.reject_destructively{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
I've tried delete_if, select!, reject! and none of them seem to be able to give you the affected items whilst leaving the array with the rest.
Unless I'm going mad, which is entirely possible.
As I understood the question, you do not want to produce two new objects. Here you go:
class Array
def carve!
dup.tap { delete_if &Proc.new } - self
end
end
array = [1,2,3,4,5,6,7,8]
p array.carve! { |v| v < 3 }
#⇒ [1, 2] # returned by Array#carve method
p array
#⇒ [3, 4, 5, 6, 7, 8] # remained in original array
Using this solution, array.__id__ remains the same. And this is the golfiest answer all around :)
You can build your own method for this...
class Array
def extract(&block)
temp = self.select(&block)
self.reject!(&block)
temp
end
end
then...
a = [1, 2, 3, 4, 5]
a.extract{|x| x < 3}
=> [1,2]
p a
=> [3, 4, 5]
EDIT: If you don't want to monkey patch (but monkey patching isn't evil in itself) you can do it with a vanilla method...
def select_from_array(array, &block)
temp = array.select(&block)
array.reject!(&block)
temp
end
array = [1,2,3,4,5,6,7,8]
less_than_three = select_from_array(array){|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = select_from_array(array){|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
In rails 6 there is a method extract!:
a = [1, 2, 3] #=> [1, 2, 3]
a.extract! { |num| num.odd? } #=> [1, 3]
a #=> [2]
irb(main):001:0> array = [1,2,3,4,5,6,7,8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
irb(main):002:0> array.partition{|v| v < 3}
=> [[1, 2], [3, 4, 5, 6, 7, 8]]
is there a specific reason, why this has to be destructive ?
Will this help
class Array
def reject_destructively(&block)
arr = self.select(&block)
arr.each{ |i| self.delete(i) }
arr
end
end
array = [1,2,3,4,5,6,7,8]
p less_than_three = array.reject_destructively{|v| v<3}
#=> [1,2]
p array
#=> [3,4,5,6,7,8]
p more_than_five = array.reject_destructively{|v| v>5}
#=> [6,7,8]
p array
#=> [3,4,5]
The above code can be simplified further to look like:
class Array
def reject_destructively(&block)
self.select(&block).each{ |i| self.delete(i) }
end
end
Ok. This works, avoids monkey patching, keeps it to one line...etc, but it's damn ugly....
less_than_three = array.dup - array.reject!{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.dup - array.reject!{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
module Enumerable
def reject_destructively
array=[]
self.each do |y|
if yield(y)
array<<y
end
end
array.each do |x|
self.delete(x)
end
return array
end
end
array=[10,9,2,1,3,45,52]
print less_than_three = array.reject_destructively{|v| v < 3}
print array
You can use group_by to get all of the elements that satisfy the condition in one group, and all of the rest in the other.
For example
[1,2,3,4,5].group_by{|i| i > 3}
gives
{false=>[1, 2, 3], true=>[4, 5]}
More information is available at http://ruby-doc.org/core-2.1.1/Enumerable.html#method-i-group_by

Delete duplicate elements from Array

I have two arrays, array1 and array2, as follows:
array1 = [ obj11, obj21, obj31 ]
array2 = [ obj21, obj22, obj23 ]
Objects in both arrays are from the same class. I want to check if array1 contains objects that already exist in array2 and delete them.
Let's say obj11 and obj22 are equal. By "equal," I mean they have similar attribute values. Then I would like to delete obj11 from array1, then insert obj21 and obj31 in array2.
I already define equality for attributes in the objects' class from here:
def ==(other)
return self.a == other.a && self.b == other.b
end
The resulting array would be:
array2 = [ obj21, obj22, obj23, obj21, obj31 ]
You can use Array#| ( it does union operation) to remove duplicates too.
array1 = ["dog", "cat", "had"]
array2 = ["big", "fight", "had"]
array1 | array2
# => ["dog", "cat", "had", "big", "fight"]
A quick way to remove duplicate values from multiple arrays is by using uniq
array1 = ["dog", "cat", "had"]
array2 = ["big", "fight", "had"]
new_array = (array1 + array2).uniq # <- ["dog", "cat", "had", "big", "fight"]
uniq removes duplicate values from an array. By combining array1 and array2 together, you can then filter out duplicates between the two of them.
If I want to solve your problem literally, then, I will write something like this:
array1 = [ :obj11, :obj21, :obj31 ]
array2 = [ :obj21, :obj22, :obj23 ]
new_array = (array1 - array2) + array2
p new_array
(array1 - array2) takes those elements from array1 that are also present in array2 and adding array2 to that will get you the final result
Output
[:obj11, :obj31, :obj21, :obj22, :obj23]
(Note: I used symbols as elements of arrays for illustration purposes)
I got the answer. In the following I delete from array1 what is already exist in array2. Equality here works as I define it in the question. Thus, checking if attributes (that have been defined in the method ==) are equal.
array1.delete_if{|elem| array2.any?{|e| e == elem}}
Then add the rest of array1 into array2.
array2 << array1
Then I flatten array2.
array2.flatten!
You could do that as follows:
a2.concat(a1.delete_if { |e| a2.include?(e) })
Here's an example:
class MyClass
attr_reader :a, :b, :c
def initialize(a, b, c)
#a, #b, #c = a, b, c
end
def ==(other)
self.a == other.a && self.b == other.b
end
end
a1 = [MyClass.new('cat', 'bat', 'rat'),
MyClass.new('dog', 'hog', 'pig'),
MyClass.new('jay', 'bee', 'fly'),]
#=> [#<MyClass:0x007fca8407b678 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007fca8407bee8 #a="dog", #b="hog", #c="pig">,
# #<MyClass:0x007fca84073ef0 #a="jay", #b="bee", #c="fly">]
a2 = [MyClass.new('fly', 'bee', 'bat'),
MyClass.new('cat', 'bat', 'rat'),
MyClass.new('dog', 'hog', 'cat'),]
#=> [#<MyClass:0x007fca840382d8 #a="fly", #b="bee", #c="bat">,
# #<MyClass:0x007fca840381e8 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007fca840380d0 #a="dog", #b="hog", #c="cat">]
a2.concat(a1.delete_if { |e| a2.include?(e) })
#=> [#<MyClass:0x007f96d404ea08 #a="fly", #b="bee", #c="bat">,
# #<MyClass:0x007f96d404e8c8 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007f96d404e710 #a="dog", #b="hog", #c="cat">,
# #<MyClass:0x007f96d409d9c8 #a="jay", #b="bee", #c="fly">]
a1
#=> [#<MyClass:0x007f96d409d9c8 #a="jay", #b="bee", #c="fly">]
If we change the first element of a1 from:
MyClass.new('cat', 'bat', 'rat')
to:
MyClass.new('cat', 'rat', 'bat')
we obtain:
a2.concat(a1.delete_if { |e| a2.include?(e) })
#=> [#<MyClass:0x007f89528568c0 #a="fly", #b="bee", #c="bat">,
# #<MyClass:0x007f8952856708 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007f89528562d0 #a="dog", #b="hog", #c="cat">,
# #<MyClass:0x007f89519277f0 #a="cat", #b="rat", #c="bat">,
# #<MyClass:0x007f8951927598 #a="jay", #b="bee", #c="fly">]
Another way (using intersection operation):
array1 = [ 1, 2, 3, 4, 5 ]
array2 = [ 2, 3, 4, 5, 6 ]
final_array = array1 + array2
final_array & final_array
This will also delete duplicates. IRB output:
2.2.1 :012 > array1 = [ 1, 2, 3, 4, 5 ]
=> [1, 2, 3, 4, 5]
2.2.1 :013 > array2 = [ 2, 3, 4, 5, 6 ]
=> [2, 3, 4, 5, 6]
2.2.1 :014 > final_array = array1 + array2
=> [1, 2, 3, 4, 5, 2, 3, 4, 5, 6]
2.2.1 :015 > final_array & final_array
=> [1, 2, 3, 4, 5, 6]

Resources