Identify runs on array with ruby - ruby

If we have an array
array = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]
How can we identify the run (amount of consecutive numbers with same value) of a given number?
By example:
run_pattern_for(array, 0) -> 2
run_pattern_for(array, 3) -> 1
run_pattern_for(array, 1) -> 1
run_pattern_for(array, 2) -> 0
There are no runs for 2 because there are no consecutive apparitions of two.
There are one run for 3 because there are only one apparition with the tree as consecutive numbers.

try:
class Array
def count_runs(element)
chunk {|n| n}.count {|a,b| a == element && b.length > 1}
end
end
a = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]
a.count_runs 0 #=> 2
a.count_runs 3 #=> 1
a.count_runs 1 #=> 1
a.count_runs 2 #=> 0

I agree with #BroiSatse that Enumerable#chunk should be used here, but I would like to show how an enumerator could be employed directly to solve this problem, using the methods Enumerator#next and Enumerator#peek.
Code
def count_em(array)
return [] if array.empty?
h = Hash.new(0)
enum = array.each
loop do
x = enum.next
if x == enum.peek
h[x] += 1
enum.next until (enum.peek != x)
else
h[x] = 0 unless h.key?(x)
end
end
h
end
Example
array = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]
count_em(array) #=> {1=>1, 0=>2, 2=>0, 3=>1}
Explanation
Suppose
array = [1, 1, 1, 0, 2, 2]
h = Hash.new(0)
enum = array.each
#=> #<Enumerator: [1, 1, 1, 0, 2, 2]:each>
x = enum.next #=> 1
enum.peek #=> 1
so x == enum.peek #=> true, meaning there is a run of at least two 1's, so wish execute:
h[x] += 1 #=> h[1] += 1
which means
h[1] = h[1] + 1
Since h does not have a key 1, h[x] on the right side of the equality set to zero, the default value we established when creating the hash. Therefore, the hash h is now { 1=>1 }. Now we want need to enumerate and discard any more 1's in the run:
enum.next until (enum.peek != x)
enum.next #=> 1
enum.peek #=> 1
enum.next #=> 1
enum.peek #=> 0
Now go back to the top of the loop:
x = enum.next #=> 0
enum.peek #=> 2
Since (x == enum.peek) => (0 == 2) => false, and h.key?(x) => false, we set
h[0] = 0
and the hash is now { 1=>1, 0=>0 }. Returning again to the top of the loop,
x = enum.next #=> 2
enum.peek #=> 2
Since (x == enum.peek) => (2 == 2) => true, we execute:
h[2] += 1 #=> 1
so now h => {1=>1, 0=>0, 2=>1}. Now when we execute
x = enum.next #=> 2
enum.peek #=> StopIteration: iteration reached an end
The exception is rescued by Kernel#loop. That is, raising a StopIteration error is one way to break out of the loop, causing the last line of the method to be executed and returned:
h #=> {1=>1, 0=>0, 2=>1}
(Note this result differs from that in the example above because it is for a different array.)

Ruby 2.2, which was released roughly seven months after this question was posted, gave us a method that has application here, Enumerable#slice_when:
array.slice_when { |i,j| i != j }.each_with_object(Hash.new(0)) { |a,h|
h[a.first] += (a.size > 1) ? 1 : 0 }
#=> {1=>1, 0=>2, 2=>0, 3=>1}

It's a simple task; Here are two different ways I've done it:
array = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]
hash = Hash[array.group_by { |e| e }.map{ |k, v| [k, v.size] }]
# => {1=>2, 0=>5, 2=>1, 3=>4}
And:
hash = Hash.new{ |h,k| h[k] = 0 }
array.each { |e| hash[e] += 1 }
hash # => {1=>2, 0=>5, 2=>1, 3=>4}
Once you have the hash the rest is easy:
hash[0] # => 5
hash[1] # => 2
hash[2] # => 1
hash[3] # => 4
If it's possible you'll request a count for a number that didn't exist in the array, and want a numeric response instead of nil, use something like:
Integer(hash[4]) # => 0
Integer(...) converts nil to 0 for you.
In the first example above, group_by will do the heavy lifting, and results in:
array.group_by { |e| e } # => {1=>[1, 1], 0=>[0, 0, 0, 0, 0], 2=>[2], 3=>[3, 3, 3, 3]}
The map statement simply converts the array to its size.

Related

Create a new array based on a relationship between elements in an existing array in ruby [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I have the following array:
a = [1, 2, 6, 10, 11]
and I want to return a new array b that consists of the sums of adjacent elements that differ by one. In this case, the returned array would be:
b = [3, 21]
i.e. a[0] and a[1] differ by one, so sum them and add 3 to b.
a[3] and a[4] differ by one, so sum them and add 21 to b.
Update
I've made a mistake:
a = [1, 2, 6, 10, 11, 12]
It should return:
b = [3, 33]
You can initialize a b variable and use each_cons, taking two consecutive elements from the array, then use map, inside you can get the sum of those two values per array if the substraction of both values is equal to 1, as you'll get nil values then you can compact the "mapped" result:
a = [1, 2, 6, 10, 11]
b = a.each_cons(2).map do |value|
value.reduce(:+) if value[1] - value[0] == 1
end.compact
# => [3, 21]
Here's an update, you can use slice_when and convert to array the enumerator that you get as result, then to map the sum of each array element that has more than one value inside, and the compact in order to remove nil elements:
p arr.slice_when{|a, b| b != a.next}.to_a
# => [[1, 2], [6], [10, 11, 12]]
p arr.slice_when{|a, b| b != a.next}.to_a.map{|e| e.sum if e.size > 1}
# => [3, nil, 33]
p arr.slice_when{|a, b| b != a.next}.to_a.map{|e| e.sum if e.size > 1}.compact
# => [3, 33]
But this looks better using select and mapping the sum of elements at the end:
p arr.slice_when{|a, b| b != a.next}.to_a.select{|e| e.size > 1}.map(&:sum)
A benchmark:
arr = [1, 2, 6, 10, 11, 12]
Benchmark.bm do |bm|
bm.report do
iterations.times do
arr.slice_when{|a, b| b != a.next}.to_a.map{|e| e.sum if e.size > 1}.compact
end
end
bm.report do
iterations.times do
arr.slice_when{|a, b| b != a.next}.to_a.select{|e| e.size > 1}.map(&:sum)
end
end
bm.report do
iterations.times do
arr.chunk_while { |a,b| b == a.next }.select{ |a| a.size > 1 }.map{|e| e.reduce(:+)}
end
end
end
user system total real
0.920000 0.010000 0.930000 ( 0.942134)
0.920000 0.010000 0.930000 ( 0.939316)
0.940000 0.010000 0.950000 ( 0.964895)
You can use chunk_while. It 'chunks' adjacent elements if they differ by 1 (using the test #SebastiánPalma has but with abs). See Ruby documentation for more information about these methods.
a.chunk_while { |x,y| (x-y).abs == 1 }.select{ |a| a.size > 1 }.map(&:sum)
#=> [3, 21]
Note: Array#sum can only be used in Ruby >= 2.4. Use inject(&:+) otherwise:
a.chunk_while {|x,y| (x-y).abs == 1 }.select{|a| a.size > 1}.map {|a| a.inject(&:+)}
Steps
a.chunk_while {|x,y| (x-y).abs == 1 } #actually returns an enumerator.
#=> [[1, 2], [6], [10, 11]]
a.chunk_while {|x,y| (x-y).abs == 1 }.select{|a| a.size > 1}
#=> [[1, 2], [10, 11]]
a.chunk_while {|x,y| (x-y).abs == 1 }.select{|a| a.size > 1}.map(&:sum)
#=> [3, 21]
This will work with Ruby v1.9+.
arr = [1, 2, 6, 6, 10, 11, 12]
arr.drop(1).
each_with_object([[arr.first]]) { |n,a| (a.last.last - n).abs == 1 ?
a.last.push(n) : a.push([n]) }.
reject { |a| a.size == 1 }.
map(&:sum)
#=> [3, 33]
Here's a variant that allows us to skip the step reject { |a| a.size == 1 }. (I thought this might be of interest even though I don't think I'd advocate it.)
e = (arr + [Float::INFINITY]).to_enum
a = [[e.next]]
loop do
n = e.next
(a.last.last-n).abs==1 ? a.last.push(n) : (a.push([n]) if (n-e.peek).abs==1)
end
a.map(&:sum)
#=> [3, 33]
When the iterator is at the end and n #=> Float::INFINITY, e.peek raises a StopIteration exception which Kernel#loop handles by breaking out of the loop.
iterate through each element, initialize var 'sum' to elem if sum is nil. When difference between elem and next is one, add next elem to sum and store in sum, increment seq so we know there was at-least one with diff as 1.
Do this until the diff b/t elem and next is not 1, when diff is not 1 push the sum to res array if seq > 0 and reset sum to nil and seq to 0. This only takes O(n).
a.each_with_object([]).with_index do |(x, res), i|
sum ||= x
if a[i+1] && (x - a[i+1]).abs == 1
seq += 1
sum += a[i+1]
else
res << sum if seq > 0
sum = nil
seq = 0
end
end

Distributions using nested loops

I would like to write a program which generates all distributions for a given n.
For example, if I enter n equal to 7, the returned result will be:
7
6 1
5 2
5 1 1
4 3
4 2 1
4 1 1 1
3 3 1
3 2 2
3 2 1 1
3 1 1 1 1
2 2 2 1
2 2 1 1 1
2 1 1 1 1 1
1 1 1 1 1 1 1
I wrote the following code:
def sum(a, n)
for i in 1..a.length
a.each do |a|
z = a+i
if z == n
print i
puts a
end
end
end
end
def distribution(n)
numbers_container = []
for i in 1..n-1
numbers_container<<i
end
sum(numbers_container,n)
end
puts "Enter n"
n = gets.chomp.to_i
distribution(n)
I'm stuck in the part where the program needs to check the sum for more than two augends. I don't have an idea how can I write a second loop.
I suggest you use recursion.
Code
def all_the_sums(n, mx=n, p=[])
return [p] if n.zero?
mx.downto(1).each_with_object([]) { |i,a|
a.concat(all_the_sums(n-i, [n-i,i].min, p + [i])) }
end
Example
all_the_sums(7)
#=> [[7],
# [6, 1],
# [5, 2], [5, 1, 1],
# [4, 3], [4, 2, 1], [4, 1, 1, 1],
# [3, 3, 1], [3, 2, 2], [3, 2, 1, 1], [3, 1, 1, 1, 1],
# [2, 2, 2, 1], [2, 2, 1, 1, 1], [2, 1, 1, 1, 1, 1],
# [1, 1, 1, 1, 1, 1, 1]]
Explanation
The argument mx is to avoid the generation of permuations of results. For example, one sequence is [4,2,1]. There are six permutations of the elements of this array (e.g., [4,1,2], [2,4,1] and so on), but we want just one.
Now consider the calculations performed by:
all_the_sums(3)
Each eight-space indentation below reflects a recursive call to the method.
We begin with
n = 3
mx = 3
p = []
return [[]] if 3.zero? #=> no return
# first value passed block by 3.downto(1)..
i = 3
a = []
# invoke all_the_sums(0, [0,3].min, []+[3])
all_the_sums(0, 0, [3])
return [[3]] if 0.zero? #=> return [[3]]
a.concat([[3]]) #=> [].concat([[3]]) => [[3]]
# second value passed block by 3.downto(1)..
i = 2
a = [[3]]
# invoke all_the_sums(1, [1,2].min, []+[2])
all_the_sums(1, 1, [2])
return [[2]] if 1.zero? #=> do not return
# first and only value passed block by 1.downto(1)..
i = 1
a = []
# invoke all_the_sums(0, [0,1].min, [2]+[1])
all_the_sums(0, 0, [2,1])
return [[2,1]] if 0.zero? #=> [[2,1]] returned
a.concat([[2,1]]) #=> [].concat([[2,1]]) => [[2,1]]
return a #=> [[2,1]]
a.concat([[2,1]]) #=> [[3]].concat([[2,1]]) => [[3],[2,1]]
# third and last value passed block by 3.downto(1)..
i = 1
a = [[3],[2,1]]
# invoke all_the_sums(2, [2,1].min, [1])
all_the_sums(2, 1, [1])
return [] if 2.zero? #=> [] not returned
# first and only value passed block by 1.downto(1)..
i = 1
a = []
# invoke all_the_sums(1, [1,1].min, [1]+[1])
all_the_sums(1, 1, [1,1])
return [1,1] if 1.zero? #=> [1,1] not returned
# first and only value passed block by 1.downto(1)..
i = 1
a = []
# invoke all_the_sums(0, [0,1].min, [1,1]+[1]])
all_the_sums(0, 0, [1,1,1])
return [1,1,1] if 1.zero?
#=> return [1,1,1]
a.concat([[1,1,1]]) #=> [[1,1,1]]
return a #=> [[1,1,1]]
a.concat([[1,1,1]]) #=> [].concat([[1,1,1]]) => [[1,1,1]]
return a #=> [[1,1,1]]
a.concat([[1,1,1]]) #=> [[3],[2,1]].concat([[1,1,1]])
return a #=> [[3],[2,1],[1,1,1]]
You can use unary with parameters to have infinite amounts of parameters:
def test_method *parameters
puts parameters
puts parameters.class
end
test_method("a", "b", "c", "d")
So, parameters inside the block becomes an array of parameters. You can then easly loop through them:
parameters.each { |par| p par }
Also, don't use for loops for this as they are less readable than using each methods.
[1..n-1].each do i
# body omitted
end
I think you be able to work it out if you tried to call sum recursively. After this bit:
print i
puts a
Try calling sum again, like this:
sum((1..a).to_a, a)
It won't solve it, but it might lead you in the right direction.

Checking arrays and implementing bool methods

You have an array. If any two numbers add to zero in the array, return true. It doesn't matter how many pairs there are—as long as there is one pair that adds to zero, return true. If there is a zero, it can only return true if there is more than one.
I wrote two functions, one to check for each, and a final one to combine both, and return false if either aren't met.
def checkZero(array)
zerocount = 0
for j in 0..array.count
if array[j] == 0
zerocount += 1
end
end
if zerocount > 1 #this part seems to not be working, not sure why
return true
else
return false
end
end
def checkNegative(array)
for j in 0..array.count
neg = -array[j] #set a negative value of the current value
if array.include?(neg) #check to see whether the negative exists in the array
return true
else
return false
end
end
end
def checkArray(array)
if checkZero(array) == true or checkNegative(array) == true
return true
else
return false
end
end
Then run something like
array = [1,2,3,4,0,1,-1]
checkArray(array)
So far, Ruby isn't returning anything. I just get a blank. I have a feeling my return isn't right.
The problem may be that you didn't output the result.
array = [1,2,3,4,0,1,-1]
puts checkArray(array)
The checkArray method can be written like the following, if performance (O(n^2)) is not a great concern:
def check_array(array)
array.combination(2).any?{|p| p.reduce(:+) == 0}
end
The more efficient (O(n log n)) solution is:
def check_array(array)
array.sort! # `array = array.sort` if you need the original array unchanged
i, j = 0, array.size - 1
while i < j
sum = array[i] + array[j]
if sum > 0
j -= 1
elsif sum < 0
i += 1
else
return true
end
end
return false
end
Here's are a few relatively efficient ways to check if any two values sum to zero:
Solution #1
def checkit(a)
return true if a.count(&:zero?) > 1
b = a.uniq.map(&:abs)
b.uniq.size < b.size
end
Solution #2
def checkit(a)
return true if a.sort_by(&:abs).each_cons(2).find { |x,y| x == -y }
false
end
Solution #3
def checkit(a)
return true if a.count(&:zero?) > 1
pos, non_pos = a.group_by { |n| n > 0 }.values
(pos & non_pos.map { |n| -n }).any?
end
Solution #4
require 'set'
def checkit(a)
a.each_with_object(Set.new) do |n,s|
return true if s.include?(-n)
s << n
end
false
end
Examples
checkit([1, 3, 4, 2, 2,-3,-5,-7, 0, 0]) #=> true
checkit([1, 3, 4, 2, 2,-3,-5,-7, 0]) #=> true
checkit([1, 3, 4, 2,-3, 2,-3,-5,-7, 0]) #=> true
checkit([1, 3, 4, 2, 2,-5,-7, 0]) #=> false
Explanations
The following all refer to the array:
a = [1,3,4,2,2,-3,-5,-7,0]
#1
Zeroes present a bit of a problem, so lets first see if there are more than one, in which case we are finished. Since a.count(&:zero?) #=> 1, a.count(&:zero?) > 1 #=> false, so
return true if a.count(&:zero?) > 1
does not cause us to return. Next, we remove any duplicates:
a.uniq #=> [1, 3, 4, 2, -3, -5, -7, 0]
Then convert all the numbers to their absolute values:
b = a.uniq,map(&:abs) #=> [1, 3, 4, 2, 3, 5, 7, 0]
Lastly see if c contains any dups, meaning the original array contained at least two non-zero numbers with opposite signs:
c.uniq.size < c.size #=> true
#2
b = a.sort_by(&:abs)
#=> [0, 1, 2, 2, 3, -3, 4, -5, -7]
c = b.each_cons(2)
#=> #<Enumerator: [0, 1, 2, 2, 3, -3, 4, -5, -7]:each_cons(2)>
To see the contents of the enumerator:
c.to_a
#=> [[0, 1], [1, 2], [2, 2], [2, 3], [3, -3], [-3, 4], [4, -5], [-5, -7]]
c.find { |x,y| x == -y }
#=> [3, -3]
so true is returned.
#3
return true if a.count(&:zero?) > 1
#=> return true if 1 > 1
h = a.group_by { |n| n > 0 }
#=> {true=>[1, 3, 4, 2, 2], false=>[-3, -5, -7, 0]}
b = h.values
#=> [[1, 3, 4, 2, 2], [-3, -5, -7, 0]]
pos, non_pos = b
pos
#=> [1, 3, 4, 2, 2]
non_pos
#=> [-3, -5, -7, 0]
c = non_pos.map { |n| -n }
#=> [3, 5, 7, 0]
d = pos & c
#=> [3]
d.any?
#=> true
#4
require 'set'
enum = a.each_with_object(Set.new)
#=> #<Enumerator: [1, 3, 4, 2, 2, -3, -5, -7, 0]:each_with_object(#<Set: {}>)>
enum.to_a
#=> [[1, #<Set: {}>],
# [3, #<Set: {}>],
# ...
# [0, #<Set: {}>]]
Values are passed into the block, assigned to the block variables and the block is executed, as follows:
n, s = enum.next
#=> [1, #<Set: {}>]
s.include?(-n)
#=> #<Set: {}>.include?(-1)
#=> false
s << n
#=> #<Set: {1}>
n, s = enum.next
#=> [3, #<Set: {1}>]
s.include?(-3)
#=> false
s << n
#=> #<Set: {1, 3}>
...
n, s = enum.next
#=> [2, #<Set: {1, 3, 4, 2}>]
s.include?(-n)
#=> false
s << n
#=> #<Set: {1, 3, 4, 2}> # no change
n, s = enum.next
#=> [-3, #<Set: {1, 3, 4, 2}>]
s.include?(-n)
#=> true
causing true to be returned.
I can’t reproduce any problem with your code, but you can express the solution very succinctly using combination to get all possible pairs, then summing each pair with reduce, and finally checking if any are zero?:
[1,2,3,4,0,1,-1].combination(2).map { |pair| pair.reduce(:+) }.any?(&:zero?)
This is a bit of a code review. Let's start with the first method:
def checkZero(array)
Ruby naming convention is snake_case rather than camelCase. This should be def check_zero(array)
Now the loop:
zerocount = 0
for j in 0..array.count
if array[j] == 0
zerocount += 1
end
end
As #AndrewMarshall said, for is not idiomatic. each is preferable. However, in ruby initializing a variable before a loop is almost never needed thanks to all the methods available to you on Array and Enumerable (which is included in Array). I highly recommend committing these methods to memory. The above can be written
array.any? {|number| number.zero?}
or equivalently
array.any?(&:zero?)
Now, this part:
if zerocount > 1 #this part seems to not be working, not sure why
return true
else
return false
end
end
Whenever you have the pattern
if (expr that returns true or false)
return true
else
return false
end
it can be simplified to simply return (expr that returns true or false). And you can even omit the return if it is the last statement of a method.
Putting it all together:
def check_zero(array)
array.any?(&:zero?)
end
def check_zero_sum(array)
array.combination(2).any?{|a,b| a + b == 0}
end
def check_array(array)
check_zero(array) || check_zero_sum(array)
end
(Note I borrowed AndrewMarshall's code for check_zero_sum which I think is easy to follow, but #CarySwoveland's answer will be faster)
Edit
I missed the fact that check_zero isn't even necessary because you want at least a pair, in which case check_zero_sum is all you need.
def check_array(array)
array.combination(2).any?{|a,b| a + b == 0}
end

Removing elements from array Ruby

Let's say I am trying to remove elements from array a = [1,1,1,2,2,3]. If I perform the following:
b = a - [1,3]
Then I will get:
b = [2,2]
However, I want the result to be
b = [1,1,2,2]
i.e. I only remove one instance of each element in the subtracted vector not all cases. Is there a simple way in Ruby to do this?
You may do:
a= [1,1,1,2,2,3]
delete_list = [1,3]
delete_list.each do |del|
a.delete_at(a.index(del))
end
result : [1, 1, 2, 2]
[1,3].inject([1,1,1,2,2,3]) do |memo,element|
memo.tap do |memo|
i = memo.find_index(e)
memo.delete_at(i) if i
end
end
Not very simple but:
a = [1,1,1,2,2,3]
b = a.group_by {|n| n}.each {|k,v| v.pop [1,3].count(k)}.values.flatten
=> [1, 1, 2, 2]
Also handles the case for multiples in the 'subtrahend':
a = [1,1,1,2,2,3]
b = a.group_by {|n| n}.each {|k,v| v.pop [1,1,3].count(k)}.values.flatten
=> [1, 2, 2]
EDIT: this is more an enhancement combining Norm212 and my answer to make a "functional" solution.
b = [1,1,3].each.with_object( a ) { |del| a.delete_at( a.index( del ) ) }
Put it in a lambda if needed:
subtract = lambda do |minuend, subtrahend|
subtrahend.each.with_object( minuend ) { |del| minuend.delete_at( minuend.index( del ) ) }
end
then:
subtract.call a, [1,1,3]
A simple solution I frequently use:
arr = ['remove me',3,4,2,45]
arr[1..-1]
=> [3,4,2,45]
a = [1,1,1,2,2,3]
a.slice!(0) # remove first index
a.slice!(-1) # remove last index
# a = [1,1,2,2] as desired
For speed, I would do the following, which requires only one pass through each of the two arrays. This method preserves order. I will first present code that does not mutate the original array, then show how it can be easily modified to mutate.
arr = [1,1,1,2,2,3,1]
removals = [1,3,1]
h = removals.group_by(&:itself).transform_values(&:size)
#=> {1=>2, 3=>1}
arr.each_with_object([]) { |n,a|
h.key?(n) && h[n] > 0 ? (h[n] -= 1) : a << n }
#=> [1, 2, 2, 1]
arr
#=> [1, 1, 1, 2, 2, 3, 1]
To mutate arr write:
h = removals.group_by(&:itself).transform_values(&:count)
arr.replace(arr.each_with_object([]) { |n,a|
h.key?(n) && h[n] > 0 ? (h[n] -= 1) : a << n })
#=> [1, 2, 2, 1]
arr
#=> [1, 2, 2, 1]
This uses the 21st century method Hash#transform_values (new in MRI v2.4), but one could instead write:
h = Hash[removals.group_by(&:itself).map { |k,v| [k,v.size] }]
or
h = removals.each_with_object(Hash.new(0)) { | n,h| h[n] += 1 }

Ruby array subtraction without removing items more than once

The canonical Array difference example in Ruby is:
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
What's the best way to get the following behavior instead?
[ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) #=> [ 1, 2, 3, 3, 5 ]
That is, only the first instance of each matching item in the second array is removed from the first array.
Subtract values as many times as they appear in the other array, or any Enumerable:
class Array
# Subtract each passed value once:
# %w(1 2 3 1).subtract_once %w(1 1 2) # => ["3"]
# [ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) => [1, 2, 3, 3, 5]
# Time complexity of O(n + m)
def subtract_once(values)
counts = values.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
Subtract each unique value once:
require 'set'
class Array
# Subtract each unique value once:
# %w(1 2 2).subtract_once_uniq %w(1 2 2) # => [2]
# Time complexity of O((n + m) * log m)
def subtract_once_uniq(values)
# note that set is implemented
values_set = Set.new values.to_a
reject { |e| values_set.delete(e) if values_set.include?(e) }
end
end
class Array
def subtract_once(b)
h = b.inject({}) {|memo, v|
memo[v] ||= 0; memo[v] += 1; memo
}
reject { |e| h.include?(e) && (h[e] -= 1) >= 0 }
end
end
I believe this does what I want. Many thanks to #glebm
This is all I can think of so far:
[1, 2, 4].each { |x| ary.delete_at ary.index(x) }
Similar to #Jeremy Ruten's answer but accounting for the fact that some elements may not be present:
# remove each element of y from x exactly once
def array_difference(x, y)
ret = x.dup
y.each do |element|
if index = ret.index(element)
ret.delete_at(index)
end
end
ret
end
This answer also won't modify the original array as it operates, so:
x = [1,2,3]
y = [3,4,5]
z = array_difference(x, y) # => [1,2]
x == [1,2,3] # => [1,2,3]

Resources