How to convert a three-line Ruby method into one - ruby

I have a simple method that iterates through an array and returns a duplicate. (Or duplicates)
def find_dup(array)
duplicate = 0
array.each { |element| duplicate = element if array.count(element) > 1}
duplicate
end
It works, but I'd like to express this more elegantly.
The reason it is three lines is that the variable "duplicate", which the method must return, is not visible to the method if I introduce it inside the block, i.e,
def find_dup(array)
array.each { |element| duplicate = element if array.count(element) > 1}
duplicate
end
I've tried a few ways to define "duplicate" as the result of a block, but to no avail.
Any thoughts?

It's a little too much to do cleanly in a one-liner, but this is a more
efficient solution.
def find_dups(arr)
counts = Hash.new { |hash,key| hash[key] = 0 }
arr.each_with_object(counts) do |x, memo|
memo[x] += 1
end.select { |key,val| val > 1 }.keys
end
The Hash.new call instantiates a hash where the default value is 0.
each_with_object modifies this hash to track the count of each element in arr, then at the
end the filter is used to select only those having a count greater than one.
The benefit of this approach over a solution using Array#includes? or Array#count is that it only scans the array a single time. Thus it is a O(N) time instead of O(N^2).

Your method is only finding the last duplicate in the array. If you want all the duplicates, I would do something like this:
def find_dups(arr)
dups = Hash.new { |h, k| h[k] = 0 }
arr.each { |el| dups[el] += 1 }
dups.select { |k, v| v > 1 }.keys
end

If what you really want is a one-liner that isn't concerned with big-O complexity and only returns the last duplicate in the array, I would do this:
def find_last_dup(arr)
arr.reverse_each { |el| return el if arr.count(el) > 1 }
end

You can do this as one line and it flows a bit nicer. Though this would find the first instance of a duplicate whereas your code is returning the last instance of a duplicate, not sure if that's part of your requirement.
def find_dup(array)
array.group_by { |value| value }.find { |_, groups| groups.count > 1 }.first
end
Also, note that making things one line doesn't strictly mean is better. I'd find the code more readable split over more lines, but that's just my opinion.
def find_dup(array)
array.group_by { |value|
value
}.find { |_, groups|
groups.count > 1
}.first
end

Just want to add one more approach to the mix.
def find_last_dup(arr)
arr.reverse_each.detect { |x| arr.count(x) > 1 }
end
Alternatively, you can get linear time complexity in two lines.
def find_last_dup(arr)
freq = arr.each_with_object(Hash.new(0)) { |x, obj| obj[x] += 1 }
arr.reverse_each.detect { |x| freq[x] > 1 }
end
For the sake of argument, the latter approach can be reduced to one line as well, but this would be unidiomatic and confusing.
def find_last_dup(arr)
arr.each_with_object(Hash.new(0)) { |x, obj| obj[x] += 1 }
.tap do |freq| return arr.reverse_each.detect { |x| freq[x] > 1 } end
end

Given:
> a
=> [8, 5, 6, 6, 5, 8, 6, 1, 9, 7, 2, 10, 7, 7, 3, 4]
You can group the dups together:
> a.uniq.each_with_object(Hash.new(0)) {|e, h| c=a.count(e); h[e]=c if c>1}
=> {8=>2, 5=>2, 6=>3, 7=>3}
Or,
> a.group_by{ |e| e}.select{|k,v| v if v.length>1}
=> {8=>[8, 8], 5=>[5, 5], 6=>[6, 6, 6], 7=>[7, 7, 7]}
In each case, the order of the result is based on the order of the elements in a that have dups. If you just want the first:
> a.group_by{ |e| e}.select{|k,v| v if v.length>1}.first
=> [8, [8, 8]]
Or last:
> a.group_by{ |e| e}.select{|k,v| v if v.length>1}.to_a.last
=> [7, [7, 7, 7]]
If you want to 'fast forward' to the first value that has a dup, you can use drop_while:
> b=[1,2,3,4,5,4,5,6]
> b.drop_while {|e| b.count(e)==1 }[0]
=> 4
Or the last:
> b.reverse.drop_while {|e| b.count(e)==1 }[0]
=> 5

def find_duplicates(array)
array.dup.uniq.each { |element| array.delete_at(array.index(element)) }.uniq
end
The above method find_duplicates duplicated the input array and deletes the first occurrence of all the elements, leaving the array with only remaining occurrences of the duplicate elements.
Example:
array = [1, 2, 3, 4, 3, 4, 3]
=> [1, 2, 3, 4, 3, 4, 3]
find_duplicates(array)
=> [3, 4]

Related

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.

How can I remove duplicates in an array without using `uniq`?

The object of my coding exercise is to get rid of duplicates in an array without using the uniq method. Here is my code:
numbers = [1, 4, 2, 4, 3, 1, 5]
def my_uniq(array)
sorted = array.sort
count = 1
while count <= sorted.length
while true
sorted.delete_if {|i| i = i + count}
count += 1
end
end
return sorted
end
When I run this, I get an infinite loop. What is wrong?
Can I use delete the way that I am doing with count?
How will it execute? Will count continue until the end of the array before the method iterates to the next index?
I did this with each or map, and got the same results. What is the best way to do this using each, delete_if, map, or a while loop (with a second loop that compares against the first one)?
Here is a clearly written example.
numbers = [1, 4, 2, 4, 3, 1, 5]
def remove_duplicates(array)
response = Array.new
array.each do |number|
response << number unless response.include?(number)
end
return response
end
remove_duplicates(numbers)
As others pointed out, your inner loop is infinite. Here's a concise solution with no loops:
numbers.group_by{|n| n}.keys
You can sort it if you want, but this solution doesn't require it.
the problem is that the inner loop is an infinite loop:
while true
sorted.delete_if {|i| i = i + count}
count += 1
end #while
you can probably do what you are doing but it's not eliminating duplicates.
one way to do this would be:
numbers = [1, 4, 2, 4, 3, 1, 5]
target = []
numbers.each {|x| target << x unless target.include?(x) }
puts target.inspect
to add it to the array class:
class ::Array
def my_uniq
target = []
self.each {|x| target << x unless target.include?(x) }
target
end
end
now you can do:
numbers = [1, 4, 2, 4, 3, 1, 5]
numbers.my_uniq
You count use Set that acts like an array with does not allow duplicates:
require 'set'
numbers = [1, 4, 2, 4, 3, 1, 5]
Set.new(numbers).to_a
#=> [1, 4, 2, 3, 5]
Try using Array#& passing the array itself as parameter:
x = [1,2,3,3,3]
x & x #=> [1,2,3]
This is one of the answer. However, I do not know how much of performance issue it takes to return unique
def my_uniq(ints)
i = 0
uniq = []
while i < ints.length
ints.each do |integers|
if integers == i
uniq.push(integers)
end
i += 1
end
end
return uniq
end

Find all max of elements of an array [duplicate]

This question already has answers here:
Returning all maximum or minimum values that can be multiple
(3 answers)
Closed 8 years ago.
Suppose I have a array, namely arr: [1, 2, 3, 4, 8, 8], and I want to find all max elements in this array:
arr.allmax # => [8, 8]
Is there a built-in method combinations to solve this? I don't like to monkey patch as I am doing now:
class Array
def allmax
max = self.max
self.select { |e| e == max }
end
end
Monkey patch is not a good idea, I could just do:
some_array.select { |e| e == some_array.max }
and it will work as allmax. Thanks for all answers and comments for inspirations.
Here's a fun way to do it.
arr.sort!.slice arr.index(arr[-1]) || 0..-1
Sort the array, then find the leftmost index of the array which matches the rightmost index of the array, and take the subslice that matches that range (or the range 0..-1 if the array is empty).
This one is kind of fun in that it requires no intermediate arrays, though it does mutate the input to achieve the one-liner.
Here is one way :
2.1.0 :006 > arr = [1, 2, 3, 4, 8, 8]
=> [1, 2, 3, 4, 8, 8]
2.1.0 :007 > arr.group_by { |i| i }.max.last
=> [8, 8]
2.1.0 :008 >
Here is a method :-
def all_max(arr)
return [] if arr.empty?
arr.group_by { |i| i }.max.last
end
Another way:
def all_max(arr)
return [] if arr.empty?
mx = arr.max
[mx] * arr.count { |e| e == mx }
end
all_max([1, 2, 3, 4, 8, 8])
#=> [8, 8]
To construct the array in a single pass, you could do this:
arr.each_with_object([]) do |e,a|
if a.empty?
a << e
else
case e <=> a.first
when 0 then a << e
when 1 then a.replace([e])
end
end
end

How to map and remove nil values in Ruby

I have a map which either changes a value or sets it to nil. I then want to remove the nil entries from the list. The list doesn't need to be kept.
This is what I currently have:
# A simple example function, which returns a value or nil
def transform(n)
rand > 0.5 ? n * 10 : nil }
end
items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]
I'm aware I could just do a loop and conditionally collect in another array like this:
new_items = []
items.each do |x|
x = transform(x)
new_items.append(x) unless x.nil?
end
items = new_items
But it doesn't seem that idiomatic. Is there a nice way to map a function over a list, removing/excluding the nils as you go?
You could use compact:
[1, nil, 3, nil, nil].compact
=> [1, 3]
I'd like to remind people that if you're getting an array containing nils as the output of a map block, and that block tries to conditionally return values, then you've got code smell and need to rethink your logic.
For instance, if you're doing something that does this:
[1,2,3].map{ |i|
if i % 2 == 0
i
end
}
# => [nil, 2, nil]
Then don't. Instead, prior to the map, reject the stuff you don't want or select what you do want:
[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
i
}
# => [2]
I consider using compact to clean up a mess as a last-ditch effort to get rid of things we didn't handle correctly, usually because we didn't know what was coming at us. We should always know what sort of data is being thrown around in our program; Unexpected/unknown data is bad. Anytime I see nils in an array I'm working on, I dig into why they exist, and see if I can improve the code generating the array, rather than allow Ruby to waste time and memory generating nils then sifting through the array to remove them later.
'Just my $%0.2f.' % [2.to_f/100]
Try using reduce or inject.
[1, 2, 3].reduce([]) { |memo, i|
if i % 2 == 0
memo << i
end
memo
}
I agree with the accepted answer that we shouldn't map and compact, but not for the same reasons.
I feel deep inside that map then compact is equivalent to select then map. Consider: map is a one-to-one function. If you are mapping from some set of values, and you map, then you want one value in the output set for each value in the input set. If you are having to select before-hand, then you probably don't want a map on the set. If you are having to select afterwards (or compact) then you probably don't want a map on the set. In either case you are iterating twice over the entire set, when a reduce only needs to go once.
Also, in English, you are trying to "reduce a set of integers into a set of even integers".
Ruby 2.7+
There is now!
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
In your case, as the block evaluates to falsey, simply:
items.filter_map { |x| process_x url }
"Ruby 2.7 adds Enumerable#filter_map" is a good read on the subject, with some performance benchmarks against some of the earlier approaches to this problem:
N = 100_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
x.report("select + map") { N.times { enum.select { |i| i.even? }.map{ |i| i + 1 } } }
x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
x.report("filter_map") { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end
# Rehearsal -------------------------------------------------
# select + map 8.569651 0.051319 8.620970 ( 8.632449)
# map + compact 7.392666 0.133964 7.526630 ( 7.538013)
# filter_map 6.923772 0.022314 6.946086 ( 6.956135)
# --------------------------------------- total: 23.093686sec
#
# user system total real
# select + map 8.550637 0.033190 8.583827 ( 8.597627)
# map + compact 7.263667 0.131180 7.394847 ( 7.405570)
# filter_map 6.761388 0.018223 6.779611 ( 6.790559)
Definitely compact is the best approach for solving this task. However, we can achieve the same result just with a simple subtraction:
[1, nil, 3, nil, nil] - [nil]
=> [1, 3]
In your example:
items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]
it does not look like the values have changed other than being replaced with nil. If that is the case, then:
items.select{|x| process_x url}
will suffice.
If you wanted a looser criterion for rejection, for example, to reject empty strings as well as nil, you could use:
[1, nil, 3, 0, ''].reject(&:blank?)
=> [1, 3, 0]
If you wanted to go further and reject zero values (or apply more complex logic to the process), you could pass a block to reject:
[1, nil, 3, 0, ''].reject do |value| value.blank? || value==0 end
=> [1, 3]
[1, nil, 3, 0, '', 1000].reject do |value| value.blank? || value==0 || value>10 end
=> [1, 3]
You can use #compact method on the resulting array.
[10, nil, 30, 40, nil].compact => [10, 30, 40]
each_with_object is probably the cleanest way to go here:
new_items = items.each_with_object([]) do |x, memo|
ret = process_x(x)
memo << ret unless ret.nil?
end
In my opinion, each_with_object is better than inject/reduce in conditional cases because you don't have to worry about the return value of the block.
One more way to accomplish it will be as shown below. Here, we use Enumerable#each_with_object to collect values, and make use of Object#tap to get rid of temporary variable that is otherwise needed for nil check on result of process_x method.
items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}
Complete example for illustration:
items = [1,2,3,4,5]
def process x
rand(10) > 5 ? nil : x
end
items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}
Alternate approach:
By looking at the method you are calling process_x url, it is not clear what is the purpose of input x in that method. If I assume that you are going to process the value of x by passing it some url and determine which of the xs really get processed into valid non-nil results - then, may be Enumerabble.group_by is a better option than Enumerable#map.
h = items.group_by {|x| (process x).nil? ? "Bad" : "Good"}
#=> {"Bad"=>[1, 2], "Good"=>[3, 4, 5]}
h["Good"]
#=> [3,4,5]

How to return the index of the array items occuring under a certain threshold

In Ruby, given an array of elements, what is the easiest way to return the indices of the elements that are not identical?
array = ['a','b','a','a','a','c'] #=> [1,5]
Expanded question:
Assuming that the identity threshold is based on the most frequent element in the array.
array = ['a','c','a','a','a','d','d'] #=> [1,5,6]
For an array with two equally frequent elements, return the indices of either of the 2 elements. e.g.
array = ['a','a','a','b','b','b'] #=>[0,1,2] or #=> [3,4,5]
Answer edited after question edit:
def idx_by_th(arr)
idx = []
occur = arr.inject(Hash.new(0)) { |k,v| k[v] += 1; k }
th = arr.sort_by { |v| occur[v] }.last
arr.each_index {|i| idx << i if arr[i]!=th}
idx
end
idx_by_th ['a','b','a','a','a','c'] # => [1, 5]
idx_by_th ['a','c','a','a','a','d','d'] # => [1, 5, 6]
idx_by_th ['a','a','a','b','b','b'] # => [0, 1, 2]
These answers are valid for the first version of the question:
ruby < 1.8.7
def get_uniq_idx(arr)
test=[]; idx=[]
arr.each_index do |i|
idx << i if !(arr[i+1..arr.length-1] + test).include?(arr[i])
test << arr[i]
end
return idx
end
puts get_uniq_idx(['a','b','a','a','a','c']).inspect # => [1, 5]
ruby >= 1.8.7:
idxs=[]
array.each_index {|i| idxs<<i if !(array.count(array[i]) > 1)}
puts idxs.inspect # => [1, 5]
It's not quite clear what you're looking for, but is something like this what you want?
array = ['a','b','a','a','a','c']
array.uniq.inject([]) do |arr, elem|
if array.count(elem) == 1
arr << array.index(elem)
end
arr
end
# => [1,5]

Resources