Ruby: Want a Set-like object which preserves order - ruby

... or alternatively an Array which prevents duplicate entries.
Is there some kind of object in Ruby which:
responds to [], []= and <<
silently drops duplicate entries
is Enumerable (or at least supports find_all)
preserves the order in which entries were inserted
?
As far as I can tell, an Array supports points 1, 3 and 4; while a Set supports 1, 2 and 3 (but not 4). And a SortedSet won't do, because my entries don't implement <=>.

As of Ruby 1.9, the built-in Hash object preserves insertion order. For example:
h = {}
h[:z] = 1
h[:b] = 2
h[:a] = 3
h[:x] = 0
p h.keys #=> [:z, :b, :a, :x]
h.delete :b
p h.keys #=> [:z, :a, :x]
h[:b] = 1
p h.keys #=> [:z, :a, :x, :b]
So, you can set any value (like a simple true) for any key and you now have an ordered set. You can test for a key using either h.key?(obj) or, if you always set each key to have a truthy value, just h[obj]. To remove a key, use h.delete(obj). To convert the ordered set to an array, use h.keys.
Because the Ruby 1.9 Set library happens to be built upon a Hash currently, you can currently use Set as an ordered set. (For example, the to_a method's implementation is just #hash.keys.) Note, however, that this behavior is not guaranteed by that library, and might change in the future.
require 'set'
s = Set[ :f, :o, :o, :b, :a, :r ] #=> #<Set: {:f, :o, :b, :a, :r}>
s << :z #=> #<Set: {:f, :o, :b, :a, :r, :z}>
s.delete :o #=> #<Set: {:f, :b, :a, :r, :z}>
s << :o #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s << :o #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s << :f #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s.to_a #=> [:f, :b, :a, :r, :z, :o]

There isn't one as far as I know, and Set by its mathematical nature is meant to be unordered (or at least, implementationally, meant not to guarantee order - in fact its usually implemented as a hash table so it does mess up order).
However, it's not hard to either extend array directly or subclass it to do this. I just tried it out and this works:
class UniqueArray < Array
def initialize(*args)
if args.size == 1 and args[0].is_a? Array then
super(args[0].uniq)
else
super(*args)
end
end
def insert(i, v)
super(i, v) unless include?(v)
end
def <<(v)
super(v) unless include?(v)
end
def []=(*args)
# note: could just call super(*args) then uniq!, but this is faster
# there are three different versions of this call:
# 1. start, length, value
# 2. index, value
# 3. range, value
# We just need to get the value
v = case args.size
when 3 then args[2]
when 2 then args[1]
else nil
end
super(*args) if v.nil? or not include?(v)
end
end
Seems to cover all the bases. I used OReilly's handy Ruby Cookbook as a reference - they have a recipe for "Making sure a sorted array stays sorted" which is similar.

I like this solution although it requires active_support's OrderedHash
require 'active_support/ordered_hash'
class OrderedSet < Set
def initialize enum = nil, &block
#hash = ActiveSupport::OrderedHash.new
super
end
end
=)

You could use a Hash to store the values, and have an incrementing value stored in the value of each Hash pair. Then you can access the set in a sorted manner, albeit slowly, by accessing the objects via their values.
I'll try to add some code in here later to explain further.
I am aware accessing via values is much slower than by keys.
Update 1: In Ruby 1.9, Hash elements are iterated in their insertion order.

Not that I know of, but it wouldn't be hard to roll your own. Just subclass Array and use a Set to maintain your uniqueness constraint.
One question about silent dropping. How would this affect #[]=? If I was trying to overwrite an existing entry with something which was already stored elsewhere, should it remove the would-be-removed element anyway? I think either way could provide nasty surprises down the road.

Related

Mass method invocation

So in Ruby one can mass assign variables like this:
a, b, c = [1, 2, 3]
But what if I wanted to do the same for object methods, but without having to write out the whole thing like so:
foo.a, foo.b, foo.c = [1, 2, 3]
Is there a DRY way to accomplish this?
I am not sure you’ll like it, but the DRYest way I can think of is:
[:a, :b, :c].zip([1, 2, 3]).each { |k, v| foo.public_send "#{k}=", v }
# or vice versa
[1, 2, 3].zip([:a, :b, :c]).each { |v, k| foo.public_send "#{k}=", v }
Or, in more OO way:
class Foo
attr_accessor :a, :b, :c
def massive_assign attrs, values
attrs.zip(values).each { |k, v| public_send "#{k}=", v }
end
end
foo = Foo.new
foo.massive_assign([:a, :b, :c], [1, 2, 3])
Mass assignment to instance variables can be done like below as well:
foo.instance_eval { #a, #b, #c = [1, 2, 3] }
There's no specific syntax, but you could implement a setter for multiple attributes that are passed as a hash. Rails uses a similar approach:
class Foo
attr_accessor :a, :b, :c
def attributes=(attrs)
attrs.each do |name, value|
public_send("#{name}=", value)
end
end
end
foo = Foo.new
#=> #<Foo:0x007fc6d8a1e950>
foo.attributes = { a: 1, b: 2, c: 3 }
#=> #<Foo:0x007fc6d8a1e950 #a=1, #b=2, #c=3>
From the comments up there I assume that the poster is meaning a case where it's not actually foo but a more complex expression; and not only 3 assignments (method calls) but many.
There are 2 techniques that come to mind: tap and send:
some.complex[expression].which.evaluates.to.the.receiving.object.tap do |obj|
value_hash.each_pair do |key,value|
obj.send("#{key}=", value)
end
end
Hope that helps.
Of course, the usual security caveats apply (better make sure value_hash only contains valid/sane/allowed names).

Ruby all possible permutations of an array of arrays (one liner?)

Questions similar to this have been asked before on SO, but they're not quite what I need and I can't seem to arrive at my solution through altering/modifying those approaches.
In any case, I have an array of arrays, as follows:
b= [["1"],["2"],["3"],["4"],["5"],["6"]]
(If it makes it easier to arrive at a solution, b can also be a one dimensional array, as follows: ["1","2","3","4","5","6"]. Either type of input works for my needs.)
and I would like to generate the following:
[["123456"],["213456"],["312456"],...]
where each array in the output array is a unique permutation of the six numbers. I would also take it as a single array (e.g., ["123456", "213456",...]). The order of the output isn't particularly important as long as each entry is unique and no number repeats in a string (e.g., "112345" isn't allowed). All 6 numbers must also be used in each entry, so I'm not interested in incremental output like "123", either.
As much as this sounds like it, this isn't a homework problem. I could brute for this thing and get the output I need. I just feel like there has to be a better, more elegant, solution.
With Array#permutation:
permutations = (1..6).to_a.permutation.map(&:join)
# ["123456", "123465", "123546", ..., "654312", "654321"]
Ruby does this natively :)
From the ruby documentation :
a = [1, 2, 3]
a.permutation.to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
a.permutation(1).to_a #=> [[1],[2],[3]]
a.permutation(2).to_a #=> [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
a.permutation(3).to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
a.permutation(0).to_a #=> [[]] # one permutation of length 0
a.permutation(4).to_a #=> [] # no permutations of length 4
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-permutation
You should definitely have a look at Permutation Gem. Example from documentation
perm = Permutation.new(3)
# => #<Permutation:0x57dc94 #last=5, #rank=0, #size=3>
colors = [:r, :g, :b]
# => [:r, :g, :b]
perm.map { |p| p.project(colors) }
# => [[:r, :g, :b], [:r, :b, :g], [:g, :r, :b], [:g, :b, :r], [:b, :r, :g],
# [:b, :g, :r]]
UPDATE
If you are using Ruby > 1.8.6, Array.permutation is built in.
This should do it:
b.permutation.to_a.collect! { |i| i = [i.flatten.join] }

Array of hashes to hash

For example, I have array of single hashes
a = [{a: :b}, {c: :d}]
What is best way to convert it into this?
{a: :b, c: :d}
You may use
a.reduce Hash.new, :merge
which directly yields
{:a=>:b, :c=>:d}
Note that in case of collisions the order is important. Latter hashes override previous mappings, see e.g.:
[{a: :b}, {c: :d}, {e: :f, a: :g}].reduce Hash.new, :merge # {:a=>:g, :c=>:d, :e=>:f}
You can use .inject:
a.inject(:merge)
#=> {:a=>:b, :c=>:d}
Demonstration
Which initiates a new hash on each iteration from the two merged. To avoid this, you can use destructive :merge!( or :update, which is the same):
a.inject(:merge!)
#=> {:a=>:b, :c=>:d}
Demonstration
These two are equivalent (reduce/inject are the same method):
total_hash = hs.reduce({}) { |acc_hash, hash| acc_hash.merge(hash) }
total_hash = hs.reduce({}, :merge)
Note that Hash#merge creates a new hash on each iteration, which may be a problem if you are building a big one. In that case, use update instead:
total_hash = hs.reduce({}, :update)
Alternatively, you can convert the hashes to pairs and then build the final hash:
total_hash = hs.flat_map(&:to_a).to_h
I came across this answer and I wanted to compare the two options in terms of performance to see which one is better:
a.reduce Hash.new, :merge
a.inject(:merge)
using the ruby benchmark module, it turns out that option (2) a.inject(:merge) is faster.
code used for comparison:
require 'benchmark'
input = [{b: "c"}, {e: "f"}, {h: "i"}, {k: "l"}]
n = 50_000
Benchmark.bm do |benchmark|
benchmark.report("reduce") do
n.times do
input.reduce Hash.new, :merge
end
end
benchmark.report("inject") do
n.times do
input.inject(:merge)
end
end
end
the results were
user system total real
reduce 0.125098 0.003690 0.128788 ( 0.129617)
inject 0.078262 0.001439 0.079701 ( 0.080383)
Just use
a.reduce(:merge)
#=> {:a=>:b, :c=>:d}
Try this
a.inject({}){|acc, hash| acc.merge(hash)} #=> {:a=>:b, :c=>:d}
You can transform it to array [[:a, :b]] and after that translate everything to hash {:a=>:b}
# it works like [[:a, :b]].to_h => {:a=>:b}
[{a: :b}, {c: :d}].map { |hash| hash.to_a.flatten }.to_h
# => {:a=>:b, :c=>:d}

Sort items in a nested hash by their values

I'm being sent a nested hash that needs to be sorted by its values. For example:
#foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}}
When running the following:
#foo["a"].sort{|a,b| a[1]<=>b[1]}
I get:
[["y", 3], ["z", 5], ["x", 88]]
This is great, it's exactly what I want. The problem is I'm not always going to know what all the keys are that are being sent to me so I need some sort of loop. I tried to do the following:
#foo.each do |e|
e.sort{|a,b| a[1]<=>b[1]}
end
This to me makes sense since if I manually call #foo.first[0] I get
"a"
and #foo.first[1] returns
{"z"=>5, "y"=>3, "x"=>8}
but for some reason this isn't sorting properly (e.g. at all). I assume this is because the each is calling sort on the entire hash object rather than on "a"'s values. How do I access the values of the nested hash without knowing what it's key is?
You might want to loop over the hash like this:
#foo.each do |key, value|
#foo[key] = value.sort{ |a,b| a[1]<=>b[1] }
end
#foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}}
#bar = Hash[ #foo.map{ |key,values| [ key, values.sort_by(&:last) ] } ]
Or, via a less-tricky path:
#bar = {}
#foo.each do |key,values|
#bar[key] = values.sort_by{ |key,value| value }
end
In both cases #bar turns out to be:
p #bar
#=> {
#=> "a"=>[["y", 3], ["z", 5], ["x", 88]],
#=> "b"=>[["d", -5], ["a", 2]]
#=> }
My coworker came up with a slightly more flexible solution that will recursively sort an array of any depth:
def deep_sort_by(&block)
Hash[self.map do |key, value|
[if key.respond_to? :deep_sort_by
key.deep_sort_by(&block)
else
key
end,
if value.respond_to? :deep_sort_by
value.deep_sort_by(&block)
else
value
end]
end.sort_by(&block)]
end
You can inject it into all hashes and then just call it like this:
myMap.deep_sort_by { |obj| obj }
The code would be similar for an array. We published it as a gem for others to use, see blog post for additional details.
Disclaimer: I work for this company.
in your example e is an temporary array containing a [key,value] pair. In this case, the character key and the nested hash. So e.sort{|a,b|...} is going to try to compare the character to the hash, and fails with a runtime error. I think you probably meant to type e[1].sort{...}. But even that is not going to work correctly, because you don't store the sorted hash anywhere: #foo.each returns the original #foo and leaves it unchanged.
The better solution is the one suggested by #Pan Thomakos:
#foo.each do |key, value|
#foo[key] = value.sort{ |a,b| a[1]<=>b[1] }
end

Deleting multiple key and value pairs from hash in Rails

number = {:a => 1, :b => 2, :c => 3, :d => 4}
upon evaluation of certain condition i want to delete key-value pair of a,b,c
number.delete "A"
number.delete "B"
number.delete "C"
Or, less performant but more terse:
number.reject! {|k, v| %w"A B C".include? k }
or, more performant than second Chris' solution but shorter than first:
%w"A B C".each{|v| number.delete(v)}
ActiveSupport that is a part of Rails comes with several built-in methods can help you to achieve your goal.
If you just want to delete some key-value pairs, you can use Hash#except!
number.except!(:a, :b, :c)
If you want to keep the original hash, then use Hash#except
new_hash = number.except!(:a, :b, :c)
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
You also can go with Rails-free way:
new_hash = number.dup.tap do |hash|
%i[a b c].each {|key| hash.delete(key)}
end
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
P.S.: the last code example is very slow, I'm just providing it as an alternative.

Resources