Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: Delete and return an array value based on block find condition

Is there a built-in way to delete a value from an array, based on a block condition returning true, and return the value that was deleted?

This is a simplified version of what I'm trying to do, but it seems like there has to be a better way:

array = [1,2,3,4,5,6,7,8,9,10]

index = array.index {|v| v == 5} # returns index if block is true

value = array.delete_at(index) # deletes and returns element at index

value is then 5

like image 575
Peter Brown Avatar asked Mar 29 '11 23:03

Peter Brown


People also ask

How do I remove a specific value from an array in Ruby?

Ruby | Array delete() operation Array#delete() : delete() is a Array class method which returns the array after deleting the mentioned elements. It can also delete a particular element in the array. Syntax: Array. delete() Parameter: obj - specific element to delete Return: last deleted values from the array.

What does .first do Ruby?

The first() is an inbuilt method in Ruby returns an array of first X elements. If X is not mentioned, it returns the first element only. Parameters: The function accepts X which is the number of elements from the beginning. Return Value: It returns an array of first X elements.

Can you split an array Ruby?

split is a String class method in Ruby which is used to split the given string into an array of substrings based on a pattern specified. Here the pattern can be a Regular Expression or a string. If pattern is a Regular Expression or a string, str is divided where the pattern matches.


3 Answers

You can't update the array in place and get a return of a different set of values that are deleted. You can do the following using delete_if to remove values and capture the ones removed by the logic in the block:

reject = []  => []  content = [1,2,3,4,5,6,7,8,9]  => [1, 2, 3, 4, 5, 6, 7, 8, 9]  content.delete_if {|v| reject << v if v > 5}  => [1, 2, 3, 4, 5]  reject  => [6, 7, 8, 9]  content  => [1, 2, 3, 4, 5]  
like image 170
Wes Avatar answered Sep 29 '22 12:09

Wes


Do you really need to delete items from the original array or are you really just trying to split it into two pieces based on some condition? If the latter, then:

accepted = [ ]
rejected = [ ]
original.each { |e| (want_this_one(e) ? accepted : rejected).push(e) }

or

parts = original.inject({ :accepted => [ ], :rejected => [ ] }) do |accumulator, e|
  if(want_this_one(e))
    accumulator[:accepted].push(e)
  else
    accumulator[:rejected].push(e)
  end
  accumulator
end

And then a simple method wrapper to make it easy to supply a block:

def categorize(array)
  categories = array.inject({ :accepted => [ ], :rejected => [ ] }) do |accumulator, e|
    if(yield e)
      accumulator[:accepted].push(e)
    else
      accumulator[:rejected].push(e)
    end
    accumulator
  end
  return categories[:accepted], categories[:rejected]
end

kept, deleted = categorize([1, 2, 3, 4, 5]) { |n| n % 2 == 0 }
# kept    = [2, 4]
# deleted = [1, 3, 5]

Or you could just use Enumerable#partition to split the array into two pieces.

If you really need to modify the array in-place then this version of Wes's should do the trick:

def slice_out(array)
  dead = [ ]
  array.delete_if do |e|
    if(yield e)
      dead.push(e)
      true
    else
      false  
    end
  end
  dead
end

a = [1,2,3,4]
x = slice_out(a) { |n| n % 2 == 0 }
# a == [1, 3]
# x == [2, 4]
like image 34
mu is too short Avatar answered Sep 29 '22 12:09

mu is too short


Array#extract (Rails 6+)

If you are using Rails, then starting from version 6, there is a method Array#extract!, which does almost what you need.

It removes and returns the elements for which the block returns a true value and modifies the original array.

Please, have a look at the following example:

array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

values = array.extract! { |value| value == 5 }

# array
# => [1, 2, 3, 4, 6, 7, 8, 9, 10]

# values
# => [5]
like image 38
Marian13 Avatar answered Sep 29 '22 10:09

Marian13