Reputation: 10997
I have an array:
array = ['a', 'b', 'c', 'a', 'b', 'a', 'a']
sorted, just to make it easier to look at:
array = ['a', 'a', 'a', 'a', 'b', 'b', 'c']
I want to remove, for example, three of the a
's. array.delete('a')
removes every a
.
The following code 'works' but I think you'll agree it's absolutely hideous.
new_array = array.sort.join.sub!('aaa', '').split(//)
How do I do this more cleanly?
To give a bit more information on what I'm doing here, I have some strings being pushed into an array asynchronously. Those strings can be (and often are) identical to each other. If there are a certain number of those matching strings, an action is triggered, the matching object is removed (like Tetris, I guess), and the process continues.
Before the following code is run, array
could be ['a', 'a', 'a', 'b']
:
while array.count(product[:name]) >= product[:quantity]
# trigger an event
product[:quantity].times do
array.slice!(array.index(product[:name]))
end
end
assuming that product[:name]
is a
and product[:quantity]
is 3
, after the code above runs, array should be ['b']
.
Upvotes: 3
Views: 126
Reputation: 160551
If you want to maintain, or convert, an array so it only one instance of each element, you can use uniq
or a Set instead of an array.
array = ['a', 'b', 'c', 'a', 'b', 'a', 'a']
array.uniq # => ["a", "b", "c"]
require 'set'
array.to_set # => #<Set: {"a", "b", "c"}>
A Set will automatically maintain the uniqueness of all the elements for you, which is useful if you're going to have a huge number of potentially repetitive elements and don't want to accumulate them in memory before doing a uniq
on them.
@sawa mentioned that this looks like an "XY problem", and I agree.
The source of the problem is using an array instead of a hash as your basic container. An array is good when you have a queue or list of things to process in order but it's horrible when you need to keep track of the count of things, because you have to walk that array to find out how many of a certain thing you have. There are ways to coerce the information you want out of an array when you get the array as your source.
Since it looks like he identified the real problem, here are some building blocks to use around the problem.
If you have an array and want to figure out how many different elements there are, and their count:
array = ['a', 'a', 'a', 'a', 'b', 'b', 'c', 'c']
array_count = array.group_by { |i| i }.map{ |k, v| [k, v.size] }.to_h
# => {"a"=>4, "b"=>2, "c"=>2}
From that point it's easy to find out which ones exceed a certain count:
array_count.select{ |k, v| v >= 3 } # => {"a"=>4}
For a quick way to remove all elements of something from the array, after processing you can use a set "difference" operation:
array = ['a', 'a', 'a', 'a', 'b', 'b', 'c']
array -= ['a']
# => ["b", "b", "c", "c"]
or delete_if
:
array.delete_if { |i| i == 'a' }
array # => ["b", "b", "c"]
Upvotes: 2
Reputation: 168101
I think you have an XY-problem. Instead of an array, you should use a hash with number of occurrences as the value.
hash = Hash.new(0)
When you want to add an entity, you should do:
hash["a"] += 1
If you want to limit the number to a certain value, say k, then do:
hash["a"] += 1 unless hash["a"] == k
Upvotes: 2
Reputation: 311393
slice
may be the thing you're looking for:
3.times {array.slice!(array.index('a'))}
Upvotes: 2