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} }
Related
I have an array that contains other arrays of items with prices but when one has a sale a new item is created How do I merge or pull value from one to the other to make 1 array so that the sale price replaces the non sale but contains the original price?
Example:
items=[{"id": 123, "price": 100, "sale": false},{"id":456,"price":25,"sale":false},{"id":678, "price":75, "sale":true, "parent_price_id":123}]
Transform into:
items=[{"id":456,"price":25,"sale":false},{"id":678, "price":75, "sale":true, "parent_price_id":123, "original_price": 100}]
It's not the prettiest solution, but here's one way you can do it. I added a minitest spec to check it against the values you provided and it gives the answer you're hoping for.
require "minitest/autorun"
def merge_prices(prices)
# Create a hash that maps the ID to the values
price_map =
prices
.map do |price|
[price[:id], price]
end
.to_h
# Create a result array which is initially duplicated from the original
result = prices.dup
result.each do |price|
if price.key?(:parent_price)
price[:original_price] = price_map[price[:parent_price]][:price]
# Delete the original
result.delete_if { |x| x[:id] == price[:parent_price] }
end
end
result
end
describe "Merge prices" do
it "should work" do
input = [
{"id":123, "price": 100, "sale": false},
{"id":456,"price":25,"sale": false},
{"id":678, "price":75, "sale": true, "parent_price":123}
].freeze
expected_output = [
{"id":456,"price":25,"sale": false},
{"id":678, "price":75, "sale": true, "parent_price":123, "original_price": 100}
].freeze
assert_equal(merge_prices(input), expected_output)
end
end
Let's being by defining items in an equivalent, but more familiar, way:
items = [
[{:id=>123, :price=>100, :sale=>false}],
[{:id=>456, :price=>25, :sale=>false}],
[{:id=>678, :price=>75, :sale=>true, :parent_price=>123}]
]
with the desired return value being:
[
{:id=>456, :price=>25, :sale=>false},
{:id=>678, :price=>75, :sale=>true, :parent_price=>123,
:original_price=>100}
]
I assume that h[:sale] #=> false for every element of items (a hash) g for which g[:parent_price] = h[:id].
A convenient first step is to create the following hash.
h = items.map { |(h)| [h[:id], h] }.to_h
#=> {123=>{:id=>123, :price=>100, :sale=>false},
# 456=>{:id=>456, :price=>25, :sale=>false},
# 678=>{:id=>678, :price=>75, :sale=>true, :parent_price=>123}}
Then:
h.keys.each { |k| h[k][:original_price] =
h.delete(h[k][:parent_price])[:price] if h[k][:sale] }
#=> [123, 456, 678] (not used)
h #=> {456=>{:id=>456, :price=>25, :sale=>false},
# 678=>{:id=>678, :price=>75, :sale=>true, :parent_price=>123,
# :original_price=>100}}
Notice that Hash#delete returns the value of the deleted key.
The last two steps are to extract the values from this hash and replace items with the resulting array of hashes:
items.replace(h.values)
#=> [{:id=>456, :price=>25, :sale=>false},
# {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
# :original_price=>100}]
See Array#replace.
If desired we could combine these steps as follows.
items.replace(
items.map { |(h)| [h[:id], h] }.to_h.tap do |h|
h.keys.each { |k| h[k][:original_price] =
h.delete(h[k][:parent_price])[:price] if h[k][:sale] }
end.values)
#=> [{:id=>456, :price=>25, :sale=>false},
# {:id=>678, :price=>75, :sale=>true, :parent_price=>123,
# :original_price=>100}]
See Object#tap.
I am trying to access a specific value inside an array. The array contains specific class instance variables and is as follows:
[[#<Supermarket:0x007f8e989daef8 #id=1, #name="Easybuy">,
#<Delivery:0x007f8e989f98a8 #type=:standard, #price=5.0>],
[#<Supermarket:0x007f8e99039f88 #id=2, #name="Walmart">,
#<Delivery:0x007f8e989f98a8 #type=:standard, #price=5.0>],
[#<Supermarket:0x007f8e9901a390 #id=3, #name="Forragers">,
#<Delivery:0x007f8e989eae20 #type=:express, #price=10.0>]]
I want to iterate over each array inside the array and find out how many Delivery's within the array have #type:standard. Is this possible? Thank you in advance
array_of_array.inject(0) do |sum, array|
sum + array.count { |el| el.class == Delivery && el.instance_variable_get(:#type) == :standard }
end
You can use select() to filter the elements of an array.
Reconstructing your data:
require 'ostruct'
require 'pp'
supermarket_data = [
['Easybuy', 1],
['Walmart', 2],
['Forragers', 3],
]
supermarkets = supermarket_data.map do |(name, id)|
supermarket = OpenStruct.new
supermarket.name = name
supermarket.id = id
supermarket
end
delivery_data = [
['standard', 5.0],
['standard', 5.0],
['express', 10.0],
]
deliveries = delivery_data.map do |(type, price)|
delivery = OpenStruct.new
delivery.type = type
delivery.price = price
delivery
end
combined = supermarkets.zip deliveries
pp combined
[[#<OpenStruct name="Easybuy", id=1>,
#<OpenStruct type="standard", price=5.0>],
[#<OpenStruct name="Walmart", id=2>,
#<OpenStruct type="standard", price=5.0>],
[#<OpenStruct name="Forragers", id=3>,
#<OpenStruct type="express", price=10.0>]]
Filtering the array with select():
standard_deliveries = combined.select do |(supermarket, delivery)|
delivery.type == 'standard'
end
pp standard_deliveries # pretty print
p standard_deliveries.count
[[#<OpenStruct name="Easybuy", id=1>,
#<OpenStruct type="standard", price=5.0>],
[#<OpenStruct name="Walmart", id=2>,
#<OpenStruct type="standard", price=5.0>]]
2
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
I have two arrays
b = ["John Roberts", "William Koleva", "Lili Joe", "Victoria Jane", "Allen Thomas"]
a = ["Jon Roberts", "Wil Koleva", "Lilian Joe", "Vic Jane", "Al Thomas"]
Currently I am using the union operator on these two arrays, like this: a | b. When combined, even though the names in each array are the "same" name (they're just using the shortened version of the name), it will duplicate my names.
My proposed solution to this is simply choose the first occurrence of first initial + last name as the name to perform the union on, however, I don't recall there being any methods in Ruby that can perform such an operation.
So the result of some_method(a | b) will return c which is just:
["John Roberts", "William Koleva", "Lili Joe", "Victoria Jane", "Allen Thomas"]
I am wondering how I could go about achieving this?
b = ["John Roberts", "William Koleva", "Lili Joe", "Victoria Jane", "Allen Thomas"]
a = ["Jon Roberts", "Wil Koleva", "Lilian Joe", "Vic Jane", "Al Thomas"]
r = /
\s # match a space
[[:alpha:]]+ # match > 0 alphabetic characters
\z # match end of string
/x # free-spacing regex definition mode
(b+a).uniq { |str| [str[0], str[r]] }
#=> ["John Roberts", "William Koleva", "Lili Joe", "Victoria Jane", "Allen Thomas"]
This uses the form of the method Array#uniq that employs a block.
You may alternatively write (b|a).uniq { |str| [str[0], str[r]] }
The steps are as follows.
c = b+a
# => ["John Roberts", "William Koleva", "Lili Joe", "Victoria Jane", "Allen Thomas",
# "Jon Roberts", "Wil Koleva", "Lilian Joe", "Vic Jane", "Al Thomas"]
The first element of c passed to the block is
str = c.first
#=> "John Roberts"
so the block calculation is
[str[0], str[r]]
#=> ["J", " Roberts"]
The calculations are similar for all the other elements of c. The upshot is that
c.uniq { |str| [str[0], str[r]] }
is equivalent to selecting the first elements of c, when converted to [<first name initial>, <last name>], that match an element of the array d, where
d = [["J", "Roberts"], ["W", "Koleva"], ["L", "Joe"], ["V", "Jane"], ["A", "Thomas"],
["J", "Roberts"], ["W", "Koleva"], ["L", "Joe"], ["V", "Jane"], ["A", "Thomas"]].uniq
#=> [["J", "Roberts"], ["W", "Koleva"], ["L", "Joe"], ["V", "Jane"], ["A", "Thomas"]]
Pascal suggested that it would be better for uniq's block to return a string:
{ |str| "#{str[0]} #{str[r]}" }
(e.g., "J Roberts") which might instead be written
{ |str| str.sub(/(?<=.)\S+/,"") }
The inclusion of the space after the first initial is optional (e.g., "JRoberts" would also work).
Sure, just use Enumerable#uniq with a block:
c = (a | b).uniq do |full_name|
first_name, last_name = full_name.split(nil, 2)
[first_name[0], last_name]
end
Note: the first iteration of the code used the initials instead of abbreviated name.
Perhaps you can introduce the concept of a Name? It's a bit more code than just providing a block to uniq but it nicely encapsulates everything related.
class Name
def initialize(first, last)
#first, #last = first, last
end
def abbreviated
"#{#first[0]} #{#last}"
end
def eql?(other)
return false if !other.respond_to?(:abbreviated)
abbreviated == other.abbreviated
end
def hash
abbreviated.hash
end
def full
"#{#first} #{#last}"
end
end
a = Name.new('John', 'Roberts')
b = Name.new('Jon', 'Roberts')
c = Name.new('William', 'Koleva')
d = Name.new('Wil', 'Koleva')
x = [a, c]
y = [b, d]
p (y | x).map(&:full)
It's worth noting that abbreviated firstname does not really suffice to check equality of names.
Consider:
Jim Jackson
James Jackson
Janine Jackson
...
#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.