5

I have a Set of elements from which I want to take and remove the first few elements a bunch of times. Is there a shorter way (so one operation instead of two) to do that than this:

require 'set'
s = Set[1, 2, 3, 4]       # => #<Set: {1, 2, 3, 4}> 

first_two = s.take(2)     # => [1, 2]
s.subtract(first_two)     # => #<Set: {3, 4}>

(So basically I'm wondering whether I'm overlooking a shift for Sets)

Confusion
  • 16,256
  • 8
  • 46
  • 71
  • 3
    You could easily wrap that in a helper (if you're interested in usability). Or are you also interested in atomicity? – Sergio Tulentsev Jan 30 '13 at 14:39
  • I was mainly interested in usability, but your comment about atomicity may be very relevant for anyone stumbling in this question. If you need that, I think the nest option is to decorate the Set with a class of your own. Either that, or reimplement Set in C. – Confusion Jan 31 '13 at 21:21
  • Or simply monkeypatch `Set` and use `Hash#delete` which is atomic. – Marc-André Lafortune Jan 31 '13 at 22:51
  • As long as the `take` and the `delete` are two seperate Ruby operations, another thread can be scheduled to run in between them, whether or not they are in a monkeypatched Set method. – Confusion Feb 01 '13 at 07:05

4 Answers4

5

You could add a new method take! (or remove! or whatever name seems appropriate) to the Set class:

class Set
  def take!(args)
    taken = self.take(args)
    self.subtract(taken)
    return taken
  end
end

a = Set[1, 2, 3, 4] # <Set: {1, 2, 3, 4}>
a.take!(2) # <Set: {1, 2}>
a # <Set: {3, 4}>
Conkerchen
  • 720
  • 1
  • 4
  • 15
3

There is no shorter way using builtin methods.

There is an open feature request for a method to return and remove one element; you may want to help refine the API?

Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
1

From http://www.ruby-doc.org/stdlib-1.9.3/libdoc/set/rdoc/Set.html:

Set implements a collection of unordered values with no duplicates. This is a hybrid of Array's intuitive inter-operation facilities and Hash's fast lookup.

It would be odd and probably illogical to implement methods like shift and pop on an object that knows nothing about index.

ichigolas
  • 7,595
  • 27
  • 50
  • I think the doc means that the elements are not sorted, in contrast with `SortedSet`. Order in a set is guaranteed (in 1.9+), as it relies on Hash. – Marc-André Lafortune Jan 30 '13 at 20:23
  • I said 'the first few' in the question, but actually I don't care which elements they are. I just want to remove `n` elements from the Set and do something with them. – Confusion Jan 31 '13 at 21:14
  • `take` does return in a specific order, I believe the same order might be used by `pop`. – phil pirozhkov Mar 01 '19 at 07:17
0

I'm late to the party, but here's my solution. Convert the set to an array first, and then all Enumerable methods are available. Take 2 from the array and then be sure to also remove the from the set. The two values from the set get removed and returned.

require 'set'
s = Set[1, 2, 3, 4]     # => #<Set: {1, 2, 3, 4}> 

first_two = s.to_a.take(2).tap {|a| s.subtract(a)}  # => [1, 2]
s                       # => #<Set: {3, 4}>
manu3569
  • 183
  • 2
  • 3