Bye
Bye

Reputation: 768

Ruby can not remove elements in array

For the piece of code below.
I want to remove element which is equals to 3 from an array. But the code only removed the first element. When I debug the code,
I found that the iterator only loops though the array one time instead of two.
I am not sure what causes the problem. Any help will be appreciated.

nums = [3,3]
def remove_element(nums, val)
    nums.each_with_index do |num,index|
        if num == val
            nums.slice!(index)
        end
    end
    nums.length
end

remove_element(nums,3)

Upvotes: 0

Views: 175

Answers (3)

ReggieB
ReggieB

Reputation: 8212

As @steenslag has pointed out, the delete method will do what you want:

n = [1,2,3,3,4,5,6,3,4,5,3,2,1,8]
n.delete(3)
n

returns: [1, 2, 4, 5, 6, 4, 5, 2, 1, 8]

It's worth looking at this alternative to your code:

nums = [3,3]
def remove_element(nums, val)
    nums.each_with_index do |num,index|
        nums_before_slice = nums.clone
        if num == val
            sliced = nums.slice!(index)
        end
        puts "nums: #{nums_before_slice}, index: #{index}, sliced: #{sliced.inspect}"
    end
end

remove_element(nums,3)

puts "Result: #{nums.inspect}"

The output will be:

nums: [3, 3], index: 0, sliced: 3
Result: [3]

As you can see, the iteration only happens once, because the second element has been removed before it is time to do the second iteration.

Compare that result to this version of the code:

nums = [3,3]
def remove_element(nums, val)
    nums.clone.each_with_index do |num,index|
        nums_before_slice = nums.clone
        if num == val
            sliced = nums.slice!(index)
        end
        puts "nums: #{nums_before_slice}, index: #{index}, sliced: #{sliced.inspect}"
    end
end

remove_element(nums,3)

puts "Result: #{nums.inspect}"

Which results in:

nums: [3, 3], index: 0, sliced: 3
nums: [3], index: 1, sliced: nil
Result: [3]

This now runs the iteration over a copy of the original nums, but the result is the same, as on the second iteration - there is no second element to be removed.

Upvotes: 3

Oleksandr Holubenko
Oleksandr Holubenko

Reputation: 4440

What about method delete ?

nums = [3,3]
def remove_element(nums, val)
    nums.delete(val)
    nums.length
end
remove_element(nums, 3)
#=> 0

or delete_if ?

nums = [3,3]
def remove_element(nums, val)
    nums.delete_if { |element| element == val }
    nums.length
end
remove_element(nums, 3)
#=> 0

UPD

require 'benchmark'

array = Array.new(100000) { rand(5) }

Benchmark.bm do |x|
  x.report("delete: ") { array.delete(5) }
  x.report("delete_if: ") { array.delete_if { |e| e == 5 } }
  x.report("reject: ") { array.reject! { |e| e == 5 } }
end

#            user       system     total        real
# delete:    0.000000   0.000000   0.000000 (  0.004230)
# delete_if: 0.010000   0.000000   0.010000 (  0.006387)
# reject:    0.010000   0.000000   0.010000 (  0.007543)

Upvotes: 2

user7135007
user7135007

Reputation:

As rightly commented, you have modified the array while iterating it, due to which it removed the first element, and concluded the iteration. If you put a print statment inside nums.each_with_index loop, you will see it gets printed only once.

A better way to remove an element can be using reject method like following:

nums.reject!{|item| item == 3}

Upvotes: 3

Related Questions