A proper way to do this array comparrison in Ruby? - arrays

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]

Related

Ruby: insert and sort numbers

I have to create a method in Ruby which inserts a number and sorts the resulting list.
Input would be like:
insert_number([2.0,3.5,4.8], 4.1)
which should output:
[2.0,3.5,4.1,4.8]
With input like:
insert_number([], 5.1)
it should output:
[5.1]
Here is my incomplete code:
def insert_number(list, number)
new_list = []
position = 0
number_has_been_inserted = false # Remember whether a new number
# has been inserted.
while position < list.length
position += 1
new_list = list + [number]
...
end
...
new_list
end
print insert_number([2.0,3.5,4.8], 4.1)
bsearch only works if the original input array is already sorted, which is not a pre-condition. – #pjs
Considering your original array is sorted you can use binary search here. It will perform much better because it won't need to perform expensive sorting procedure on each insert.
This one mutates original array
def insert_number(arr, num)
i = (0...arr.size).bsearch{ |a| arr[a] > num }
i ||= arr.size
arr.insert(i, num)
end
arr = []
insert_number(arr, 1)
#=> [1]
insert_number(arr, 2)
# => [1, 2]
insert_number(arr, 2.1)
# => [1, 2, 2.1]
insert_number(arr, 1.3)
#=> [1, 1.3, 2, 2.1]
And this one will return new array on each call
def insert_number(arr, num)
i = (0...arr.size).bsearch{ |a| arr[a] > num }
i ||= arr.size
arr[0, i] + [num] + arr[i..-1]
# or
# arr.dup.insert(i, num)
end
arr = []
arr = insert_number(arr, 1)
#=> [1]
arr = insert_number(arr, 2)
# => [1, 2]
arr = insert_number(arr, 2.1)
# => [1, 2, 2.1]
arr = insert_number(arr, 1.3)
#=> [1, 1.3, 2, 2.1]
PS:
Recent Ruby versions have bsearch_index – #Stefan
I'd do something like:
def insert_number(list, number)
(list << number).sort
end
list = [1, 2, 3]
insert_number(list, 2.1) # => [1, 2, 2.1, 3]
insert_number(list, 4.1) # => [1, 2, 2.1, 3, 4.1]
insert_number([], 1) # => [1]
The problem is this changes list. If that's not desired then use dup:
def insert_number(list, number)
(list.dup << number).sort
end
list = [1, 2, 3]
insert_number(list, 2.1) # => [1, 2, 2.1, 3]
insert_number(list, 4.1) # => [1, 2, 3, 4.1]
insert_number([], 1) # => [1]
or a "splat" AKA *:
def insert_number(list, number)
[*list, number].sort
end
list = [1, 2, 3]
insert_number(list, 2.1) # => [1, 2, 2.1, 3]
insert_number(list, 4.1) # => [1, 2, 3, 4.1]
insert_number([], 1) # => [1]
[*list, number] tells Ruby to explode the array list into its elements, effectively creating a new array:
[1, 2, 3, number]
A little shorter without having to use dup.
def insert_number(list, number)
(list + [number]).sort
end
use Array#sort:
def insert_number(list,number=false)
number.is_a?(Numeric) ? list.push(number).sort : list.sort
end
The above method uses the ternary operator which is a short form of an if-statement.
Use In-Place Array Methods
In-place operations on the array should be fastest, since they won't have to create additional arrays. For example:
#array = [2.0, 3.5, 4.8]
def insert_number float
(#array << float).sort!
end
If you can't operate directly on a shared variable, or want to reduce coupling, then you can still shave some time with the in-place sort rather than returning a new sorted array:
def insert_number array, float
(array.dup << float).sort!
end
In all cases, appending to an array with << should be faster than creating new arrays with the + method.
Actually we do not need any sort because we know that our array is sorted, and then when we will new number x we can put it into array with O(n) time, where n is size of array. We will go one by one, let's take i as index and our new array will be built like NewArray = (left numbers < x) + x + (x < right number): here left numbers are that numbers less than x and right number are numbers bigger than x.
ispushed = false
arr.each do |number|
if number < x
NewArray.push(number)
elsif ispushed == false
ispushed=true
NewArray.push(x)
newArray.push(number)
else
NewArray.push(number)
if our x is biggest so that ispushed will be remained false
in the end we can just push our 'x' number
if you use sort it works by O(nlogn) time complexity

Multiple return values with arrays and hashes

I believe arrays are mostly used for returning multiple values from methods:
def some_method
return [1, 2]
end
[a, b] = some_method # should yield a = 1 and b = 2
I presume this is a kind of syntactic sugar that Ruby provides. Can we get a similar result with hashes, for instance
def some_method
return { "a" => 1, "b" => 2 }
end
{"c", "d"} = some_method() # "c" => 1, "d" => 2
I'm looking for the result { "c" => 1, "d" => 2 }, which obviously does not happen. Is there any other way this can be done? I know that we can return a hash from the method and store it and use it like so
def some_method
return {"a" => 1, "b" => 2}
end
hash = some_method()
Just curious if there is another way similar to the one with arrays but using hashes....
I think a simpler way to put the question would be...
# If we have a hash
hash = {"a" => 1, "b" => 2}
# Is the following possible
hash = {2, 3} # directly assigning values to the hash.
OR
# another example
{"c", "d"} = {2, 3} # c and d would be treated as keys and {2, 3} as respective values.
First of all, you have a syntax error. Instead of this:
[a, b] = [1, 2]
you should use:
a, b = [1, 2]
And if you want to use similar syntax with hashes, you can do:
a, b = { "c" => 1, "d" => 2 }.values # a => 1, b => 2
This is actually the same thing as the array version, beacause Hash#values returns an array of the hash values in the order they were inserted to the hash (because ruby hashes have a nice feature of preserving their order)
What you are asking is syntactically not possible.
What you want to accomplish is possible, but you will have to code it.
One possible way to do that is shown below
hash = {"a" => 1, "b" => 2}
# Assign new keys
new_keys = ["c", "d"]
p [new_keys, hash.values].transpose.to_h
#=> {"c"=>1, "d"=>2}
# Assign new values
new_values = [3, 4]
p [hash.keys, new_values].transpose.to_h
#=> {"a"=>3, "b"=>4}
If you really want some more easier looking way of doing, you could monkey-patch Hash class and define new methods to manipulate the values of keys and values array. Please be cautioned that it may not be really worthwhile to mess with core classes. Anyways, a possible implementation is shown below. Use at your own RISK.
class Hash
def values= new_values
new_values.each_with_index do |v, i|
self[keys[i]] = v if i < self.size
end
end
def keys= new_keys
orig_keys = keys.dup
new_keys.each_with_index do |k, i|
if i < orig_keys.size
v = delete(orig_keys[i])
self[k] = v
rehash
end
end
end
end
hash = {"a" => 1, "b" => 2}
hash.values = [2,3]
p hash
#=> {"a"=>2, "b"=>3}
hash.keys = ["c", "d"]
p hash
#=> {"c"=>2, "d"=>3}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
# *** results can be unpredictable at times ***
hash.keys, hash.values = ["a"], [20, 10]
p hash
#=> {"y"=>20, "a"=>10}

Sort a hash of arrays by one of the arrays in ruby

In Ruby, is there a short and sweet way to sort this hash of arrays by score descending:
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
so it looks like this?:
scored = {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
I couldnt find an example where I can sort arrays within a hash like this? I could do this with some nasty code but I feel like there should be a 1 or 2 liner that does it?
You could use sort_by
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
scored.tap do |s|
s[:id] = s[:id].sort_by.with_index{ |a, i| -s[:score][i] }
s[:score] = s[:score].sort_by{ |a| -a }
end
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
order = scored[:score].each_with_index.sort_by(&:first).map(&:last).reverse
#=> [2,0,1]
scored.update(scored) { |_,a| a.values_at *order }
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
If scored is to not to be mutated, replace update with merge.
Some points:
Computing order makes it easy for the reader to understand what's going on.
The second line uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged (which here is all keys). This is a convenient way to modify hash values (generally), in part because the new hash is returned.
I sorted then reversed, rather than sorted by negated values, to make the method more rubust. (That is, the elements of the arrays that are the values can be from any class that implements <=>).
With Ruby 2.2+, another way to sort an array arr in descending order is to use Enumerable#max_by: arr.max_by(arr.size).to_a.
The first line could be replaced with:
arr = scored[:score]
order = arr.each_index.sort_by { |i| arr[i] }.reverse
#=> [2,0,1]
Here is one possible solution. It has an intermediate step, where it utilizes a zipped version of the scores object, but produces the correct output:
s = scored.values.inject(&:zip).sort_by(&:last).reverse
#=> [[3, 10], [1, 8.3], [2, 5]]
result = { id: s.map(&:first), score: s.map(&:last) }
#=> { :id => [3, 1, 2], :score => [10, 8.3, 5] }

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

Complete a Ruby Array

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

Resources