I have a few arrays of Ruby objects of class UserInfo:
class UserInfo
attr_accessor :name, :title, :age
end
How can I merge these arrays into one array? A user is identified by its name, so I want no duplicate names. If name, title, age, etc. are equal I'd like to have 1 entry in the new array. If names are the same, but any of the other details differ I probably want those 2 users in a different array to manually fix the errors.
Thanks in advance
Redefine equality comparison on your object, and you can get rid of actual duplicates quickly with Array#uniq
class UserInfo
attr_accessor :name, :title, :age
def == other
name==other.name and title==other.title and age==other.age
end
end
# assuming a and b are arrays of UserInfo objects
c = a | b
# c will only contain one of each UserInfo
Then you can sort by name and look for name-only duplicates
d = c.sort{ |p,q| p.name <=> q.name } #sort by name
name = ""
e = []
d.each do |item|
if item.name == name
e[-1] = [e[-1],item].flatten
else
e << item
end
end
A year ago I monkey patched a kind of cryptic instance_variables_compare on Object. I guess you could use that.
class Object
def instance_variables_compare(o)
Hash[*self.instance_variables.map {|v|
self.instance_variable_get(v)!=o.instance_variable_get(v) ?
[v,o.instance_variable_get(v)] : []}.flatten]
end
end
A cheesy example
require 'Date'
class Cheese
attr_accessor :name, :weight, :expire_date
def initialize(name, weight, expire_date)
#name, #weight, #expire_date = name, weight, expire_date
end
end
stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02"))
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
irb is my weapon of choice
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola", "#expire_date"=>#<Date: 4910305/2,0,2299161>}
>> gorgonzola.instance_variables_compare(stilton)
=> {"#name"=>"Stilton", "#expire_date"=>#<Date: 4910275/2,0,2299161>}
>> stilton.expire_date=gorgonzola.expire_date
=> #<Date: 4910305/2,0,2299161>
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola"}
>> stilton.instance_variables_compare(stilton)
=> {}
As you can see the instance_variables_compare returns an empty Hash if the two objects has the same content.
An array of cheese
stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02"))
gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2
One hash without problems and one with
h={}
problems=Hash.new([])
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name].instance_variables_compare(c) != {}
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Now the Hash h contains the objects without merging problems and the problems hash contains those that has instance variables that differs.
>> h
=> {"Gorgonzola"=>#<Cheese:0xb375e8 #name="Gorgonzola", #weight=250, #expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>}
>> problems
=> {"Stilton"=>[#<Cheese:0xf54c30 #name="Stilton", #weight=210, #expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 #name="Stilton", #weight=250,#expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}
As far as I can see you will not have to modify this code at all to support an array of UserInfo objects.
It would most probably be much faster to compare the properties directly or with a override of ==. This is how you override ==
def ==(other)
return self.weight == other.weight && self.expire_date == other.expire_date
end
and the loop changes into this
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name] != c
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Finally you might want to convert the Hash back to an Array
result = h.values
Here's another potential way. If you have a way of identifying each UserInfo, say a to_str method that prints out the values:
def to_str()
return "#{#name}:#{#title}:#{#age}"
end
You can use inject and a hash
all_users = a + b # collection of users to "merge"
res = all_users.inject({})do |h,v|
h[v.to_str] = v #save the value indexed on the string output
h # return h for the next iteration
end
merged = res.values #the unique users
Related
In Ruby, I have a list of objects called Things with an Id property and a value property.
I want to make a Hash that contains Id as the key and Value as the value for the cooresponding key.
I tried:
result = Hash[things.map { |t| t.id, t.value }]
where things is a list of Thing
But this did not work.
class Thing
attr_reader :id, :value
def initialize(id, value)
#id = id
#value = value
end
end
cat = Thing.new("cat", 9)
#=> #<Thing:0x007fb86411ad90 #id="cat", #value=9>
dog = Thing.new("dog",1)
#=> #<Thing:0x007fb8650e49b0 #id="dog", #value=1>
instances =[cat, dog]
#=> [#<Thing:0x007fb86411ad90 #id="cat", #value=9>,
# #<Thing:0x007fb8650e49b0 #id="dog", #value=1>]
instances.map { |i| [i.id, i.value] }.to_h
#=> {"cat"=>9, "dog"=>1}
or, for Ruby versions prior to 2.0:
Hash[instances.map { |i| [i.id, i.value] }]
#=> {"cat"=>9, "dog"=>1}
result = things.map{|t| {t.id => t.value } }
The content of the outer pair of curly brackets is a block, the inner pair forms a hash.
However, if one hash is the desired result (as suggested by Cary Swoveland) this may work:
result = things.each_with_object({}){| t, h | h[t.id] = t.value}
I want to override the Hash class native brackets in ruby.
Note I don't want to override them in a class that inherits from Hash (no subclassing), I want to actually override Hash itself, such that any hash anywhere will always inherit my behavior.
Specifically (bonus points for..) - I want this in order to natively emulate a hash with indifferent access. In JavaScript I would modify the prototype, Ruby is known for its metaprogramming, so I hope this is possible.
So what I am aiming for is:
>> # what do I do here to overload Hash's []?...
>> x = {a:123} # x is a native Hash
>> x[:a] # == 123, as usual
>> x['a'] # == 123, hooray!
I've tried:
1)
class Hash
define_method(:[]) { |other| puts "Hi, "; puts other }
end
and
class Hash
def []
puts 'bar'
end
end
Both crash irb.
This seems to get the job done.
class Hash
def [](key)
value = (fetch key, nil) || (fetch key.to_s, nil) || (fetch key.to_sym, nil)
end
def []=(key,val)
if (key.is_a? String) || (key.is_a? Symbol) #clear if setting str/sym
self.delete key.to_sym
self.delete key.to_s
end
merge!({key => val})
end
end
And now:
user = {name: 'Joe', 'age' => 20} #literal hash with both symbols and strings as keys
user['name'] == 'Joe' # cool!
user[:age] == 20 # cool!
For more details see: http://www.sellarafaeli.com/blog/ruby_monkeypatching_friendly_hashes
class Hash
def [] key
value = fetch key rescue
case key
when Symbol then "#{value}, as usual"
when String then "#{value}, hooray!"
else value end
end
end
If using Rails HashWithIndifferentAccess supports this functionality already, even if using Ruby you can weigh including Active Support to have this functionality.
I am trying to compose an object Transaction from objects TranFee and Rate.
class Transaction
attr_reader :tranfee, :rate
def initialize(hash)
#tranfee = PaymentType::TranFee.new(hash)
#rate = PaymentType::Rate.new(hash)
end
end
module PaymentType
def initialize(args = {}, regex)
args.each do |key,value|
if key =~ regex
instance_variable_set("##{key}", value) unless value.nil?
eigenclass = class << self; self; end
eigenclass.class_eval do
attr_reader key
end
end
end
end
class TranFee
include PaymentType
def initialize(args, regex = /\Atran.*/)
super(args, regex)
end
end
class Rate
include PaymentType
def initialize(args, regex = /\Arate.*/)
super(args, regex)
end
end
end
The rate and TranFee objects are created from a hash like the one below.
reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005,
"tran_fee" => 0.21, "rate_basis_points" => 0.002, "tran_auth_fee" => 0.10}
I am initializing the objects based on regex because the hash will eventually contain more values and I want the program to adjust as more items/classes are added.
Additionally there will be some instances where there are no key's starting with "tran". Does anyone know how to make Transaction create only a Rate object if TranFee has no instance variables inside of it? (in otherwords, if the hash returns nothing when keys =~ /\Atran.*/)
an example would be when the hash looks like this reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005, "rate_basis_points" => 0.002}, right now the output is
#<Transaction:0x007ff98c070548 #tranfee=#<PaymentType::TranFee:0x007ff98c070520>, #rate=#<PaymentType::Rate:0x007ff98c0704a8 #rate_base=0.0005, #rate_basis_points=0.002>>
So I am getting a TranFee object with nothing in it and I would like for that to drop off in this situation. not sure if there may be a better way to design this? I was trying to think of a way to use ostruct or struct, but I havnt been able to figure it out. thanks for any help here.
I believe your strategy is very problematic - creating attributes to a class from user input doesn't sound like a very good idea.
Furthermore, adding methods (like attr_reader) to every instances can have severe performance issues.
If all you want is a data structure to hold your data, keep using a Hash. If you want a structure you can query using a dot notation instead of bracket notation, you might want to consider a gem like hashie or hashr.
If you want some code to make the flat data-structure hierarchical, I can suggest something like this:
hierarchical_hash = hash.each_with_object({}) do |(k, v), h|
if k.match(/^([^_]+)_(.+)$/)
root_key = $1
child_key = $2
h[root_key] ||= {}
h[root_key][child_key] = v
else
h[k] = v
end
end
# => {
# => "name" => "reg_debit",
# => "rate" => {
# => "base" => 0.0005,
# => "basis_points" => 0.002
# => },
# => "tran" => {
# => "fee" => 0.21,
# => "auth_fee" => 0.1
# => }
# => }
Your question raises some interesting issues. I will try to explain how you can fix it, but, as #Uri mentions, there may be better ways to address your problem.
I've assumed #tranfee is to be set equal to the first value in the hash whose key begins with "tran" and that #rate is to be set equal to the first value in the hash whose key begins with "rate". If that interpretation is not correct, please let me know.
Note that I've put initialize in the PaymentType module in a class (Papa) and made TranFee and Rate subclasses. That's the only way you can use super within initialize in the subclasses of that class.
Code
class Transaction
attr_reader :tranfee, :rate
def initialize(hash={})
o = PaymentType::TranFee.new(hash)
#tranfee = o.instance_variable_get(o.instance_variables.first)
o = PaymentType::Rate.new(hash)
#rate = o.instance_variable_get(o.instance_variables.first)
end
end
.
module PaymentType
class Papa
def initialize(hash, prefix)
key, value = hash.find { |key,value| key.start_with?(prefix) && value }
(raise ArgumentError, "No key beginning with #{prefix}") unless key
instance_variable_set("##{key}", value)
self.class.singleton_class.class_eval { attr_reader key }
end
end
class TranFee < Papa
def initialize(hash)
super hash, "tran"
end
end
class Rate < Papa
def initialize(hash)
super hash, "rate"
end
end
end
I believe the method Object#singleton_class has been available since Ruby 1.9.3.
Example
reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005, "tran_fee" => 0.21,
"rate_basis_points" => 0.002, "tran_auth_fee" => 0.10}
a = Transaction.new reg_debit
p Transaction.instance_methods(false) #=> [:tranfee, :rate]
p a.instance_variables #=> [:#tranfee, :#rate]
p a.tranfee #=> 0.21
p a.rate #=> 0.0005
I am looking for a way to have, I would say synonym keys in the hash.
I want multiple keys to point to the same value, so I can read/write a value through any of these keys.
As example, it should work like that (let say :foo and :bar are synonyms)
hash[:foo] = "foo"
hash[:bar] = "bar"
puts hash[:foo] # => "bar"
Update 1
Let me add couple of details. The main reason why I need these synonyms, because I receive keys from external source, which I can't control, but multiple keys could actually be associated with the same value.
Rethink Your Data Structure
Depending on how you want to access your data, you can make either the keys or the values synonyms by making them an array. Either way, you'll need to do more work to parse the synonyms than the definitional word they share.
Keys as Definitions
For example, you could use the keys as the definition for your synonyms.
# Create your synonyms.
hash = {}
hash['foo'] = %w[foo bar]
hash
# => {"foo"=>["foo", "bar"]}
# Update the "definition" of your synonyms.
hash['baz'] = hash.delete('foo')
hash
# => {"baz"=>["foo", "bar"]}
Values as Definitions
You could also invert this structure and make your keys arrays of synonyms instead. For example:
hash = {["foo", "bar"]=>"foo"}
hash[hash.rassoc('foo').first] = 'baz'
=> {["foo", "bar"]=>"baz"}
You could subclass hash and override [] and []=.
class AliasedHash < Hash
def initialize(*args)
super
#aliases = {}
end
def alias(from,to)
#aliases[from] = to
self
end
def [](key)
super(alias_of(key))
end
def []=(key,value)
super(alias_of(key), value)
end
private
def alias_of(key)
#aliases.fetch(key,key)
end
end
ah = AliasedHash.new.alias(:bar,:foo)
ah[:foo] = 123
ah[:bar] # => 123
ah[:bar] = 456
ah[:foo] # => 456
What you can do is completely possible as long as you assign the same object to both keys.
variable_a = 'a'
hash = {foo: variable_a, bar: variable_a}
puts hash[:foo] #=> 'a'
hash[:bar].succ!
puts hash[:foo] #=> 'b'
This works because hash[:foo] and hash[:bar] both refer to the same instance of the letter a via variable_a. This however wouldn't work if you used the assignment hash = {foo: 'a', bar: 'a'} because in that case :foo and :bar refer to different instance variables.
The answer to your original post is:
hash[:foo] = hash[:bar]
and
hash[:foo].__id__ == hash[:bar].__id__it
will hold true as long as the value is a reference value (String, Array ...) .
The answer to your Update 1 could be:
input.reduce({ :k => {}, :v => {} }) { |t, (k, v)|
t[:k][t[:v][v] || k] = v;
t[:v][v] = k;
t
}[:k]
where «input» is an abstract enumerator (or array) of your input data as it comes [key, value]+, «:k» your result, and «:v» an inverted hash that serves the purpose of finding a key if its value is already present.
Excuse I am newbie in ruby.
My problem is about argument passes by value and reference.
I am coding this method
def show_as_tree(parents)
array = []
iterate_categories(parents, array)
end
def iterate_categories(parents, array)
parents.each do |p|
#return p.description or "-#{p.description} if the node is root or not
p.description = category_name(p)
#add to array
array << p
#call iterate categories with children of parent node and same array
iterate_categories(p.children, array)
end
end
however the array content is only the parent nodes.
I need understand the ruby mechanism for references and how could fix my problem?
I'm pretty sure Ruby just creates a copy of your array. Therefore what you should be doing is having
array << iterate_categories(p.children, new_array)
And return your array at the end of the function.
Just did a quick example: (Updating code based on Wayne Conrad's answer… it is correct)
class Person
attr_accessor :name, :children
end
class Test
def iterate_categories(parents,array)
parents.each do |p|
array << p.name
if !p.children.nil?
iterate_categories(p.children,array)
end
end
end
def iterate_categories_test
p1 = Person.new
p1.name = "Bob"
p2 = Person.new
p2.name = "Joe"
p3 = Person.new
p3.name = "Ann"
p4 = Person.new
p4.name = "John"
p1.children = [p2,p3]
p3.children = [p4]
array = []
iterate_categories([p1],array)
puts array
end
end
Then:
>> a = Test.new
>> a.iterate_categories_test
Bob
Joe
Ann
John
=> nil
Hopefully that helps.
Your show_as_tree method should return the array.
def show_as_tree(parents)
array = []
iterate_categories(parents, array)
array
end
Without array as the last line, the return value of show_as_tree is the return value of iterate_categories, which happens to be parents. That's why it looks like only parents is getting added to array. That's an illusion: It was parents being returned, not array.
Ruby does not make copies of its arguments. It passes references by value. That means it is the same array being acted upon throughout your functions.
I don't quite get the code, the formatting seems all wrong but I'll try to help.
When you pass an argument to ruby, it seems to give the method a copy, not a reference. Fortunately, you don't need to worry about that, as ruby lets you return multiple things in one call.
For example, in irb I ran something like this:
ruby-1.9.2-p180 :005 > def stuff(a, b)
ruby-1.9.2-p180 :006?> c = a + b
ruby-1.9.2-p180 :007?> [c, a]
ruby-1.9.2-p180 :008?> end
=> nil
ruby-1.9.2-p180 :009 > a, b = stuff(1, 2)
=> [3, 1]
ruby-1.9.2-p180 :010 > a
=> 3
ruby-1.9.2-p180 :011 > b
=> 1
That way you can easily return multiple values without problem
I hope I answered your question.