Take and remove elements from a Ruby Set in one operation - ruby

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)

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}>

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?

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.

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}>

Related

Generate Set from Array with the bounds

I want to know is some sort of "Array.to_range" method in order to create some Range from an Array of two elements.
I know that i could perform something like this:
a = [1,5]
Set.new(a[0]..a[1])
=> #<Set: {1, 2, 3, 4, 5}>
This a good solution, however i try to synthesize more, to archive something like this:
a = [1,5]
Set.new(a.to_range)
because if i do this, generate a Set with only 2 elements and i want the full range of elements.
a = [1,5]
Set.new(a)
=> #<Set: {1, 5}>
and obviously
{1, 5} != {1, 2, 3, 4, 5}
So, any idea how can i synthesize this expression more?
Set.new(a[0]..a[1])
I think this give you what you are looking for:
a = [1,5]
Set.new(Range.new(*a)) # => #<Set: {1, 2, 3, 4, 5}>

Ruby variable behaviour when calling a method

I'm pretty good at getting answers from google, but I just don't get this. In the following code, why does variable 'b' get changed after calling 'addup'? I think I understand why 'a' gets changed (although its a bit fuzzy), but I want to save the original array 'a' into 'b', run the method on 'a' so I have two arrays with different content. What am I doing wrong?
Thanks in advance
def addup(arr)
i=0
while i< arr.length
if arr[i]>3
arr.delete_at(i)
end
i += 1
end
return arr
end
a = [1,2,3,4]
b = a
puts "a=#{a}" # => [1,2,3,4]
puts "b=#{b}" # => [1,2,3,4]
puts "addup=#{addup(a)}" # => [1,2,3]
puts "a=#{a}" # => [1,2,3]
puts "b=#{b}" # => [1,2,3]
Both a and b hold a reference to the same array object in memory. In order to save the original array in b, you'd need to copy the array.
a = [1,2,3,4] # => [1, 2, 3, 4]
b = a # => [1, 2, 3, 4]
c = a.dup # => [1, 2, 3, 4]
a.push 5 # => [1, 2, 3, 4, 5]
a # => [1, 2, 3, 4, 5]
b # => [1, 2, 3, 4, 5]
c # => [1, 2, 3, 4]
For more information on why this is happening, read Is Ruby pass by reference or by value?
but I want to save the original array 'a' into 'b'
You are not saving the original array into b. Value of a is a reference to an array. You are copying a reference, which still points to the same array. No matter which reference you use to mutate the array, the changes will be visible through both references, because, again, they point to the same array.
To get a copy of the array, you have to explicitly do that. For shallow arrays with primitive values, simple a.dup will suffice. For structures which are nested or contain references to complex objects, you likely need a deep copy. Something like this:
b = Marhal.load(Marshal.dump(a))
In the following code, why does variable 'b' get changed after calling 'addup'?
The variable doesn't get changed. It still references the exact same array it did before.
There are only two ways to change a variable in Ruby:
Assignment (foo = :bar)
Reflection (Binding#local_variable_set, Object#instance_variable_set, Module#class_variable_set, Module#const_set)
Neither of those is used here.
I think I understand why 'a' gets changed (although its a bit fuzzy)
a doesn't get changed either. It also still references the exact same array it did before. (Which, incidentally, is the same array that b references.)
The only thing which does change is the internal state of the array that is referenced by both a and b. So, if you really understand why the array referenced by a changes, then you also understand why the array referenced by b changes, since it is the same array. There is only one array in your code.
The immediate problem with your code is that, if you want a copy of the array, then you need to actually make a copy of the array. That's what Object#dup and Object#clone are for:
b = a.clone
Will fix your code.
BUT!
There are some other problems in your code. The main problem is mutation. If at all possible, you should avoid mutation (and side-effects in general, of which mutation is only one example) as much as possible and only use it when you really, REALLY have to. In particular, you should never mutate objects you don't own, and this means you should never mutate objects that were passed to you as arguments.
However, in your addup method, you mutate the array that is passed to you as arr. Mutation is the source of your problem, if you didn't mutate arr but instead returned a new array with the modifications you want, then you wouldn't have the problem in the first place. One way of not mutating the argument would be to move the cloneing into the method, but there is an even better way.
Another problem with your code is that you are using a loop. In Ruby, there is almost never a situation where a loop is the best solution. In fact, I would go so far as to argue that if you are using a loop, you are doing it wrong.
Loops are error-prone, hard to understand, hard to get right, and they depend on side-effects. A loop cannot work without side-effects, yet, we just said we want to avoid side-effects!
Case in point: your loop contains a serious bug. If I pass [1, 2, 3, 4, 5], the result will be [1, 2, 3, 5]. Why? Because of mutation and manual looping:
In the fourth iteration of the loop, at the beginning, the array looks like this:
[1, 2, 3, 4, 5]
# ↑
# i
After the call to delete_at(i), the array looks like this:
[1, 2, 3, 5]
# ↑
# i
Now, you increment i, so the situation looks like this:
[1, 2, 3, 5]
# ↑
# i
i is now greater than the length of the array, ergo, the loop ends, and the 5 never gets removed.
What you really want, is this:
def addup(arr)
arr.reject {|el| el > 3 }
end
a = [1, 2, 3, 4, 5]
b = a
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
puts "addup=#{addup(a)}" # => [1, 2, 3]
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
As you can see, nothing was mutated. addup simply returns the new array with the modifications you want. If you want to refer to that array later, you can assign it to a variable:
c = addup(a)
There is no need to manually fiddle with loop indices. There is no need to copy or clone anything. There is no "spooky action at a distance", as Albert Einstein called it. We fixed two bugs and removed 7 lines of code, simply by
avoiding mutation
avoiding loops

Can I merge two Set objects in Ruby?

I understand the Set class has the merge method just as the Hash class does. However, the Set#merge documentation says:
Merges the elements of the given enumerable object to the set and returns self.
It seems that merging can only take place between a Set and another non-Set object. Is that the case, or can I merge two sets as below?
set1.merge(set2)
Why This Question is Useful
While the OP has been criticized for lack of research effort, it should be pointed out that the Ruby documentation for Set#merge is not friendly for new Rubyists. As of Ruby 2.3.0, it says:
Merges the elements of the given enumerable object to the set and returns self.
It offers merge(enum) as the signature, but no useful examples. If you need to know what classes mix in Enumerable, it can be difficult to grok from just this one piece of documentation what kind of duck-typed ducks can be merged in. For example, set.merge {foo: 'bar'}.to_enum will raise SyntaxError despite the fact that it is enumerable:
{foo: 'bar'}.to_enum.class
#=> Enumerator
{foo: 'bar'}.to_enum.class.include? Enumerable
#=> true
Merging Sets
If you're thinking of Set#merge as creating a set union, then yes: you can merge sets. Consider the following:
require 'set'
set1 = Set.new [1, 2, 3]
#=> #<Set: {1, 2, 3}>
set2 = Set.new [3, 4, 5]
#=> #<Set: {3, 4, 5}>
set1.merge set2
#=> #<Set: {1, 2, 3, 4, 5}>
Merging Other Enumerable Objects Like Arrays
However, you can also merge other Enumerable objects (like arrays) into a set. For example:
set = Set.new [1, 2, 3]
#=> #<Set: {1, 2, 3}>
set.merge [3, 4, 5]
#=> #<Set: {1, 2, 3, 4, 5}>
Using Array Union Instead
Of course, you may not need sets at all. Compare and contrast Set to array unions (Array#|). If you don't need the actual features of the Set class, you can often do similar things directly with arrays. For example:
([1, 2, 3, 4] | [3, 4, 5, 6]).uniq
#=> [1, 2, 3, 4, 5, 6]

Three ways to create a range, hash, array in ruby

I am doing a tutorial course on ruby and it asks for 3 ways to create range, hash, array.
I can only think of 2: (1..3) and Range.new(1,3) (and similarly for hash and array).
What is the third way?
The tutorial in question is The Odin Project
Ranges may be constructed using the s..e and s...e literals, or with ::new.
Ranges constructed using .. run from the beginning to the end inclusively.
Those created using ... exclude the end value. When used as an iterator, ranges return each value in the sequence.
(0..2) == (0..2) #=> true
(0..2) == Range.new(0,2) #=> true
(0..2) == (0...2) #=> false
Read More Here
For Arrays there's Array::[] (example taken directly from the docs):
Array.[]( 1, 'a', /^A/ ) # => [1, "a", /^A/]
Array[ 1, 'a', /^A/ ] # => [1, "a", /^A/]
[ 1, 'a', /^A/ ] # => [1, "a", /^A/]
Similarly there's Hash::[]. Not sure about Ranges; in fact, the docs (as far as I can tell) only mention literals and Range::new.
I can't see why you'd use these over a literal, but there you go.
You can also make a exclusive range, using (1...4), which if turned into an array would become [1, 2, 3]
(1..3) is an inclusive range, so it contains all numbers, from 1 to 3, but if you used (1...3), having 3 dots instead of 2 makes it exclusive, so it contains all numbers from 1, up to but not including 3.
As for arrays and hashes, #to_a, Array#[], #to_h, and Hash#[] will work.
(1..3).to_a
=> [1, 2, 3]
Array[1, 2, 3]
=> [1, 2, 3]
[[1, 2], [3, 4], [5, 6]].to_h
=> {1=>2, 3=>4, 5=>6}
Hash[ [[1, 2], [3, 4], [5, 6]] ]
=> {1=>2, 3=>4, 5=>6}
But they are probably looking for Array#[] and Hash#[] on the array and hash part.

How do I extract specific elements from an array?

If I have an array a = [1,2,3,4,5,6,7,8,9,10] and I want a subset of this array - the 1st, 5th and 7th elements. Is it possible to extract these from this array in a simple way. I was thinking something like:
a[0,4,6] = [1,5,7]
but that doesn't work.
Also is there a way to return all indices except those specified? For example, something like
a[-0,-4,-6] = [2,3,4,6,8,9,10]
You can simply do:
[1] pry(main)> [1,2,3,4,5,6,7,8,9,10].values_at(0, 4, 6)
=> [1, 5, 7]
Here's one way:
[0,4,6].map{|i| a[i]}
a = [1,2,3,4,5,6,7,8,9,10]
For a[0,4,6] = [1,5,7] :
a.values_at(0, 4, 6)
=> [1, 5, 7]
For a[-0,-4,-6] = [2,3,4,6,8,9,10] :
a - a.values_at(0, 4, 6)
=> [2, 3, 4, 6, 8, 9, 10]
In Ruby, Array objects have a method/operator [] which allows you to get/reference an object at a specific index, or a contiguous subset of objects specified by a range.
ary[index] → obj or nil
ary[start, length] → new_ary or nil
ary[range] → new_ary or nil
Personally, I like your syntax. It would be useful but it is not provided by the standard Array object. Until Ruby adopts your syntax, as others have suggested, you have the values_at method to do exactly what you need.
values_at(selector,... ) → new_ary
If a = [1,2,3,4,5,6,7,8,9,10], then:
a.values_at(0, 4, 6)
=> [1, 5, 7]

Resources