How to convert a 2D array to a value object in ruby - arrays

I have a 2D array:
a = [["john doe", "01/03/2017", "01/04/2017", "event"], ["jane doe", "01/05/2017", "01/06/2017", "event"]...]
I would like to convert it to a value object in ruby. I found how to do it with a hash Ruby / Replace value in array of hash in the second answer of this question but not a 2D array. I would like to assign the value at a[0][0] to an attribute named "name", a[0][1] to "date1", a[0][2] to "date2" and a[0][3] to "event".
This is something like what I'd like to accomplish although it is not complete and I dont know how to assign multiple indexes to the different attributes in one loop:
class Schedule_info
arrt_accessor :name, :date1, :date2, :event
def initialize arr
#I would like this loop to contain all 4 attr assignments
arr.each {|i| instance_variable_set(:name, i[0])}

This should be short and clean enough, without unneeded metaprogramming :
data = [["john doe", "01/03/2017", "01/04/2017", "event"],
["jane doe", "01/05/2017", "01/06/2017", "event"]]
class ScheduleInfo
attr_reader :name, :date1, :date2, :type
def initialize(*params)
#name, #date1, #date2, #type = params
end
def to_s
format('%s for %s between %s and %s', type, name, date1, date2)
end
end
p info = ScheduleInfo.new('jane', '31/03/2017', '01/04/2017', 'party')
# #<ScheduleInfo:0x00000000d854a0 #name="jane", #date1="31/03/2017", #date2="01/04/2017", #type="party">
puts info.name
# "jane"
schedule_infos = data.map{ |params| ScheduleInfo.new(*params) }
puts schedule_infos
# event for john doe between 01/03/2017 and 01/04/2017
# event for jane doe between 01/05/2017 and 01/06/2017

You can't store the key value pairs in array index. Either you need to just remember that first index of array is gonna have "name" and assign a[0][0] = "foo" or just use array of hashes for the key value functionality you want to have
2.3.0 :006 > a = []
=> []
2.3.0 :007 > hash1 = {name: "hash1name", date: "hash1date", event: "hash1event"}
=> {:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}
2.3.0 :008 > a << hash1
=> [{:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}]
2.3.0 :009 > hash2 = {name: "hash2name", date: "hash2date", event: "hash2event"}
=> {:name=>"hash2name", :date=>"hash2date", :event=>"hash2event"}
2.3.0 :010 > a << hash2
=> [{:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}, {:name=>"hash2name", :date=>"hash2date", :event=>"hash2event"}]

It sounds like you want to call the attribute accessor method that corresponds to each array value. You use send to call methods programmatically. So you need an array of the method names that corresponds to the values you have in your given array. Now, assuming the class with your attributes is called Data.
attrs = [:name, :date1, :date2, :event]
result = a.map do |e|
d = Data.new
e.each.with_index do |v, i|
d.send(attrs[i], v)
end
d
end
The value result is an array of Data objects populated from your given array.
Of course, if you control the definition of your Data object, the best things would be to give it an initialize method that takes an array of values.

Try this:
class Schedule_info
arrt_accessor :name, :date1, :date2, :event
def initialize arr
#name = []
#date1 = []
#date2 = []
#event = []
arr.each |i| do
name << i[0]
date1 << i[1]
date2 << i[2]
event << i[3]
end
end
end

Related

Turning an array into an array of hashes using .each

Given this array of String names, return an array of Hashes. Each Hash should have the keys name and id, which will represent their unique identifier in the form of an integer. The ids can start at 1 and then go up by one.
output should look like:
[{:name=>"Bruno", :id=>1},
{:name=>"Bella", :id=>2},
{:name=>"Ringo", :id=>3},
{:name=>"Spot", :id=>4},
{:name=>"Fluffy", :id=>5},
{:name=>"Snowball", :id=>6},
{:name=>"Doc", :id=>7}]
I'm trying to do something like this but I'm having trouble coming up with the right syntax and methods.
NAMES = ['Bella', 'Bruno', 'Ringo', 'Spot', 'Fluffy', 'Snowball', 'Doc']
def array_to_hash_array(names)
i = 1
NAMES.each do |name|
name = Hash.new(:name => name, :id => i)
NAMES << name
i += 1
end
return NAMES
end
puts array_to_hash_array(NAMES)
You can use map and with_index to return an array of Hash objects with the name and id values:
names = ['Bella', 'Bruno', 'Ringo', 'Spot', 'Fluffy', 'Snowball', 'Doc']
def array_to_hash_array(array)
array.map.with_index(1) do |name, index|
{ id: index, name: name }
end
end
p array_to_hash_array(names)
# => [{:id=>1, :name=>"Bella"}, {:id=>2, :name=>"Bruno"}, {:id=>3, :name=>"Ringo"}, {:id=>4, :name=>"Spot"}, {:id=>5, :name=>"Fluffy"}, {:id=>6, :name=>"Snowball"}, {:id=>7, :name=>"Doc"}]
I've reworked the code in your example to produce the correct output (with some notes on what was tweaked):
# variable renamed from NAMES as uppercase is meant for constants
names = ['Bella', 'Bruno', 'Ringo', 'Spot', 'Fluffy', 'Snowball', 'Doc']
def array_to_hash_array(array)
# added a result array for building up the final object to be returned
result = []
i = 1
# changed to iterate over the passed in array object
array.each do |name|
# using Hash literal instead of Hash.new since it allows us
# to specify contents of hash object directly
name = { :name => name, :id => i }
# push to result object instead of same iterated array
result << name
i += 1
end
# return built up result object
result
end
p array_to_hash_array(names)
# => [{:name=>"Bella", :id=>1}, {:name=>"Bruno", :id=>2}, {:name=>"Ringo", :id=>3}, {:name=>"Spot", :id=>4}, {:name=>"Fluffy", :id=>5}, {:name=>"Snowball", :id=>6}, {:name=>"Doc", :id=>7}]
Indexing an Array with #each_with_index
As Array includes the Enumerable module, you can invoke Array#each_with_index to create the data structure you want using the array index of each element of your names array with a suitable offset. For example:
names = %w[Bella Bruno Ringo Spot Fluffy Snowball Doc]
array = []
names.each_with_index do |name, index|
array << { name: name, id: index.succ }
end
Note the use of Integer#succ, which is used to offset the index by one. Since Ruby arrays are zero-indexed, you need to increment the index value passed in by #each_with_index to get the numbering the way you want.
You can pretty-print the array with Kernel#pp (e.g. pp array) to verify the results:
[{:name=>"Bella", :id=>1},
{:name=>"Bruno", :id=>2},
{:name=>"Ringo", :id=>3},
{:name=>"Spot", :id=>4},
{:name=>"Fluffy", :id=>5},
{:name=>"Snowball", :id=>6},
{:name=>"Doc", :id=>7}]
Note on Hash Key Ordering
As you can see, this populated the array of hashes the way you wanted, and in the specific format you're looking for. However, it's important to remember that while the order of keys in a hash are kept in insertion order in mainline Ruby, you shouldn't really rely on that behavior. Conceptually, hashes are unordered, so:
[{:id=>1, :name=>"Bella"},
{:id=>2, :name=>"Bruno"},
{:id=>3, :name=>"Ringo"},
{:id=>4, :name=>"Spot"},
{:id=>5, :name=>"Fluffy"},
{:id=>6, :name=>"Snowball"},
{:id=>7, :name=>"Doc"}] ==
[{:name=>"Bella", :id=>1},
{:name=>"Bruno", :id=>2},
{:name=>"Ringo", :id=>3},
{:name=>"Spot", :id=>4},
{:name=>"Fluffy", :id=>5},
{:name=>"Snowball", :id=>6},
{:name=>"Doc", :id=>7}]
#=> true
You can try it
names.each_with_index.map { |x,i| {name: x, id: i+1} }
names = ['Bella', 'Bruno', 'Ringo', 'Spot', 'Fluffy', 'Snowball', 'Doc']
enum = 1.step
#=> (1.step)
names.map { |name| { name: name, id: enum.next } }
#=> [{:name=>"Bella", :id=>1}, {:name=>"Bruno", :id=>2},
# {:name=>"Ringo", :id=>3}, {:name=>"Spot", :id=>4},
# {:name=>"Fluffy", :id=>5}, {:name=>"Snowball", :id=>6},
# {:name=>"Doc", :id=>7}]
See Numeric#step and Enumerator#next.
You could zip (Enumerable#zip) to an endless range (beginless-endless-range) then map to the desired hash:
ary = ['Bella', 'Bruno', 'Ringo', 'Spot', 'Fluffy', 'Snowball', 'Doc']
ary.zip(1..).map { |name, id| {name: name, id: id} }

Iterate over array of objects. Then access object method if correct one is found. Otherwise create a new object in the array

I start with an empty array, and a Hash of key, values.
I would like to iterate over the Hash and compare it against the empty array. If the value for each k,v pair doesn't already exist in the array, I would like to create an object with that value and then access an object method to append the key to an array inside the object.
This is my code
class Test
def initialize(name)
#name = name
#values = []
end
attr_accessor :name
def values=(value)
#values << value
end
def add(value)
#values.push(value)
end
end
l = []
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z', 'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do |key, value|
l.any? do |a|
if a.name == value
a.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
end
p l
I would expect to see this:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
Instead I just get an empty array.
I think that the condition if a.name == value is not being met and then the add method isn't being called.
#Cyzanfar gave me a clue as to what to look for, and I found the answer here
https://stackoverflow.com/a/34904864/5006720
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
#ARL you're almost there! The last thing you need to consider is when found actually returns an object since detect will find a matching one at some point.
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
You actually only want to add a new instance of Test when found return nil. This code should yield your desired output:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
I observe two things in your code :
def values=(value)
#values << value
def add(value)
#values.push(value)
two methods do the same thing, pushing a value, as << is a kind of syntactic sugar meaning push
you have changed the meaning of values=, which is usually reserved for a setter method, equivalent to attire_writer :values.
Just to illustrate that there are many ways to do things in Ruby, I propose the following :
class Test
def initialize(name, value)
#name = name
#values = [value]
end
def add(value)
#values << value
end
end
h_cluster = {} # intermediate hash whose key is the cluster name
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z',
'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do | server, cluster |
puts "server=#{server}, cluster=#{cluster}"
cluster_found = h_cluster[cluster] # does the key exist ? => nil or Test
# instance with servers list
puts "cluster_found=#{cluster_found.inspect}"
if cluster_found
then # add server to existing cluster
cluster_found.add(server)
else # create a new cluster
h_cluster[cluster] = Test.new(cluster, server)
end
end
p h_cluster.collect { | cluster, servers | servers }
Execution :
$ ruby -w t.rb
server=server_1, cluster=cluster_x
cluster_found=nil
server=server_2, cluster=cluster_y
cluster_found=nil
server=server_3, cluster=cluster_z
cluster_found=nil
server=server_4, cluster=cluster_x
cluster_found=#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1]>
server=server_5, cluster=cluster_y
cluster_found=#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2]>
[#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1, :server_4]>,
#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2, :server_5]>,
#<Test:0x007fa7a619aac8 #name="cluster_z", #values=[:server_3]>]

Ruby - Filtering array of hashes based on another array

I am trying to filter an array of hashes based on another array. What's the best way to accomplish this? Here are the 2 brutes I've right now:
x=[1,2,3]
y = [{dis:4,as:"hi"},{dis:2,as:"li"}]
1) aa = []
x.each do |a|
qq = y.select{|k,v| k[:dis]==a}
aa+=qq unless qq.empty?
end
2) q = []
y.each do |k,v|
x.each do |ele|
if k[:dis]==ele
q << {dis: ele,as: k[:as]}
end
end
end
Here's the output I'm intending:
[{dis:2,as:"li"}]
If you want to select only the elements where the value of :dis is included in x:
y.select{|h| x.include? h[:dis]}
You can delete the nonconforming elements of y in place with with .keep_if
> y.keep_if { |h| x.include? h[:dis] }
Or reverse the logic with .delete_if:
> y.delete_if { |h| !x.include? h[:dis] }
All produce:
> y
=> [{:dis=>2, :as=>"li"}]
Yes use select, nonetheless here's another way which works:
y.each_with_object([]) { |hash,obj| obj << hash if x.include? hash[:dis] }

Max value within array of objects

I'm new in ruby. I'm trying to do the following but haven't succeeded.
I've got an array of objects, let's call it objs. Each object has multiple properties, one of those is a variable that holds a number, let's call it val1. I wanna iterate through the array of objects and determine the max value of val1 through all the objects.
I've tried the following:
def init(objs)
#objs = objs
#max = 0
cal_max
end
def cal_max
#max = #objs.find { |obj| obj.val1 >= max }
# also tried
#objs.each { |obj| #max = obj.val1 if obj.val1 >= #max }
end
As I said, I'm just learning about blocks.
Any suggestion is welcome.
Thanks
Let's say you have set up the following model:
class SomeObject
attr_accessor :prop1, :prop2, :val1
def initialize(prop1, prop2, val1)
#prop1 = prop1
#prop2 = prop2
#val1 = val1
end
end
#define your objects from the class above
david = SomeObject.new('David', 'Peters', 23)
steven = SomeObject.new('Steven', 'Peters', 26)
john = SomeObject.new('John', 'Peters', 33)
#define an array of the above objects
array = [david, steven, john]
Then use max_by by passing the condition into its block as follows to determine the object with the max val1 value. Finally call val1 to get the value of the max object.
array.max_by {|e| e.val1 }.val1 #=> 33
You may also consider using hashes (negating the need to define a new class), like so:
david = {f_name: 'David', s_name: 'Peters', age: 23}
steven = {f_name: 'Steven', s_name: 'Peters', age: 26}
john = {f_name: 'John', s_name: 'Peters', age: 33}
array = [david, steven, john]
array.max_by { |hash| hash[:age] }[:age] #=> 33
#objs.map(&:val1).max
That will invoke the method on each object, and create a new array of the results, and then find the max value. This is shorthand for:
#objs.map{ |o| o.val1 }.max
Alternatively, you can select the object with the largest value, and then operate on it (as properly recomended by Cary Swoveland below):
#objs.max_by(&:val1).val1

Finding an element in an array inside a hash

#members = {
approved: ["Jill"],
unapproved: ["Daniel"],
removed: ["John"],
banned: ["Daniel", "Jane"]
}
Very simply: making a program to track membership. In the above hash you can see the four membership status keys each with an array containing names.
I'm trying to create a find_member method which allows the user to enter a name and then searches each array for the name and tells the user which key the name was found in.
I'm not very good with hashes and in attempting to do this I've created a mess of loops and I imagine there's a very easy solution, I just haven't found it so far. Is there a really simple way to do this?
I've tried a few things and don't have all my past efforts still, but this is the latest mess I've ended up with, which is probably worse than what I had previously:
def find_member
puts "==Find Member=="
puts "Name: "
#name = gets.chomp
#members.each do |key|
key.values.each do |array|
array.each do |element|
if #name == element
puts "#{#name} found in #{key}"
else
puts "#{#name} not found in #{key}"
end
end
end
end
end
Thanks.
The most efficient way to do this is to create a one-to-many mapping of names to keys, and update that mapping only when #members changes.
def find_member(name)
update_names_to_keys_if_necessary
#member_to_keys[name]
end
def update_names_to_keys_if_necessary
new_hashcode = #members.hash
return if #old_members.hashcode == new_hashcode
#member_to_keys = #members.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h|
v.each { |name| h[name] << k } }
#old_members_hashcode = new_hashcode
end
Note that #old_members_hashcode evaluates to nil the first time update_names_to_keys_if_necessary is called, so #member_to_keys will be created at that time.
Initially we obtain
#member_to_keys
#=> {"Jill"=>[:approved], "Daniel"=>[:unapproved, :banned],
# "John"=>[:removed], "Jane"=>[:banned]}
Try it.
find_member("Jill")
#=> [:approved]
find_member("Daniel")
#=> [:unapproved, :banned]
find_member("John")
#=> [:removed]
find_member("Jane")
#=> [:banned]
find_member("Billy-Bob")
#=> []
You can use this itteration with include? method.
#members = {
approved: ["Jill"],
unapproved: ["Daniel"],
removed: ["John"],
banned: ["Daniel", "Jane"]
}
def find_member_group(name)
#members.each { |group, names| return group if names.include?(name) }
nil
end
#name = 'Jane'
group_name = find_member_group(#name)
puts group_name ? "#{#name} found in #{group_name}." : "#{#name} not found."
# => Jane found in banned.
Hash#select is the method to use here:
def find_member(name)
#members.select {|k,v| v.include? name }.keys
end
find_member("Jill") #=> [:approved]
find_member("Daniel") #=> [:unapproved, :banned]
find_member("John") #=> [:removed]
find_member("Jane") #=> [:banned]
Explanation:
select as the name suggests selects and maps only those elements that satisfy the condition in the corresponding code-block. The code-block negates the need for an if statement. Within the code-block we check each key-value pair and if its value includes the name argument, then that key-value pair is selected and mapped to the final output. Finally we're only interested in the memberships (namely the keys), so we apply the keys method to get these in the form of an array.

Resources