Iterating through two arrays to make a hash in Ruby - arrays

I have two arrays that need to be combined into a hash. However, one of the arrays will always be the key. So I need to go through a list of names, numbers, addresses, etc and give them all titles. An example would be:
Adjustor name => Chase, Firm name => Chase Bank
and then repeat for another location.
Adjustor name => Rob, Firm name => Walmart.
Here is what I have so far:
Array_headers = ['Adjuster Name','Firm Name', 'Firm Number', 'Adjustment Type', 'Address 1', 'Address 2', 'City', 'State', 'Zip Code', 'Phone', 'Fax', 'Website', 'Comments', 'Latitude', 'Longitude', 'Manual LongLatCalc', 'LongLat Error']
Data_examples = ["AdjusterName", "FirmName", "FirmNumber", "AdjustmentType", "Address1", "Address2", "City", "State", "ZipCode", "Phone", "Fax", "WebSite", "Comments", "Latitude", "Longitude", "ManualLongLatCalc", "LongLatError", "chase", "chase bank", "260-239-1761", "property", "501 w", "200 s", "albion", "in", "46701", "555-555-5555", "c#gamil", "whatsupwhatups.com", "hahahah", "12.332", "12.222", "no", "none"]
CombiningArrays= Hash[Array_headers.zip data_examples]
p CombiningArrays
It should return the following:
{"Adjuster Name"=>"AdjusterName", "Firm Name"=>"FirmName", "Firm Number"=>"FirmNumber", "Adjustment Type"=>"AdjustmentType", "Address 1"=>"Address1", "Address 2"=>"Address2", "City"=>"City", "State"=>"State", "Zip Code"=>"ZipCode", "Phone"=>"Phone", "Fax"=>"Fax", "Website"=>"WebSite", "Comments"=>"Comments", "Latitude"=>"Latitude", "Longitude"=>"Longitude", "Manual LongLatCalc"=>"ManualLongLatCalc", "LongLat Error"=>"LongLatError", *"Adjuster Name"=>" \r\nchase", "Firm Name"=>"chase", "Firm Number"=>"260-239-1761", "Adjustment Type"=>"property", "Address 1"=>"501 w", "Address 2"=>"200 s", "City"=>"albion", "State"=>"in", "Zip Code"=>"46701", "Phone"=>"555-555-5555", "Fax"=>"c#gamil", "Website"=>"whatsupwhatups.com", "Comments"=>"hahahah", "Latitude"=>"12.332", "Longitude"=>"12.222", "Manual LongLatCalc"=>"no", "LongLat Error"=>"none"*}
It stops at "LongLat Error"=>"LongLatError" and everything that is italicized does not show up. How do I get it to continually loop through my other array?
I also tried the following code:
#Creating a method to go through arrays
def array_hash_converter headers, data
hash = Hash.new
headers.each_with_index do |header, index|
hash[header] = data[index]
end
puts hash
end
i=0
while i < data.count do
array_hash_converter Array_header, data
i+=1
end
Please Help!

I suggest to slice the values array on the keys array length, and then just map them into an array of hashes. For example:
sliced_values = Data_examples.each_slice(Array_headers.length)
result = sliced_values.map { |slice| Array_headers.zip(slice).to_h }
You will never get a single hash as result, because you'll have collision on the keys and, then, only the last result will be returned, since it overwrites the previous ones. Remember that hash keys are unique in Ruby.

Looks like you actually want an array of hashes. So, your first array is going to be your list of keys for your hashes (I'll reference to this as "HEADER_KEYS"). In your second array, I see "\r\n". You may want to back up a step. I'm assuming this is coming from a CSV, so there are known delimiters and an unknown number of rows. To start parsing your CSV, split on the "\r\n" (or whatever the line break happens to be), and then iterate over each of those items and split on the commas. Something like:
final_dataset = []
HEADER_KEYS = [].freeze # put your actual array_of_headers here and freeze it
array_of_rows = []
csv_string.split("\r\n").each { |row| array_of_rows.push(row.split) }
This should give you an array of arrays that you can loop over.
array_of_rows.each do |row|
row_hash = {}
HEADER_KEYS.each_with_index do |key, index|
row_hash[key] = row[index]
end
final_dataset.push(row_hash)
end
There may be a more elegant way of handling this, but this should do the trick to get you going.

Related

How to remove square brackets (only) from an array? [duplicate]

This question already has an answer here:
How to pass an array as an argument list
(1 answer)
Closed 10 months ago.
I have an array stored in a constant like this:
FIELDS = ["first_name", "last_name", "occupation"]
I need to remove the square brackets only, it will look like this:
"first_name", "last_name", "occupation"
Does anyone have any ideas?
Thanks. :-)
For a little more context:
I have a complicated hash where I need to grab specific values from. My idea was to have the key of each value stored as an array, so I could then so
hash.values_at("first_name", "last_name", "occupation")
But that won't work with the square brackets from the array, hence my question!
I may be going around this the wrong way, however!
hash.values_at(*FIELDS)
The asterisk is called the splat operator. Doc
Edit: Now that you've added some context to your question, I see you'd like to do something quite different.
Here is how you would iterate (loop) over a list of key hashes to get their values:
EXAMPLE_HASH = { 'first_name' => 'Jane', 'last_name' => 'Doe', 'occupation' => 'Developer', 'other_key' => 'Dont return this' }.freeze
FIELDS = ['first_name', 'last_name', 'occupation'].freeze # Actually keys.
results = FIELDS.map{ |key|
EXAMPLE_HASH[key]
}
=> ["Jane", "Doe", "Developer"]
Or more succinctly:
EXAMPLE_HASH = { 'first_name' => 'Jane', 'last_name' => 'Doe', 'occupation' => 'Developer', 'other_key' => 'Dont return this' }.freeze
FIELDS = ['first_name', 'last_name', 'occupation'].freeze # Actually keys.
results = EXAMPLE_HASH.values_at(*FIELDS)
=> ["Jane", "Doe", "Developer"]
This is a very odd thing to do as the array doesn't actually have those square brackets in reality, it's just how the array prints out to give an observer the clue that it is any array.
So you can't ever actually "remove" those square brackets from an array, since they aren't there to begin with.
You could potentially override the to_s (and inspect?) methods on the Array class so it would print out differently.
But assuming you want a string as the output instead, this would accomplish the task:
FILEDS = ["first_name", "last_name", "occupation"]
fields_as_string = "\"#{FILEDS.join('", "')}\""
=> "\"first_name\", \"last_name\", \"occupation\""
However, it's not clear if you want a string as the output, nor is it clear if you're happy with the double-quotes being escaped in the string.

Is there a way of reading from sub arrays?

I am currently building an iOS application that stores user added products using Google Firestore. Each product that is added is concatenated into a single, user specific "products" array (as shown below - despite having separate numbers they are part of the same array but separated in the UI by Google to show each individual sub-array more clearly)
I use the following syntax to return the data from the first sub-array of the "products" field in the database
let group_array = document["product"] as? [String] ?? [""]
if (group_array.count) == 1 {
let productName1 = group_array.first ?? "No data to display :("`
self.tableViewData =
[cellData(opened: false, title: "Item 1", sectionData: [productName1])]
}
It is returned in the following format:
Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17
However I am trying to query each of the individual sections of this sub array, so for example, I can return "Product Name: 1" instead of the whole sub-array. As let productName1 = group_array.first is used to return the first sub-array, I have tried let productName1 = group_array.first[0] to try and return the first value in this sub-array however I receive the following error:
Cannot infer contextual base in reference to member 'first'
So my question is, referring to the image from my database (at the top of my question), if I wanted to just return "Product Name: 1" from the example sub-array, is this possible and if so, how would I extract it?
I would reconsider storing the products as long strings that need to be parsed out because I suspect there are more efficient, and less error-prone, patterns. However, this pattern is how JSON works so if this is how you want to organize product data, let's go with it and solve your problem.
let productRaw = "Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17"
First thing you can do is parse the string into an array of components:
let componentsRaw = productRaw.components(separatedBy: ", ")
The result:
["Product Name: 1", "Listing Price: 3", "A brief description: 4", "Product URL: 2", "Listing active until: 21/04/2021 10:22:17"]
Then you can search this array using substrings but for efficiency, let's translate it into a dictionary for easier access:
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: ": ")
product[keyVal[0]] = keyVal[1]
}
The result:
["Listing active until": "21/04/2021 10:22:17", "A brief description": "4", "Product Name": "1", "Product URL": "2", "Listing Price": "3"]
And then simply find the product by its key:
if let productName = product["Product Name"] {
print(productName)
} else {
print("not found")
}
There are lots of caveats here. The product string must always be uniform in that commas and colons must always adhere to this strict formatting. If product names have colons and commas, this will not work. You can modify this to handle those cases but it could turn into a bowl of spaghetti pretty quickly, which is also why I suggest going with a different data pattern altogether. You can also explore other methods of translating the array into a dictionary such as with reduce or grouping but there are big-O performance warnings. But this would be a good starting point if this is the road you want to go down.
All that said, if you truly want to use this data pattern, consider adding a delimiter to the product string. For example, a custom delimiter would greatly reduce the need for handling edge cases:
let productRaw = "Product Name: 1**Listing Price: 3**A brief description: 4**Product URL: 2**Listing active until: 21/04/2021 10:22:17"
With a delimiter like **, the values can contain commas without worry. But for complete safety (and efficiency), I would add a second delimiter so that values can contain commas or colons:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
With this string, you can much more safely parse the components by ** and the value from the key by $$. And it would look something like this:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
let componentsRaw = productRaw.components(separatedBy: "**")
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: "$$")
product[keyVal[0]] = keyVal[1]
}
if let productName = product["name"] {
print(productName)
} else {
print("not found")
}

How to return an array of arrays that contains hashes

This is my method that is called by the test function:
def movies_with_directors_set(source)
director = []
director1 = []
hash = {}
while outer_index < source.length do
inner_index = 0
while inner_index < source[outer_index][:movies].length do
hash[:title] = []
hash[:title] = source[outer_index][:movies][inner_index][:title]
hash[:director_name] = []
hash[:director_name] = source[outer_index][:name]
director1 << hash.dup
inner_index +=1
end
director << director1.dup
outer_index += 1
end
return director
end
This is the test code:
describe 'movies_with_directors_set' do
describe 'when given a Hash with keys :name and :movies,' do
describe 'returns an Array of Hashes that represent movies' do
describe 'and each Hash has a :director_name key set with the value that was in :name' do
# This lets "sample_data" be used in the two "it" statements below
let (:test_data) {
[
{ :name => "Byron Poodle", :movies => [
{ :title => "At the park" },
{ :title => "On the couch" },
]
},
{ :name => "Nancy Drew", :movies => [
{ :title => "Biting" },
]
}
]
}
it 'correctly "distributes" Byron Poodle as :director_name of the first film' do
# { :name => "A", :movies => [{ :title => "Test" }] }
# becomes... [[{:title => "Test", :director_name => "A"}], ...[], ... []]
results = movies_with_directors_set(test_data)
expect(results.first.first[:director_name]).to eq("Byron Poodle"),
"The first element of the AoA should have 'Byron Poodle' as :director_name"
end
it 'correctly "distributes" Nancy Drew as :director_name of the last film' do
results = movies_with_directors_set(test_data)
expect(results.last.first[:director_name]).to eq("Nancy Drew"),
"The last element of the AoA should have 'Nancy Drew' as :director_name"
end
end
end
end
end
My method returns an array of hashes but for some reason it does not want to pass the test, as it tells me the second part of the test fails.
It could be that it requires arrays within an array due to the way the test is worded.
source is the database that gets passed into the method. This specific database is shown in the test code.
If you want to transform the structure from a nested director-name/titles into director-name/title pairs, there's a much easier way of going about that. Ruby's strength is in the Enumerable library which makes data transformation really fast, efficient, and easy to express. Here's an approach worth using:
def movies_with_directors(source)
# flat_map will join together the inner arrays into a single contiguous array
source.flat_map do |set|
set[:movies].map do |movie|
{
director_name: set[:name],
title: movie[:title]
}
end
end
end
This produces a very flat, easy to navigate structure like this:
# => [{:director_name=>"Byron Poodle", :title=>"At the park"}, {:director_name=>"Byron Poodle", :title=>"On the couch"}, {:director_name=>"Nancy Drew", :title=>"Biting"}]
Where you can iterate over that and assert more easily without having to do .first.first and such.
When using Ruby always try and think in terms of data transformation, not in terms of loops. There's a multitude of tools in the Enumerable library that can perform complicated operations with a single line of code. In your case you needed a combination of flat_map and map, and you're done.
Your test data and test expectations seem to be out of sync.
You have a :name key, and then expect :director_name to be present in your test hash. Change :director_name to be :name and it should pass:
{ :name => "Nancy Drew", :movies => [
and
expect(results.last.first[:director_name]).to eq("Nancy Drew")
So your failing test is probably saying something like expected nil to be "Nancy Drew", right?

Ruby way of summing an array of objects by field

I have an array of objects that I'd like to group by field1 and sum by field2. An example would be a class product that has a title field and a price field.
In an array of products, I have multiple gloves with different prices, and multiple hats with different prices. I'd like to have an array with distinct titles, that aggregate all the prices under the same title.
There's an obvious solution with iterating over the array and using a hash, but I was wondering if there was a "ruby way" of doing something like this? I've seen a lot of examples where Ruby has some unique functionality that applies well to certain scenarios and being a Ruby newbie I'm curious about this.
Thanks
There's a method transform_values added in ruby 2.4 or if you require 'active_support/all', with this you can do something like so:
products = [
{type: "hat", price: 1, name: "fedora"},
{type: "hat", price: 2, name: "sombrero"},
{type: "glove", price: 3, name: "mitten"},
{type: "glove", price: 4, name: "wool"}
]
result = products
.group_by { |product| product[:type] }
.transform_values { |vals| vals.sum { |val| val[:price] } }
# => {"hat"=>3, "glove"=>7}
It's a little unclear to me from the question as asked what your data looks like, so I ended up with this:
Product = Struct.new(:title, :price)
products = [
Product.new("big hat", 1),
Product.new("big hat", 2),
Product.new("small hat", 3),
Product.new("small hat", 4),
Product.new("mens glove", 8),
Product.new("mens glove", 9),
Product.new("kids glove", 1),
Product.new("kids glove", 2)
]
Given that data, this is how I'd go about building a data structure which contains the sum of all the prices for a given title:
sum_by_title = products.inject({}) do |sums, product|
if sums[product.title]
sums[product.title] += product.price
else
sums[product.title] = product.price
end
sums
end
This produces:
{"big hat"=>3, "small hat"=>7, "mens glove"=>17, "kids glove"=>3}
To explain:
Ruby inject takes an initial value and passes that to the iteration block as a "memo". Here, {} is the initial value. The return value from the block is passed into the next iteration as the memo.
The product.title is used as a hash key and the running sum is stored in the hash value. An if statement is necessary because the first time a product title is encountered, the stored value for that title is nil and cannot be incremented.
I probably wouldn't ship this code due to the hidden magic of the hash default value constructor but it's possible to write the same code without the if statement:
sum_by_title = products.inject(Hash.new { 0 }) do |sums, product|
sums[product.title] += product.price
sums
end
Hope you enjoy Ruby as much as I do!

How do I generate a table from an array of hashes without consistent keys?

I have an array of hashes that I want to turn into a table, but the tricky part is that the hash keys are not consistent:
a = [
{
"name" => "Jack",
"phone" => "9542221234",
"state" => "FL"
},
{
"name" => "John",
"job" => "Lawyer"
},
{
"name" => "Mike",
"campaign" => "test",
"state" => "NY"
}
]
I am at a loss for how to loop through the array, pull out the unique key name's and add the applicable values to rows. I'm trying to achieve this effect:
Name | Phone | State | Campaign | Job
---------------------------------------------
Jack 9542221234 FL
John Lawyer
Mike NY test
I searched for a solution and looked into different gems such as Builder, but every example I found assumes that the key names are consistent and pulls the table header keys from the first hash within the array.
cols = a.map(&:keys).flatten.uniq
cols.each do |colname|
printf "%-10s ", colname
end
puts
a.each do |row|
cols.each do |colname|
printf "%-10s ", row[colname]
end
puts
end

Resources