3

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'].

5
  • 3.times { |a| a.shift }.... ?/ Commented Jun 1, 2015 at 19:51
  • 2
    If you want to keep only one entry for each value you may use uniq (or uniq!) Commented Jun 1, 2015 at 19:51
  • Give us an example of the array prior to triggering your code, and what it's supposed to look like afterwards. (input data and output data in other words.) Commented Jun 1, 2015 at 21:37
  • @theTinMan adding now Commented Jun 2, 2015 at 9:34
  • @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. Do you create the array? Commented Jun 2, 2015 at 18:03

3 Answers 3

2

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
Sign up to request clarification or add additional context in comments.

5 Comments

A Hash or a Set. Sets are designed for this exact problem.
Forgive me, but I am not very familiar with Ruby's Set. Is there some way to extract the number of instances of a specific item in the set? I would assume a set would consider each entry to have a count of 1, while it seems that maintaining multiple instances or a count of instances for @dax's problem is part of the goal. As far as I can tell a set will not provide that functionality.
A Set only contains unique items. It's impossible to have duplicates, just as it's impossible to have duplicate keys in a Hash. A hash is appropriate for keeping a count.
@sawa, I agree this is an XY problem and I think using a hash with the count as a base object is the best starting point. Flesh this out showing how to trigger when the count is exceeded and you'll have a winner.
thanks sawa - this adds an extra step in mapping the array i get to a hash, but it does make the iteration much cleaner and more meaningful.
2

slice may be the thing you're looking for:

3.times {array.slice!(array.index('a'))}

2 Comments

so you're finding the index of a given object, then slicing that object, and repeating however many times it needs to be taken off. nice approach!
You should remember to check if your item exists or you'll get a TypeError trying to convert nil to an index to slice: 3.times { arr.slice!(arr.index('a')) if arr.include? 'a' }
2

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"]

7 Comments

In this case, I need the multiple matching objects so uniq won't work.
@dax Are you saying that you only want it to apply to 'a' but not necessarily 'b'?
@MarkThomas, no - i'll edit the original post so you can see what i'm doing more accurately.
It's really important to supply a complete and thorough example on the first pass. Not doing that results in a lot of wasted work and takes a lot longer to get a usable answer.
@theTinMan, sorry about that - I always struggle in striking a good balance between detailed and not overly complex
|

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.