# Help to refactor my code

I want to delete the first 3 duplicate entries -

I wrote a code :

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.delete_at(ind)
end
a
end

del_first_three([3,4,5,3,3])

But at the method end I put `a`, to return the resultant array, which I
don't like. So I took the help of `#tap` as below :

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.tap { |ob| ob.delete_at(ind) }
end
end

del_first_three([3,4,5,3,3]) # => 3

But it is also helpless, as `Integer#times` returns `self`. Is there any
method to meet my need. I am actually looking for a method, which work
as `File:open` with block.

···

--
Posted via http://www.ruby-forum.com/.

Dear Arup,

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.delete_at(ind)
end
a
end

First step:

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?

a.delete_if { |e| e == num_to_del }
end

# All the part from 3.times do block on got substituted.
# Now is easy to figure out that the first return is also redundant

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
a.delete_if { |e| e == num_to_del }
end

I hope it helps.

Best regards,
Abinoam Jr.

···

On Sun, Feb 9, 2014 at 7:13 AM, Arup Rakshit <lists@ruby-forum.com> wrote:

I want to delete the first 3 duplicate entries -

I wrote a code :

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.delete_at(ind)
end
a
end

del_first_three([3,4,5,3,3])

But at the method end I put `a`, to return the resultant array, which I
don't like. So I took the help of `#tap` as below :

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.tap { |ob| ob.delete_at(ind) }
end
end

del_first_three([3,4,5,3,3]) # => 3

But it is also helpless, as `Integer#times` returns `self`. Is there any
method to meet my need. I am actually looking for a method, which work
as `File:open` with block.

--
Posted via http://www.ruby-forum.com/.

Abinoam Jr. wrote in post #1136118:

Dear Arup,

# All the part from 3.times do block on got substituted.
# Now is easy to figure out that the first return is also redundant

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
a.delete_if { |e| e == num_to_del }
end

Nice re-factoring. But the point is, I want to delete first 3
duplicates. But `a.delete_if { |e| e == num_to_del }` will delete all
the elements, which has occurrence 3 or more than 3.

My point is if any element occurs 4 times, I would delete only first 3
occurrence.

···

--
Posted via http://www.ruby-forum.com/.

Dear Arup,

Sorry if I misunderstood the requirements.
But, I think there is two "FIRST THREE" in play over here.

For [ 3, 4, 5, 3, 3, 4, 4, 4, 5 ] what do you expect to get in return?

Because the line

num_to_del = a.find { |e| a.count(e) >= 3 }

Finds the only the first number in array that has three duplicates.
So it will find 3 and not 4. ( I misunderstood this as being a "First
Three Duplicate Number" )

After that, do you want do delete the first 3 occurrences of the number 3 ?
So, if there's only 3 number 3s. It will rest none of them?
But, with the number five that has only 2 elements. it will rest the
two of them? (more than the 3 number)
And the 4 number, that has more than three duplicates, it rest all the
Or it will rest 1 element of them, so that it has 4 and 4-3 = 1?
The sorting order of the elements are important?
If you have one more triplet like in [3, 4, 5, 3, 3, 3, 3, 3] with 6 number 3s?
Do you want the second triplet to be deleted also?

Could you state more clearly your requirements?

Best regards,
Abinoam Jr.

···

On Sun, Feb 9, 2014 at 7:47 AM, Arup Rakshit <lists@ruby-forum.com> wrote:

Abinoam Jr. wrote in post #1136118:

Dear Arup,

# All the part from 3.times do block on got substituted.
# Now is easy to figure out that the first return is also redundant

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
a.delete_if { |e| e == num_to_del }
end

Nice re-factoring. But the point is, I want to delete first 3
duplicates. But `a.delete_if { |e| e == num_to_del }` will delete all
the elements, which has occurrence 3 or more than 3.

My point is if any element occurs 4 times, I would delete only first 3
occurrence.

--
Posted via http://www.ruby-forum.com/.

Abinoam Jr. wrote in post #1136120:

Dear Arup,

Sorry if I misunderstood the requirements.
But, I think there is two "FIRST THREE" in play over here.

For [ 3, 4, 5, 3, 3, 4, 4, 4, 5 ] what do you expect to get in return?

Because the line

num_to_del = a.find { |e| a.count(e) >= 3 }

Finds the only the first number in array that has three duplicates.
So it will find 3 and not 4. ( I misunderstood this as being a "First
Three Duplicate Number" )

Yes with your example target element would be 3.

After that, do you want do delete the first 3 occurrences of the number
3 ?

Yes, exactly. output should be [ 4, 5, 4, 4, 4, 5 ]

So, if there's only 3 number 3s. It will rest none of them?
But, with the number five that has only 2 elements. it will rest the
two of them? (more than the 3 number)

If there is 5 3's, first 3 occurrence will be deleted, rest will be as
it is.

And the 4 number, that has more than three duplicates, it rest all the

Nothing to be done with, as we got first 3, which has met the condition
`>=3`

The sorting order of the elements are important?

elements order should be preserved there, no need to sort.

If you have one more triplet like in [3, 4, 5, 3, 3, 3, 3, 3] with 6
number 3s?

Expected output is [4, 5, 3, 3, 3].

···

Best regards,
Abinoam Jr.

--
Posted via http://www.ruby-forum.com/.

Dear Arup,

I don't know if the code bellow could read better in your opinion.

But, from your first example you could pass "a" as an object on the iteration.

def del_first_three_refactored_2(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times.with_object(a) do |turn, obj|
ind = obj.index { |e| e == num_to_del }
obj.delete_at(ind)
end
end

Does it fit?

And, I could come up with another approach that is faster than your
first attempts.
I just don't know if it will read well for your taste.

def del_first_three_refactored(ary)
el_indices = Hash.new { |hash, key| hash[key] = Array.new }
ary.each_with_index do |el, ix|
el_indices[el].push ix
if el_indices[el].size >= 3
el_indices[el].each_with_index do |del_ix, offset|
ary.delete_at(del_ix - offset)
end
break ary
end
end
end

For an array with more than 100_000 elements

Original (1) lasted 4.272368536 seconds
Original (2) lasted 4.282821329 seconds
Refactored lasted 0.618384996 seconds

Best regards,
Abinoam Jr.

···

On Sun, Feb 9, 2014 at 8:19 AM, Arup Rakshit <lists@ruby-forum.com> wrote:

Abinoam Jr. wrote in post #1136120:

Dear Arup,

Sorry if I misunderstood the requirements.
But, I think there is two "FIRST THREE" in play over here.

For [ 3, 4, 5, 3, 3, 4, 4, 4, 5 ] what do you expect to get in return?

Because the line

num_to_del = a.find { |e| a.count(e) >= 3 }

Finds the only the first number in array that has three duplicates.
So it will find 3 and not 4. ( I misunderstood this as being a "First
Three Duplicate Number" )

Yes with your example target element would be 3.

After that, do you want do delete the first 3 occurrences of the number
3 ?

Yes, exactly. output should be [ 4, 5, 4, 4, 4, 5 ]

So, if there's only 3 number 3s. It will rest none of them?
But, with the number five that has only 2 elements. it will rest the
two of them? (more than the 3 number)

If there is 5 3's, first 3 occurrence will be deleted, rest will be as
it is.

And the 4 number, that has more than three duplicates, it rest all the

Nothing to be done with, as we got first 3, which has met the condition
`>=3`

The sorting order of the elements are important?

elements order should be preserved there, no need to sort.

If you have one more triplet like in [3, 4, 5, 3, 3, 3, 3, 3] with 6
number 3s?

Expected output is [4, 5, 3, 3, 3].

Best regards,
Abinoam Jr.

--
Posted via http://www.ruby-forum.com/.

Abinoam Jr. wrote in post #1136125:

Dear Arup,

I don't know if the code bellow could read better in your opinion.

But, from your first example you could pass "a" as an object on the
iteration.

def del_first_three_refactored_2(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times.with_object(a) do |turn, obj|
ind = obj.index { |e| e == num_to_del }
obj.delete_at(ind)
end
end

Does it fit?

This is good! Actually I forgot that `#times` give us `Enumerator`
object.

Thank you very much. It reads nice to me.

···

--
Posted via http://www.ruby-forum.com/.

Great and you're welcome!

One more thing.
I forgot to pass the url of a gist created to benchmark the 3 approachs.

Abinoam Jr.

···

On Sun, Feb 9, 2014 at 9:38 AM, Arup Rakshit <lists@ruby-forum.com> wrote:

Abinoam Jr. wrote in post #1136125:

Dear Arup,

I don't know if the code bellow could read better in your opinion.

But, from your first example you could pass "a" as an object on the
iteration.

def del_first_three_refactored_2(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times.with_object(a) do |turn, obj|
ind = obj.index { |e| e == num_to_del }
obj.delete_at(ind)
end
end

Does it fit?

This is good! Actually I forgot that `#times` give us `Enumerator`
object.

Thank you very much. It reads nice to me.

--
Posted via http://www.ruby-forum.com/.

Abinoam Jr. wrote in post #1136127:

Great and you're welcome!

One more thing.
I forgot to pass the url of a gist created to benchmark the 3 approachs.

https://gist.github.com/abinoam/8898249

Abinoam Jr.

FYI - I wrote the below code, but it wouldn't give the desired result as
I mentioned in main post.

def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.tap { |ob| ob.delete_at(ind) }
end
end

del_first_three([3,4,5,3,3]) # => 3 # <~~~ see the output,due to #times.

So don't consider this.

···

--
Posted via http://www.ruby-forum.com/.