How to intersect arrays in ruby? - ruby-on-rails

i'm new to ruby and i want to instersect two arrays
validAccountTypes = [
'Asset' => 'Asset',
'Liability' => 'Liability',
'Equity' => 'Equity',
'Income' => 'Income',
'CostOfSales' => 'Cost of Sales',
'Expense' => 'Expenses',
'OtherIncome' => 'Other Income',
'OtherExpense' => 'Other Expenses',
]
types = [
'Asset',
'Other Income',
'Other Expenses',
]
I want a result of valid Accounts with keys base on array types. The output would be
[{"Asset"=>"Asset", "OtherIncome"=>"Other Income", "OtherExpense" => "Other Expenses"}]
Is it possible without a loop?

Here's a rewrite of your variables with a few changes:
Underscore variable names
valid_account_types is now a hash instead of an array containing a hash.
Some typos corrected so that the members of types match keys of valid_account_types.
valid_account_types = {
'Asset' => 'Asset',
'Liability' => 'Liability',
'Equity' => 'Equity',
'Income' => 'Income',
'CostOfSales' => 'Cost of Sales',
'Expense' => 'Expenses',
'OtherIncome' => 'Other Income',
'OtherExpenses' => 'Other Expenses',
}
types = [
'Asset',
'OtherIncome',
'OtherExpenses',
]
Given that setup, if you are using Rails, you can get the result you want using Hash#slice, like this:
> valid_account_types.slice(*types)
=> {"Asset"=>"Asset", "OtherIncome"=>"Other Income", "OtherExpenses"=>"Other Expenses"}
Note that Hash#slice does not exist in Ruby itself. If you want to do this in plain-old Ruby, you could check out the implementation in i18n:
class Hash
def slice(*keep_keys)
h = self.class.new
keep_keys.each { |key| h[key] = fetch(key) if has_key?(key) }
h
end
end

Your first array isn't an array, it's actually a hash (notice the surrounding braces, rather than brackets):
validAccountTypes = {
'Asset' => 'Asset',
'Liability' => 'Liability',
'Equity' => 'Equity',
'Income' => 'Income',
'CostOfSales' => 'Cost of Sales',
'Expense' => 'Expenses',
'OtherIncome' => 'Other Income',
'OtherExpense' => 'Other Expenses',
}
You can get the keys on the hash and intersect it with your array:
common_keys = validAccountTypes.keys & types
Then you can select only those keys:
# Hash#slice works in Rails:
validAccountTypes.slice(*common_keys)
# Otherwise use Hash#select:
validAccountTypes.select { |key, value| common_keys.include?(key) }
Note that in your example, your hash keys include 'OtherIncome' and 'OtherExpense', but your types array includes 'Other Income' and 'Other Expenses'. Your array values should match the keys from the hash.

Related

TypeError no implicit conversion of Symbol into Integer

Hash
data = {
:recordset => {
:row => {
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"}
]
}
},
:#xmlns => "http://localhost/test"
}
Code Used
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
I cannot get the following output:
[{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"}
Error message:
TypeError no implicit conversion of Symbol into Integer
It works correctly in case of multi records
data = {
:recordset => {
:row => [{
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"},
{:name => "Phone1", :value => "1234567890"}
]
}, {
:property => [
{:name => "Code", :value => "C0002"},
{:name => "Customer", :value => "VERDE VINCENT"},
{:name => "Phone1", :value => "9876543210"},
{:name => "Phone2", :value => "2468101214"}
]
}]
},
:#xmlns => "http://localhost/test"
}
Code used
data.keys
#=> [:recordset, :#xmlns]
data[:recordset][:row].count
#=> 2 # There are 2 set of attribute-value pairs
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
#=> [
# {"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT", "Phone1"=>"9876543210", "Phone2"=>"2468101214"}
# ]
In the first case data[:recordset][:row] is not an Array, it's a Hash, so when you iterate it, the hash variable becomes the array:
[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]
In the second case, it's an Array, not a Hash, so when you iterate it, it becomes the hash:
{:property=>[{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}, {:name=>"Phone1", :value=>"1234567890"}]}
You're always assuming it's the second format. You could force it into an array, and then flatten by 1 level to treat both instances the same:
result = [data[:recordset][:row]].flatten(1).each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO"}] # result from example 1
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT",
# "Phone1"=>"9876543210", "Phone2"=>"2468101214"}] # result from example 2
It's tempting to try and use Kernal#Array() instead of [].flatten(1), but you have to remember that Hash implements to_a to return a nested array of keys and values, so Kernal#Array() doesn't work like you'd want it to:
Array(data[:recordset][:row]) # using the first example data
# => [[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]]
You can create an array if it's not an array to normalize the input before processing it.
info = data[:recordset][:row]
info = [info] unless info.is_an? Array
result = info.each_with_object([]) do ....

Ruby sort by hash and value

I have data like this:
hash_data = [
{:key1 => 'value4', :sortby => 4},
{:key1 => 'valuesds6', :sortby => 6},
{:key1 => 'valuedsd', :sortby => 1},
{:key1 => 'value2_data_is_here', :sortby => 2}
]
I want to sort it to this by the key sortby
hash_data = [
{:key1 => 'valuedsd', :sortby => 1},
{:key1 => 'value2_data_is_here', :sortby => 2},
{:key1 => 'value4', :sortby => 4},
{:key1 => 'valuesds6', :sortby => 6}
]
I have tried using bubble sort, but is there any inbuilt function in a Hash class for such purposes?
Enumerable#sort_by to the rescue:
hash_data.sort_by { |hash| hash[:sortby] }
#=> [{:key1=>"valuedsd", :sortby=>1}, {:key1=>"value2_data_is_here", :sortby=>2}, {:key1=>"value4", :sortby=>4}, {:key1=>"valuesds6", :sortby=>6}]
If you don't care about initial object, I would suggest using Array#sort_by! to modify inplace - it is more resource-efficient:
hash_data.sort_by! { |hash| hash[:sortby] }
If you have different types of data as values to sortby key, you should first unify the data type and only then perform sorting.
To have array sorted in descending order, use Enumerable#reverse (or reverse!):
hash_data.sort_by {|hash| hash[:sortby] }.reverse
#=> [{:key1=>"valuesds6", :sortby=>6}, {:key1=>"value4", :sortby=>4}, {:key1=>"value2_data_is_here", :sortby=>2}, {:key1=>"valuedsd", :sortby=>1}]
Another option for sorting in descending order is the following - note minus sign (credits to #sagarpandya82):
hash_data.sort_by {|hash| -hash[:sortby] }

create a specific hash (dynamic)

How I can create a hash like this in a cycle ?
User.items.each do |m|
......
Result:
test = [{:name => 'Unit 1', :price => "10.00"},
{:name => 'Unit 2', :price => "12.00"},
{:name => 'Unit 3', :price => "14.00"}]]
You can use map to return hashes that you build.
Assuming your Item resource responds to name and price, it would look like
test = User.items.map do |m|
{
name: m.name,
price: m.price
}
end
You also can do like this:
Item.connection.select_all("select name, price from items where user_id = xxxxx;")
you will get an array containing hash, like this:
[{"name"=>"xxx", "price"=> xxx},{}......]

Accessing a hashmap as an attribute of an object

I have an instance of an object that is created like this:
Example.create(:attrib0 => {
:attrib1 => value,
:attrib2 => [
{:attrib3 => value},
{:attrib4 => value}
]
})
How can I access :attrib4?
You should use serialize in your model, then you'll be able to return the hash correctly:
class SomeModel < ActiveRecord::Base
serialize :attrib0
end
Then the following should return the hash
hash = #model.attrib0
# => {:attrib1 => value, :attrib2 => [{:attrib3 => value}, {:attrib4 => value}]
# now to access attrib4 you need to get the attrib2 array,
# then grab attrib4 by its index:
hash[:attrib2][1]
# => {:attrib4 => value}
# or to get the value:
hash[:attrib2][1][:attrib4]
# => value
The above however can get quite complex and ugly, which is why I recommended creating another model for these attributes instead.
I think you should use nested attributes. Here's how it can be:
class Example
has_one :attrib0
accepts_nested_attributes_for :attrib0
end
params = { :attrib0 => { :attrib1 => value1,
:attrib2 => [ {:attrib3 => value3}, {:attrib4 => value4} ] }
}
example = Example.create(params[:attrib0])
example.attrib0.attrib1 #=> value1
example.attrib0.attrib2 #=> [ {:attrib3 => value3}, {:attrib4 => value4} ]
Using Ruby technique only:
h = {:attrib0 => {
:attrib1 => :value1,
:attrib2 => [
{:attrib3 => :value2},
{:attrib4 => :value3}
]
}}
p h[:attrib0][:attrib2].last[:attrib4] #=> :value3

Accepting either a hash or an array of hashes as arguments to a Ruby method

I have the method:
def self.store(params)
params.each { }
end
It works perfectly, if I pass an Array of Hashes:
params = [ { key: 'value' }, { key: 'value' } ]
However, I might want to pass only a single Hash, instead of an Array of Hashes:
params = { key: 'value' }
What would be the cleanest Ruby way to convert a Hash into an Array of Hashes?
The Array() method a kind of ensures, that an array is always returned, but when the Hash is passed, it is converted into an Array itself.
Array({ key: 'value' }) => [[:key, 'value']]
What I need:
{ key: 'value' } => [ { key: 'value' } ]
Is there any nice way to implement this, or do I have to do a manual type checking with is_a?(Array) ?
For me, the best solution is to change the method to:
def self.store(*hashes)
params = hashes.flatten
puts params.inspect
end
If you pass a single hash, it will be an array
If you pass an array of hashes, it remains the same
If you pases N hashes, it compacts all parameters into a one dimensional array.
You can pass whatever you want.
self.store({:key => 'value'}) # => [{:key => 'value'}]
self.store({:key => 'value'}, {:foo => 'bar'}) # => [{:key => 'value'}, {:foo => 'bar'}]
self.store([{:key => 'value'}, {:foo => 'bar'}]) # => [{:key => 'value'}, {:foo => 'bar'}]
Try this
def self.store(params)
params = [params].flatten
...
end
I would do it like this:
def self.store(params)
(params.is_a?(Array) ? params : [params]).each {|single_hash| }
end

Resources