remove the duplicates in an array of object - ruby

How to remove the duplicate objects in an array of objects.
for ex:
[#< #a:1, #b:2>, #< #a:3, #b:3>, #<#a:3, #b:3>] => [ #< #a:1, #b:2>, #< #a:3, #b:3>].
Also my understanding is no two objects can be same..correct me if i am wrong.

You can use Array#uniq, but you'll need to make a small change to your class. Per the docs, uniq "compares values using their hash and eql? methods for efficiency," so you'll need to define hash and eql? such that they will identify two objects of your class as duplicates. For example, if two objects with the same a and b attributes are duplicates, you might do this:
class Foo
attr_reader :a, :b
def initialize(a, b)
#a, #b = a, b
end
def hash
[ a, b ].hash
end
def eql?(other)
hash == other.hash
end
end
arr = [ Foo.new(1, 2), Foo.new(3, 3), Foo.new(3, 3) ]
p arr.uniq
# => [#<Foo:0x007f686ac36700 #a=1, #b=2>, #<Foo:0x007f686ac366b0 #a=3, #b=3>]
Alternatively, if you don't want to or can't define hash and eql? methods you can use the block form of uniq:
class Bar
attr_reader :a, :b
def initialize(a, b)
#a, #b = a, b
end
end
arr2 = [ Bar.new(1, 2), Bar.new(3, 3), Foo.new(3, 3) ]
p arr2.uniq {|obj| [ obj.a, obj.b ] }
# => [#<Bar:0x007fe80f7b6750 #a=1, #b=2>, #<Bar:0x007fe80f7b6700 #a=3, #b=3>]
You can see both of these on repl.it.

Use Array#uniq. Example from the docs:
a = [ "a", "a", "b", "b", "c" ]
a.uniq
# => ["a", "b", "c"]
Note that this method uses their hash or eql? methods to identify duplicates. That said: If uniq doesn't work like expected, then you probably make sure that hash or eql? are implemented appropriately.

Related

Ruby: Sort array of objects after an array of name properties

I have an array "sizes" that look like this:
[#<OPTIONVALUE ID: 5, NAME: "M">,
#<OPTIONVALUE ID: 6, NAME: "M/L">,
#<OPTIONVALUE ID: 7, NAME: "XS/S">]
Consider the values of attribute NAME. The array is sorted: M, M/L, XS/S.
But the sort order should look like this:
#sizes_sort_order = ["XS", "XS/S", "S", "S/M", "M", "M/L", "L", "L/XL", "XL"]
applied to the former array the order of the elements should look like this:
[#<SPREE::OPTIONVALUE ID: 7, NAME: "XS/S">,
#<SPREE::OPTIONVALUE ID: 5, NAME: "M">,
#<SPREE::OPTIONVALUE ID: 6, NAME: "M/L">]
def sizes
#sizes ||= grouped_option_values_by_option_type[Spree::OptionType.find_by!(name: 'size')]
#sizes_sort_order = ["XS", "XS/S", "S", "S/M", "M", "M/L", "L", "L/XL", "XL"]
#sizes.map { # sort after #size_sort_order }
end
How can i achieve to get the elements in the array sorted after #sizes_sort_order ?
You can use Enumerable#sort_by
my_array.sort_by {|x| #sizes_sort_order.index(x.name) }
You can include the Comparablemodule to get a natural sort for the objects.
http://ruby-doc.org/core-2.2.3/Comparable.html
The Comparable mixin is used by classes whose objects may be ordered.
The class must define the <=> operator, which compares the receiver
against another object, returning -1, 0, or +1 depending on whether
the receiver is less than, equal to, or greater than the other object.
class Size
include Comparable
SIZES = ["XS", "XS/S", "S", "S/M", "M", "M/L", "L", "L/XL", "XL"]
attr_reader :name
def initialize(id, name)
#id = id
#name = name
end
def <=>(b)
SIZES.index(name) <=> SIZES.index(b.name)
end
end
a = Size.new(5, 'M')
b = Size.new(6, 'M/L')
c = Size.new(7, 'XS/S')
print [a, b, c].sort
[#<Size:0x007f8e910458e0 #id=7, #name="XS/S">, #<Size:0x007f8e910459a8 #id=5, #name="M">, #<Size:0x007f8e91045930 #id=6, #name="M/L">]
This approach involves more steps than ones that employ sort or sort_by, but for larger arrays it may be faster, as no sorting--which is relatively expensive--is involved.
Code
def reorder_by_size(instances, size_order)
instances.each_with_object({}) { |inst, h| h.update(inst.name=>inst) }.
values_at(*(size_order & (instances.map { |s| s.name })))
end
Example
First let's create an array of instances of
class Sizes
attr_reader :name
def initialize(id, name)
#id = id
#name = name
end
end
like so:
instances = [Sizes.new(5,'M'), Sizes.new(6,'M/L'), Sizes.new(7, 'XS/S')]
#=> [#<Sizes:0x007fa66a955ac0 #id=5, #name="M">,
# #<Sizes:0x007fa66a955a70 #id=6, #name="M/L">,
# #<Sizes:0x007fa66a955a20 #id=7, #name="XS/S">]
Then
reorder_by_size(instances, #sizes_sort_order)
#=> [#<Sizes:0x007fa66a01dfc0 #id=7, #name="XS/S">,
# #<Sizes:0x007fa66a86fdb8 #id=5, #name="M">,
# #<Sizes:0x007fa66a8404f0 #id=6, #name="M/L">]
Explanation
For instances as defined for the example, first create an array of sizes in the desired order:
names = #sizes_sort_order & (instances.map { |s| s.name })
#=> ["XS/S", "M", "M/L"]
Important: the doc for Array#& states, "The order is preserved from the original array.".
Now we can create the desired reordering without sorting, by creating a hash with keys the sizes and values the instances, then use Hash#values_at to extract the instances in the desired order.
instances.each_with_object({}) { |inst, h|
h.update(inst.name=>inst) }.values_at(*names)
#=> [#<Sizes:0x007fa66a01dfc0 #id=7, #name="XS/S">,
# #<Sizes:0x007fa66a86fdb8 #id=5, #name="M">,
# #<Sizes:0x007fa66a8404f0 #id=6, #name="M/L">]
The last operation involves the following two steps.
h = instances.each_with_object({}) { |inst, h| h.update(inst.name=>inst) }
#=> {"M" => #<Sizes:0x007fa66a955ac0 #id=5, #name="M">,
# "M/L" => #<Sizes:0x007fa66a955a70 #id=6, #name="M/L">,
# "XS/S" => #<Sizes:0x007fa66a955a20 #id=7, #name="XS/S">}
h.values_at(*names)
#=> h.values_at(*["XS/S", "M", "M/L"])
#=> h.values_at("XS/S", "M", "M/L")
#=> [#<Sizes:0x007fa66a955a20 #id=7, #name="XS/S">,
# #<Sizes:0x007fa66a955ac0 #id=5, #name="M">,
# #<Sizes:0x007fa66a955a70 #id=6, #name="M/L">]

How to make a custom proc in a class so I can do: params_hash.downcase_keys instead of downcase_keys(params_hash)?

I have a hash of key values and I want to downcase all of the Keys.
However I don't want to have to create an local variable, I would rather functionally do it.
NOT:
x = downcase_keys(params_hash)
BUT THIS:
params_hash.downcase_keys
How would do this in ruby?
I do not understand why you tagged this question as functional-programming it seems you are looking for a method to call on a Hash object.
Be aware that you may encounter problems doing so because duplicated keys are going to be overwritten.
h = {"a" => 1, "B" => 2}
# Singleton method on the object
def h.downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
# Method available on all Hash objects
class Hash
def downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
end
h = {"a" => 1, "B" => 2}
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
def downcase_keys(hash)
hash.downcase_keys
end
h = {"C" => 1, "B" => 2, "D" => 3}
downcase_keys(h)
p h # {"c"=>1, "b"=>2, "d"=>3}

How to retrieve values within an array

def self.foo
[
["a","aa"],
["b","bb"],
]
end
Given "a", I should be able to retrieve "aa"
Given "bb", I should be able to retrieve "b"
How do I do this?
assoc and rassoc are your friends:
ar = [
["a","aa"],
["b","bb"],
]
p ar.assoc("a").last #=> "aa"
p ar.rassoc("bb").first #=> "b"
Hash[self.foo].invert["bb"] #=> "b"
Hash[self.foo]["a"] #=> "aa"
Hash[] turns array into hash
Hash#invert inverts the hash so all values map to the keys
If you want to do both:
Hash[self.foo]["bb"] or Hash[self.foo].invert["bb"] #=> "b"
I would create my own "bimap" implementation, perhaps something like:
class Bimap < Hash
alias :__put__ :[]=
def []=(key,value)
__put__(key,value)
__put__(value,key)
end
alias :__size__ :size
def size
__size__ / 2
end
# ...any other Hash methods to reimplement?
end

simple hash merge by array of keys and values in ruby (with perl example)

In Perl to perform a hash update based on arrays of keys and values I can do something like:
#hash{'key1','key2','key3'} = ('val1','val2','val3');
In Ruby I could do something similar in a more complicated way:
hash.merge!(Hash[ *[['key1','key2','key3'],['val1','val2','val3']].transpose ])
OK but I doubt the effectivity of such procedure.
Now I would like to do a more complex assignment in a single line.
Perl example:
(#hash{'key1','key2','key3'}, $key4) = &some_function();
I have no idea if such a thing is possible in some simple Ruby way. Any hints?
For the Perl impaired, #hash{'key1','key2','key3'} = ('a', 'b', 'c') is a hash slice and is a shorthand for something like this:
$hash{'key1'} = 'a';
$hash{'key2'} = 'b';
$hash{'key3'} = 'c';
In Ruby 1.9 Hash.[] can take as its argument an array of two-valued arrays (in addition to the old behavior of a flat list of alternative key/value arguments). So it's relatively simple to do:
mash.merge!( Hash[ keys.zip(values) ] )
I do not know perl, so I'm not sure what your final "more complex assignment" is trying to do. Can you explain in words—or with the sample input and output—what you are trying to achieve?
Edit: based on the discussion in #fl00r's answer, you can do this:
def f(n)
# return n arguments
(1..n).to_a
end
h = {}
keys = [:a,:b,:c]
*vals, last = f(4)
h.merge!( Hash[ keys.zip(vals) ] )
p vals, last, h
#=> [1, 2, 3]
#=> 4
#=> {:a=>1, :b=>2, :c=>3}
The code *a, b = some_array will assign the last element to b and create a as an array of the other values. This syntax requires Ruby 1.9. If you require 1.8 compatibility, you can do:
vals = f(4)
last = vals.pop
h.merge!( Hash[ *keys.zip(vals).flatten ] )
You could redefine []= to support this:
class Hash
def []=(*args)
*keys, vals = args # if this doesn't work in your version of ruby, use "keys, vals = args[0...-1], args.last"
merge! Hash[keys.zip(vals.respond_to?(:each) ? vals : [vals])]
end
end
Now use
myhash[:key1, :key2, :key3] = :val1, :val2, :val3
# or
myhash[:key1, :key2, :key3] = some_method_returning_three_values
# or even
*myhash[:key1, :key2, :key3], local_var = some_method_returning_four_values
you can do this
def some_method
# some code that return this:
[{:key1 => 1, :key2 => 2, :key3 => 3}, 145]
end
hash, key = some_method
puts hash
#=> {:key1 => 1, :key2 => 2, :key3 => 3}
puts key
#=> 145
UPD
In Ruby you can do "parallel assignment", but you can't use hashes like you do in Perl (hash{:a, :b, :c)). But you can try this:
hash[:key1], hash[:key2], hash[:key3], key4 = some_method
where some_method returns an Array with 4 elements.

Bidirectional Hash table in Ruby

I need a bidirectional Hash table in Ruby. For example:
h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.fetch(:xyz) # => 789
h.rfetch(123) # => abc
h.rfetch(789) # => [:xyz, :qaz]
h.rfetch(888) # => :wsx
Method rfetch means reversed fetch and is only my proposal.
Note three things:
If multiple keys map at the same value then rfetch returns all of them, packed in array.
If value is an array then rfetch looks for its param among elements of the array.
Bidirectional Hash means that both fetch and rfetch should execute in constant time.
Does such structure exists in Ruby (including external libraries)?
I thought about implementing it using two one-directional Hashes synchronized when one of them is modified (and packing it into class to avoid synchronization problems) but maybe I could use an already existing solution?
You could build something yourself pretty easily, just use a simple object that wraps two hashes (one for the forward direction, one for the reverse). For example:
class BiHash
def initialize
#forward = Hash.new { |h, k| h[k] = [ ] }
#reverse = Hash.new { |h, k| h[k] = [ ] }
end
def insert(k, v)
#forward[k].push(v)
#reverse[v].push(k)
v
end
def fetch(k)
fetch_from(#forward, k)
end
def rfetch(v)
fetch_from(#reverse, v)
end
protected
def fetch_from(h, k)
return nil if(!h.has_key?(k))
v = h[k]
v.length == 1 ? v.first : v.dup
end
end
Look ups will behave just like normal hash lookups (because they are normal hash lookups). Add some operators and maybe decent to_s and inspect implementations and you're good.
Such a thing works like this:
b = BiHash.new
b.insert(:a, 'a')
b.insert(:a, 'b')
b.insert(:a, 'c')
b.insert(:b, 'a')
b.insert(:c, 'x')
puts b.fetch(:a).inspect # ["a", "b", "c"]
puts b.fetch(:b).inspect # "a"
puts b.rfetch('a').inspect # [:a, :b]
puts b.rfetch('x').inspect # :c
puts b.fetch(:not_there).inspect # nil
puts b.rfetch('not there').inspect # nil
There's nothing wrong with building your tools when you need them.
There is no such structure built-in in Ruby.
Note that Hash#rassoc does something similar, but it returns only the first match and is linear-time:
h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.rassoc(123) # => [:abc, 123]
Also, it isn't possible to fullfill your requirements in Ruby in a perfectly safe manner, as you won't be able to detect changes in values that are arrays. E.g.:
h = MyBidirectionalArray.new(:foo => 42, :bar => [:hello, :world])
h.rfetch(:world) # => :bar
h[:bar].shift
h[:bar] # => [:world]
h.rfetch(:world) # => should be nil, but how to detect this??
Computing a hash everytime to detect a change will make your lookup linear-time. You could duplicate the array-values and freeze them, though (like Ruby does for Hash keys that are strings!)
What you seem to need is a Graph class, which could have a different API than a Hash, no? You can check out rgl or similar, but I don't know how they're implemented.
Good luck.
There is a Hash#invert method (http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-invert) to achieve this. It won't map multiple values to an array though.
Try this:
class Hash
def rfetch val
select { |k,v| v.is_a?(Array) ? v.include?(val) : v == val }.map { |x| x[0] }
end
end
If you're not doing lots of updates to this hash, you might be able to use inverthash.

Resources