Match an item to a list of items - ruby

Let's say I want to group States into Regions. For instance, the Southwest Region might be: Texas, Oklahoma, Colorado, New Mexico, Utah, Arizona, Nevada.
Somehow I need to create the list of states and define the region groupings. I also need to be able to lookup a region given the name of a state, something like region_for('Texas') which would return 'Southwest'.
What is the best, cleanest, "Ruby Way" to do something like this? I'd like to do it using plain 'ol ruby, no database or frameworks.

You can almost type this data structure directly into Ruby...
result = {
'Southwest' => %W{Texas Oklahoma Colorado New\ Mexico Utah Arizona Nevada},
'West' => %W{California Oregon Washington},
}.inject({}) do |m, (k, v)|
m[k] = v
v.each { |s| m[s] = k }
m
end
This produces a single Hash that has both states and regions as keys identifying each other. The data structure looks something like:
{"Colorado" => "Southwest",
"New Mexico" => "Southwest",
"Oklahoma" => "Southwest",
"California" => "West",
"Oregon" => "West",
"Texas" => "Southwest",
"Washington" => "West",
"Utah" => "Southwest",
"Nevada" => "Southwest",
"Arizona" => "Southwest"
"Southwest" =>
["Texas", "Oklahoma", "Colorado", "New Mexico", "Utah", "Arizona", "Nevada"],
"West" =>
["California", "Oregon", "Washington"],
}
Another approach would create a separate hash for states. Then you could get a list of regions or states by using Hash#keys, but you could do that here as well by using Enumerable#select or Enumerable#reject based on the type of the value.

The 'pure' ruby way is just to use hashes, and then have keys to do your lookups. There's a gem that kind of does something like this: ruport. It might be worth it to look at the source-code. For the use case you've illustrated, I'd have something like:
class RegionMapper
#potentially put this in a config file
REGIONS = Hash[[['California', 'Southwest'], ...]]
def initialize
#region_map = REGIONS.inject({}) {|r, e| r[e.second] ||= []; r[e.second] << e.first; r}
end
def region_for_state(state)
REGIONS[state]
end
def states_for_region(region)
#region_map(region)
end
end
The point is, to be efficient, you want to have a hash to do the lookups on each key you want to search by. But you dont' want to expose the data-duplication, so you put it all in a class.
If you have multiple values / keys, then you really have a table. If you want to keep constant time lookups, then you build a hash for each column (like the #region_map)

Try:
class State
attr_accessor :name, :region
def initialize(name, region=nil)
#name = name
#region = region
end
end
class Region
attr_accessor :name, :states
def initialize(name, states)
#name = name
#states = states
end
def set_state_regions
self.states.each {|state| state.region = self.name}
end
end
mo = State.new("missouri")
il = State.new("illionois")
oh = State.new("ohio")
midwest = Region.new("midwest", [mo, il, oh])
midwest.states.each {|state| puts state.name}
midwest.set_state_regions
I may come back and reflect on this later, I think it violates some OO principles.

I builded a very similar answer like Caley.
Main difference: I stored my data in a yaml-structure.
require 'yaml'
class Region
##all = {}
def self.[](key)
##all[key]
end
def initialize(name)
#name = name
#states = []
##all[#name] = self
end
def <<(state)
#states << state
state.region = state
end
def each_state
#states.each{|state| yield state } if block_given?
#states
end
attr_reader :name
end
class State
##all = {}
def self.[](key)
##all[key]
end
def initialize(name, region = nil)
#name = name
#region = region
##all[#name] = self
end
attr_accessor :name
attr_accessor :region
end
YAML.load(DATA).each{|region,states|
r = Region.new(region)
states.each{|state| r << State.new(state) }
}
p Region['Southwest Region']
p Region['Southwest Region'].each_state
Region['Southwest Region'].each_state{|state|
p state.name
}
__END__
Southwest Region:
- Texas
- Oklahoma
- Colorado
- New Mexico
- Utah
- Arizona
- Nevada.
Pacific:
- California
- Oregon
- Washington

A Hash is fine, you don't need anything fancier for this.
region = {
"Maine" => "New England",
"New Hampshire" => "New England",
etc
}
Which you then use like
region["Maine"]
Or if you want to set it up more compactly, like this:
regions = {
"Southwest" => ["Texas", "Oklahoma", "Colorado", "New Mexico", "Utah", "Arizona", "Nevada"],
"New England" => ["Maine", "New Hampshire", "Vermont", "Massachusetts","Rhode Island", "Connecticut"],
etc
}
region = {}
regions.each do |r,states|
states.each do |state|
region[state] = r
end
end

Related

compose objects without initializing objects that are not in hash

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

How do you iterate over an array to create a hash?

I need to create a hash from the array below, and have it look like - peoples_ages = {"Joe"=> 25}. I can iterate over it using each_with_index, but I don't need the index as the value, I need the person's age. Instead I was thinking of using either Hash[people_array]... or .each_with_object. Is it best to use .map instead and just put .to_h after?
class Person
attr_reader :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
nick = Person.new("Nick", 25)
jim = Person.new("Jim", 37)
bob = Person.new("Bob", 23)
rob = Person.new("Rob", 29)
sue = Person.new("Sue", 31)
peeps = [nick, jim, bob, rob, sue]
# iterate over peeps array to create a hash that looks like this:
# people_ages = {
# "Nick" => 25,
# "Jim" => 37,
# "Bob" => 23,
# etc...
# }
peeps.each_with_object({}){|e, h| h[e.name] = e.age}
Hash[peeps.map {|person| [person.name, person.age]} ]
Or if Ruby 2.0
peeps.map {|person| [person.name, person.age]}.to_h

How do I calling an object's method using strings?

I've been looking for an answer to this question for a while, but I haven't been able to find one that I was able to understand and apply.
I have a class that contains three instance variables: #brand, #setup, and #year. I have a module, included in that class, that has three methods: print_brand(), print_setup(), and print_year() that simply print the value assigned to the associated variable.
I'd like to get two strings from a user and use the first as an object name and the second as a method name. Here's what I have right now:
class Bike
include(Printers)
def initialize(name, options = {})
#name = name
#brand = options[:brand]
#setup = options[:setup]
#year = options[:year]
end
end
trance = Bike.new("trance x3", {
:brand => "giant",
:setup => "full sus",
:year => 2011
}
)
giro = Bike.new("giro", {
:brand => "bianchi",
:setup => "road",
:year => 2006
}
)
b2 = Bike.new("b2", {
:brand => "felt",
:setup => "tri",
:year => 2009
}
)
puts "Which bike do you want information on?"
b = gets()
b.chomp!
puts "What information are you looking for?"
i = gets()
i.chomp!
b.send(i)
I'm missing some functionality that converts b from a string to an object name. For example, I want the user to be able to type in "trance" and then "print_year" and have "2011" printed on the screen. I tried to use constantize on b, but that doesn't seem to work. I get the error:
in 'const_defined?': wrong constant name trance (NameError)
Any other ideas?
You should store the object in a hashmap with key = name and value = object, then use b (name) to retrieve the right object from the hashmap. I'm still not sure what do you want to do with the second input, by my guess is that this answer covers that as well.
h = Hash.new()
h["trance x3"] = trance
h["giro"] = giro
...
puts "Which bike do you want information on?"
b = gets()
b.chomp!
user_bike = h[b]
puts "What information are you looking for?"
i = gets()
i.chomp!
user_bike.send(i)
I would use eval:
eval "#{ b }.#{ i }"
I suppose you have to add accessors:
attr_accessor :brand, :setup, :year

Merge Ruby arrays

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

Ruby 1.9: turn these 4 arrays into hash of key/value pairs

I have four arrays that are coming in from the client. Let's say that there is an array of names, birth dates, favorite color and location. The idea is I want a hash later where each name will have a hash with respective attributes:
Example date coming from the client:
[name0, name1, name2, name3]
[loc0, loc1]
[favcololor0, favcolor1]
[bd0, bd1, bd2, bd3, bd4, bd5]
Output I'd like to achieve:
name0 => { location => loc0, favcolor => favcolor0, bd => bd0 }
name1 => { location => loc1, favcolor => favcolor1, bd => bd1 }
name2 => { location => nil, favcolor => nil, bd => bd2 }
name3 => { location => nil, favcolor => nil, bd => bd3 }
I want to have an array at the end of the day where I can iterate and work on each particular person hash.
There need not be an equivalent number of values in each array. Meaning, names are required.. and I might receive 5 of them, but I only might receive 3 birth dates, 2 favorite colors and 1 location. Every missing value will result in a nil.
How does one make that kind of data structure with Ruby 1.9?
I would probably do it like this
# assuming names, fav_colors, birth_dates, and locations are your arrays
name_collection = {}
names.zip(birth_dates, fav_colors, locations) do |name, birth_date, fav_color, location|
name_collection[name] = { :birth_date => birth_date,
:fav_color => fav_color,
:location => location }
end
# usage
puts name_collection['jack'][:fav_color] # => 'blue'
A small class to represent a person
class Person
attr_accessor :name, :color, :loc, :bd
def initialize(args = {})
#name = args[:name]
#color = args[:color]
#loc = args[:loc]
#bd = args[:bd]
end
def pp()
puts "*********"
puts "Name: #{#name}"
puts "Location: #{#loc}"
puts "Birthday: #{#bd}"
puts "Fav. Color: #{#color}"
puts "*********"
end
end
another to represent people, which is mainly just a listing of Persons.
class People
attr_accessor :list_of_people
def initialize()
#list_of_people = {}
end
def load_people(names, locations, favcolors, birthdates)
names.each_with_index do |name, index|
#list_of_people[name] = Person.new(:name => name, :color => favcolors[index], :loc => locations[index], :bd => birthdates[index])
end
end
def pp()
#list_of_people.each_pair do |key, value|
value.pp()
end
end
end
I threw in a pretty print function for each so you can see their data. With a starting point like this it will be really easy to modify and add methods that do all sorts of useful things.
if __FILE__ == $0
names = ["name0", "name1", "name2", "name3"]
locs = ["loc0","loc1"]
favcolors = ["favcolor0", "favcolor1"]
bds = ["bd0","bd1","bd2","bd3","bd4"]
a = People.new()
a.load_people(names,locs,favcolors,bds)
a.pp()
end
I think the kind of data structure you're looking for is -ahem- a Struct.
# setup data
names = %w(name0 name1 name2 name3)
locations = %w(loc0 loc1)
colors = %w(favcololor0 favcolor1)
bd = %w(bd0 bd1 bd2 bd3 bd4 bd5)
# let's go
Person = Struct.new( :name, :location, :fav_color, :bd )
all_persons = names.zip( locations, colors, bd ).map{|p| Person.new( *p)}
# done
puts all_persons
someone= all_persons.find{|p| p.name == "name1"}
puts someone.location unless someone.nil?

Resources