Remove from the array elements that are repeated - ruby

What is the best way to remove from the array elements that are repeated.
For example, from the array
a = [4, 3, 3, 1, 6, 6]
need to get
a = [4, 1]
My method works to too slowly with big amount of elements.
arr = [4, 3, 3, 1, 6, 6]
puts arr.join(" ")
nouniq = []
l = arr.length
uniq = nil
for i in 0..(l-1)
for j in 0..(l-1)
if (arr[j] == arr[i]) and ( i != j )
nouniq << arr[j]
end
end
end
arr = (arr - nouniq).compact
puts arr.join(" ")

a = [4, 3, 3, 1, 6, 6]
a.select{|b| a.count(b) == 1}
#=> [4, 1]
More complicated but faster solution (O(n) I believe :))
a = [4, 3, 3, 1, 6, 6]
ar = []
add = proc{|to, form| to << from[1] if form.uniq.size == from.size }
a.sort!.each_cons(3){|b| add.call(ar, b)}
ar << a[0] if a[0] != a[1]; ar << a[-1] if a[-1] != a[-2]

arr = [4, 3, 3, 1, 6, 6]
arr.
group_by {|e| e }.
map {|e, es| [e, es.length] }.
reject {|e, count| count > 1 }.
map(&:first)
# [4, 1]

Without introducing the need for a separate copy of the original array and using inject:
[4, 3, 3, 1, 6, 6].inject({}) {|s,v| s[v] ? s.merge({v=>s[v]+1}) : s.merge({v=>1})}.select {|k,v| k if v==1}.keys
=> [4, 1]

I needed something like this, so tested a few different approaches. These all return an array of the items that are duplicated in the original array:
module Enumerable
def dups
inject({}) {|h,v| h[v]=h[v].to_i+1; h}.reject{|k,v| v==1}.keys
end
def only_duplicates
duplicates = []
self.each {|each| duplicates << each if self.count(each) > 1}
duplicates.uniq
end
def dups_ej
inject(Hash.new(0)) {|h,v| h[v] += 1; h}.reject{|k,v| v==1}.keys
end
def dedup
duplicates = self.dup
self.uniq.each { |v| duplicates[self.index(v)] = nil }
duplicates.compact.uniq
end
end
Benchark results for 100,000 iterations, first with an array of integers, then an array of strings. Performance will vary depending on the numer of duplicates found, but these tests are with a fixed number of duplicates (~ half array entries are duplicates):
test_benchmark_integer
user system total real
Enumerable.dups 2.560000 0.040000 2.600000 ( 2.596083)
Enumerable.only_duplicates 6.840000 0.020000 6.860000 ( 6.879830)
Enumerable.dups_ej 2.300000 0.030000 2.330000 ( 2.329113)
Enumerable.dedup 1.700000 0.020000 1.720000 ( 1.724220)
test_benchmark_strings
user system total real
Enumerable.dups 4.650000 0.030000 4.680000 ( 4.722301)
Enumerable.only_duplicates 47.060000 0.150000 47.210000 ( 47.478509)
Enumerable.dups_ej 4.060000 0.030000 4.090000 ( 4.123402)
Enumerable.dedup 3.290000 0.040000 3.330000 ( 3.334401)
..
Finished in 73.190988 seconds.
So of these approaches, it seems the Enumerable.dedup algorithm is the best:
dup the original array so it is immutable
gets the uniq array elements
for each unique element: nil the first occurence in the dup array
compact the result
If only (array - array.uniq) worked correctly! (it doesn't - it removes everything)

Here's my spin on a solution used by Perl programmers using a hash to accumulate counts for each element in the array:
ary = [4, 3, 3, 1, 6, 6]
ary.inject({}) { |h,a|
h[a] ||= 0
h[a] += 1
h
}.select{ |k,v| v == 1 }.keys # => [4, 1]
It could be on one line, if that's at all important, by judicious use of semicolons between the lines in the map.
A little different way is:
ary.inject({}) { |h,a| h[a] ||= 0; h[a] += 1; h }.map{ |k,v| k if (v==1) }.compact # => [4, 1]
It replaces the select{...}.keys with map{...}.compact so it's not really an improvement, and, to me is a bit harder to understand.

Related

JavaScript `reduce` to Ruby `reduce`

In JavaScript a reduce function may look like:
array.reduce((acc, cur, idx, arr) => {
// body
}, starting_value);
I'm trying to somehow have that arr argument, which is a copy of the original array, I've seen it being useful plenty of times. This is as far as I could take it:
array.each_with_index.reduce (starting_value) do |acc (cur, idx)|
# body
end
I've been browsing through the Ruby documentation for quite some time (I actually copied the .each_with_index since I found it somewhere), looking for anything even remotely like what I've been looking for.
To be quite honest functionally I could split it into multiple lines and store something in a variable, but if I can keep my functional approach in JavaScript with Ruby, I would be super happy.
In essence: is there any way to get the arr parameter within the body?
reduce – being an Enumerable method – is not aware of the collection it is enumerating.
You have to incorporate the array yourself, for example via then / yield_self:
[1, 2, 3].then do |arr|
arr.each_with_index.reduce(4) do |acc, (cur, idx)|
p acc: acc, cur: cur, idx: idx, arr: arr
acc + cur
end
end
# {:acc=>4, :cur=>1, :idx=>0, :arr=>[1, 2, 3]}
# {:acc=>5, :cur=>2, :idx=>1, :arr=>[1, 2, 3]}
# {:acc=>7, :cur=>3, :idx=>2, :arr=>[1, 2, 3]}
#=> 10
or somewhere within the chain:
[1, 2, 3].then do |arr|
arr.map { |x| x * 2 }.then do |arr_2|
arr_2.each_with_index.reduce(4) do |acc, (cur, idx)|
p acc: acc, cur: cur, idx: idx, arr: arr, arr_2: arr_2
acc + cur
end
end
end
# {:acc=>4, :cur=>2, :idx=>0, :arr=>[1, 2, 3], :arr_2=>[2, 4, 6]}
# {:acc=>6, :cur=>4, :idx=>1, :arr=>[1, 2, 3], :arr_2=>[2, 4, 6]}
# {:acc=>10, :cur=>6, :idx=>2, :arr=>[1, 2, 3], :arr_2=>[2, 4, 6]}
#=> 16
It is possible to create a custom reduce method:
module Enumerable
def reduce_with_self(initial_or_sym, sym = nil)
if initial_or_sym.is_a?(Symbol)
operator = initial_or_sym
initial = nil
else
initial = initial_or_sym
operator = sym
end
accumulator = initial
each_with_index do |item, index|
if index.zero? && initial.nil?
accumulator = item
next
end
accumulator = operator.nil? ? yield(accumulator, item, self) : accumulator.send(operator, item)
end
accumulator
end
end
The third argument of the block will be a reference to a collection:
> [1,2,3,4].reduce_with_self(0) do |acc, item, array|
> p array
> acc += item
> end
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
=> 10
> [1,2,3,4].reduce_with_self(2,:+)
=> 12
> [1,2,3,4].reduce_with_self(:+)
=> 10
Of course, this implementation will be slower than the original one:
require 'benchmark'
Benchmark.bm do |x|
x.report('reduce') { 1000.times { (0..10000).reduce(0) { |acc, item| acc += item } } }
x.report('reduce_with_self') { 1000.times { (0..10000).reduce_with_self(0) { |acc, item, array| acc += item } } }
end
user system total real
reduce 0.501833 0.000000 0.501833 ( 0.502698)
reduce_with_self 0.955978 0.000000 0.955978 ( 0.956809)

How to improve algorithm efficiency for nested loop

Given a list of integers and a single sum value, return the first two values (from the left) that add up to form the sum.
For example, given:
sum_pairs([10, 5, 2, 3, 7, 5], 10)
[5, 5] (at indices [1, 5] of [10, 5, 2, 3, 7, 5]) add up to 10, and [3, 7] (at indices [3, 4]) add up to 10. Among them, the entire pair [3, 7] is earlier, and therefore is the correct answer.
Here is my code:
def sum_pairs(ints, s)
result = []
i = 0
while i < ints.length - 1
j = i+1
while j < ints.length
result << [ints[i],ints[j]] if ints[i] + ints[j] == s
j += 1
end
i += 1
end
puts result.to_s
result.min
end
It works, but is too inefficient, taking 12000 ms to run. The nested loop is the problem of inefficiency. How could I improve the algorithm?
Have a Set of numbers you have seen, starting empty
Look at each number in the input list
Calculate which number you would need to add to it to make up the sum
See if that number is in the set
If it is, return it, and the current element
If not, add the current element to the set, and continue the loop
When the loop ends, you are certain there is no such pair; the task does not specify, but returning nil is likely the best option
Should go superfast, as there is only a single loop. It also terminates as soon as it finds the first matching pair, so normally you wouldn't even go through every element before you get your answer.
As a style thing, using while in this way is very unRubyish. In implementing the above, I suggest you use ints.each do |int| ... end rather than while.
EDIT: As Cary Swoveland commented, for a weird reason I thought you needed indices, not the values.
require 'set'
def sum_pairs(arr, target)
s = Set.new
arr.each do |v|
return [target-v, v] if s.include?(target-v)
s << v
end
nil
end
sum_pairs [10, 5, 2, 3, 7, 5], 10
#=> [3, 7]
sum_pairs [10, 5, 2, 3, 7, 5], 99
#=> nil
I've used Set methods to speed include? lookups (and, less important, to save only unique values).
Try below, as it is much more readable.
def sum_pairs(ints, s)
ints.each_with_index.map do |ele, i|
if ele < s
rem_arr = ints.from(i + 1)
rem = s - ele
[ele, rem] if rem_arr.include?(rem)
end
end.compact.last
end
One liner (the fastest?)
ary = [10, 0, 8, 5, 2, 7, 3, 5, 5]
sum = 10
def sum_pairs(ary, sum)
ary.map.with_index { |e, i| [e, i] }.combination(2).to_a.keep_if { |a| a.first.first + a.last.first == sum }.map { |e| [e, e.max { |a, b| a.last <=> b.last }.last] }.min { |a, b| a.last <=> b.last }.first.map{ |e| e.first }
end
Yes, it's not really readable, but if you add methods step by step starting from ary.map.with_index { |e, i| [e, i] } it's easy to understand how it works.

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

Best way to partition a sorted array into arrays of contiguous numbers?

Is there an easy way or a method to partition an array into arrays of contiguous numbers in Ruby?
[1,2,3,5,6,8,10] => [[1,2,3],[5,6],[8],[10]]
I can make some routine for that but wonder if there's a quick way.
Sam
I like to inject:
numbers = [1, 2, 3, 5, 6, 8, 10]
contiguous_arrays = []
contiguous_arrays << numbers[1..-1].inject([numbers.first]) do |contiguous, n|
if n == contiguous.last.succ
contiguous << n
else
contiguous_arrays << contiguous
[n]
end
end
#=> [[1, 2, 3], [5, 6], [8], [10]]
A smörgåsbord of approaches, with:
arr = [1,2,3,5,6,8,10]
#1
# If subarray is empty or the current value n is not the last value + 1,
# add the subarray [n] to the collection; else append the current value
# to the last subarray that was added to the collection.
arr.each_with_object([]) { |n,a|
(a.empty? || n != a.last.last+1) ? a << [n] : a[-1] << n }
#=> [[1, 2, 3], [5, 6], [8], [10]]
#2
# Change the value of 'group' to the current value n if it is the first
# element in arr or it is not equal to the previous element in arr + 1,
# then 'chunk' on 'group' and extract the result from the resulting chunked
# array.
arr.map.with_index do |n,i|
group = n if i == 0 || n != arr[i-1] + 1
[n, group]
end.chunk(&:last)
.map { |_,c| c.map(&:first) }
#=> [[1, 2, 3], [5, 6], [8], [10]]
#3
# If n is the last element of arr, append any number other than n+1 to
# a copy of arr and convert to an enumerator. Step though the enumerator
# arr.size times, adding the current value to a subarray b, and using
# 'peek' to see if the next value of 'arr' equals the current value plus 1.
# If it does, add the subarray b to the collecton a and set b => [].
enum = (arr+[arr.last]).to_enum
a, b = [], []
arr.size.times do
curr = enum.next
b << curr
(a << b; b = []) unless curr + 1 == enum.peek
end
end
a
#=> [[1, 2, 3], [5, 6], [8], [10]]
#4
# Add elements n of arr sequentially to an array a, each time first inserting
# an arbitrary separator string SEP when n does not equal the previous value
# of arr + 1, map each element of a to a string, join(' '), split on SEP and
# convert each resulting array of strings to an array of integers.
SEP = '+'
match_val = arr.first
arr.each_with_object([]) do |n,a|
(a << SEP) unless n == match_val
a << n
match_val = n + 1
end.map(&:to_s)
.join(' ')
.split(SEP)
.map { |s| s.split(' ').map(&:to_i) }
#=> [[1, 2, 3], [5, 6], [8], [10]]
All of the above methods work when arr contains negative integers.
arr = [1,2,3,5,6,8,10]
prev = arr[0]
result = arr.slice_before { |e|
prev, prev2 = e, prev
e != prev2.succ
}.entries
p result
Not very original, lifted right out of the Ruby docs actually.
Another method with enumerator:
module Enumerable
def split_if
enum = each
result = []
tmp = [enum.peek]
loop do
v1, v2 = enum.next, enum.peek
if yield(v1, v2)
result << tmp
tmp = [enum.peek]
else
tmp << v2
end
end
result
end
end
[1,2,3,5,6,8,10].split_if {|i,j| j-i > 1}
Or:
class Array
def split_if(&block)
prev_element = nil
inject([[]]) do |results, element|
if prev_element && block.call(prev_element, element)
results << [element]
else
results.last << element
end
prev_element = element
results
end
end
end
Just do it iteratively.
x = [1,2,3,5,6,8,10]
y = []; z = []
(1..x.length - 1).each do |i|
y << x[i - 1]
if x[i] != x[i-1] + 1
z << y
y = []
end
end
y << x[x.length - 1]
z << y
z
# => [[1, 2, 3], [5, 6], [8], [10]]

How do you merge consecutive repeating elements in an array?

I need to merge consecutive repeating elements in an array, such that
[1, 2, 2, 3, 1]
becomes
[1, 2, 3, 1]
#uniq doesn't work for this purpose. Why? Because #uniq will produce this:
[1, 2, 3]
There is a abstraction in the core that pretty much does the job, Enumerable#chunk:
xs = [1, 2, 2, 3, 3, 3, 1]
xs.chunk { |x| x }.map(&:first)
#=> [1, 2, 3, 1]
def remove_consecutive_duplicates(xs)
[xs.first] + xs.each_cons(2).select do |x,y|
x != y
end.map(&:last)
end
remove_consecutive_duplicates([1, 2, 2, 3, 1])
#=> [1,2,3,1]
This returns a new array like uniq does and works in O(n) time.
sepp2k's answer is already accepted, but here are some alternatives:
# Because I love me some monkeypatching
class Array
def remove_consecutive_duplicates_2
# Because no solution is complete without inject
inject([]){ |r,o| r << o unless r.last==o; r }
end
def remove_consecutive_duplicates_3
# O(2n)
map.with_index{ |o,i| o if i==0 || self[i-1]!=o }.compact
end
def remove_consecutive_duplicates_4
# Truly O(n)
result = []
last = nil
each do |o|
result << o unless last==o
last = o
end
result
end
end
And although performance is not everything, here are some benchmarks:
Rehearsal --------------------------------------------
sepp2k     2.740000   0.010000   2.750000 (  2.734665)
Phrogz_2   1.410000   0.000000   1.410000 (  1.420978)
Phrogz_3   1.520000   0.020000   1.540000 (  1.533197)
Phrogz_4   1.000000   0.000000   1.000000 (  0.997460)
----------------------------------- total: 6.700000sec
               user     system      total        real
sepp2k     2.780000   0.000000   2.780000 (  2.782354)
Phrogz_2   1.450000   0.000000   1.450000 (  1.440868)
Phrogz_3   1.530000   0.020000   1.550000 (  1.539190)
Phrogz_4   1.020000   0.000000   1.020000 (  1.025331)
Benchmarks run on removing duplicates from orig = (0..1000).map{ rand(5) } 10,000 times.
does !uniq not work for what you are doing?
http://ruby-doc.org/docs/ProgrammingRuby/html/ref_c_array.html

Resources