0

I'm looking to make a method which detects if the following value in an array is a duplicate, and deletes it if so. It should work for both strings and integers.

For example, given the Array:

arr = ["A", "B", "B", "C", "c", "A", "D", "D"]

Return:

arr = ["A", "B", "C", "c", "A", "D"]

I tried creating an empty Array a, and shovelling the values in, providing the following value was not equal to the current one. I attempted this like so:

arr.each do |x|
  following_value = arr.index(x) + 1
  a << x unless x == arr[following_value]
end

Unfortunately, instead of shovelling one of the duplicate values into the array, it shovelled neither.

arr = ["A", "C", "c", "A"] 

Can anybody help? Bonus points for telling me exactly what went wrong with my method.

Thanks!

5
  • 2
    I there a reason why you don't want to use ["A", "B", "B", "C", "c", "A", "D", "D"].uniq? Commented Jun 28, 2017 at 20:34
  • 1
    @katafrakt, it will remove A, as I understand OP wants to take element if next isn't equal to it Commented Jun 28, 2017 at 20:41
  • @katafrakt: The question is a bit unclear, but it looks like the OP only wants to compact consecutive runs of equal elements. Commented Jun 28, 2017 at 20:43
  • Yes, because I don't wish to return only the unique values. I only want the element to be deleted if it is equal to the previous element. In the example you have quoted, I wish to retain both entries equal to "A". Commented Jun 28, 2017 at 20:49
  • 1
    Your method is not working because arr.index(x) will always return the index of the first matching element. Please note my answer for details Commented Jun 28, 2017 at 21:47

4 Answers 4

3

First at all, here is simpler solution:

> arr.delete_if.with_index { |e, ind| e == arr[ind+1] }
#=> ["A", "B", "C", "c", "A", "D"]

But, this solution will mutate arr. Here are one-line solutions without mutates:

arr.each_with_index.with_object([]) { |(e, ind), res| res << e if e != arr[ind+1] }
arr.each_with_object([]) { |e, res| res << e if res.last != e }

Your problem in this line: a << x unless x == arr[following_value] You say: put this element into result if next element isn't equal to it. So, instead, you can say something like: put this element to result if the last element of the result isn't equal to it:

arr.each do |x|
  a << x unless a.last == x
end
Sign up to request clarification or add additional context in comments.

2 Comments

That solution is perfect, thank you Alex. I forgot all about with_index. I don't want to sound daft, but I'm still not 100% certain why my example didn't work. What I thought would happen is: A != B, so shovel into a; B == B, so don't shovel into a; B != C, so shovel into a. Do you see what I mean? And thanks again, all your examples are super useful. :)
Array#delete_if is the same as Array#reject!, which provides a clue to how your initial method can be changed to avoid mutating arr. :-)
2

I would use select, so you could do something like:

a = ["A", "B", "B", "C", "c", "A", "D", "D"]

# without mutation
b = a.select.with_index { |e, i| a[i+1] != e }
a #=> ["A", "B", "B", "C", "c", "A", "D", "D"]
b #=> ["A", "B", "C", "c", "A", "D"]

# with mutation
a.select!.with_index { |e, i| a[i+1] != e }
a #=> ["A", "B", "C", "c", "A", "D"]

BTW your method is not working because arr.index(x) returns index of first object for which block is true:

arr = ["A", "B", "B", "C", "c", "A", "D", "D"]
arr.each do |x|
  puts "#{x} has index #{arr.index(x)}"
end
A has index 0
B has index 1
B has index 1 # you were expecting 2
C has index 3
c has index 4
A has index 0 # you were expecting 5
D has index 6
D has index 6 # you were expecting 7

3 Comments

Gabriel, both of your solutions are also great. Additionally, thank you so much for explaining why my method wasn't working! It was driving me crazy haha. I really appreciate the explanation. +1
about arr.index(x) - it's wrong, as you can see, OP define following_value as arr.index(x) + 1 and than: x == arr[following_value] :)
@AlexGolubenko arr.index(x) for the first "B" is 1 so following_value is 2 (1 + 1). And arr.index(x) for the second "B" is also 1 so following_value is 2 (1 + 1) too. As I said arr.index(x) returns index of first object for which block is true, and therefore you can't use this method in this case - that's what went wrong with OP method.
1

Here's a succinct alternative:

arr = ["A", "B", "B", "C", "c", "A", "D", "D"]

arr.chunk(&:itself).map(&:first)
# => ["A", "B", "C", "c", "A", "D"]

See it on repl.it: https://repl.it/JGV4/1

Comments

1

Derived from this answer by Cary Swoveland:

def remove_consecs ar
  enum = ar.each
  loop.with_object([]) do |_, arr|
    curr = enum.next
    nxt = arr.last || enum.peek
    arr << curr if curr != nxt
  end
end

remove_consecs ["A", "B", "B", 'D', "C", "c", "A", "D", "D"]
#=> ["A", "B", "D", "C", "c", "A", "D"]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.