Ruby: Group array of array of object - arrays

I would like to group an array of arrays of objects by object attribute.
So, if we have
array =[[object1, object2], [object3], [object4, object5, object6]]
and this object has an attribute say "source". Now assume each object has the following source value:
object1> source=source1
object2> source=source2
object3> source=source2
object4> source=source1
object5> source=source1
object6> source=source3
I want to group the arrays that are inside "array" by source attribute.
so I would like to have:
source1 => [object1, object2], [object4, object5, object6]
source2 => [object1, object2]
source3 => [object4, object5, object6]
for source2, I don't care about array of size 1 so I don't want to add [object3]
Any help?

The key thing, which became apparent after your edit, is that you have already grouped your objects, and you want to bucket your groups into potentially more than one bucket based on the objects in each grouping, so this isn't a pure group_by algorithm. Here is one possible solution:
First, create a hash to hold your groups. This sets an empty array as the default value:
sources = Hash.new {|h,k| h[k] = Array.new }
Then reject the ones you don't care about (see Enumerable#reject).
array.reject{|group| group.size == 1}
Finally, iterate through the groups, collecting their sources and adding the group to the appropriate source in the hash. Full example:
sources = Hash.new {|h,k| h[k] = Array.new }
array.reject{|group| group.size == 1}.each do |group|
group.collect{|group| group.source}.uniq.each do |source|
sources[source] << group
end
end

Related

In Ruby, during iteration of an array, how do I send multiple array elements as values for one specific hash key?

I know how to iterate through an array and I know how to create hash keys and values. I do not know how to create an array for the value and pass it multiple elements. My desired value for hash below is:
{'abc' => [1, 2, 3] , 'def' => [4,5,6,7]}
How would I achieve this hash, while iterating through array a and b below using each?
a = [1,2,3,4,5,6,7]
c = [1,2,3]
b = ['abc', 'def']
hash = {}
From your guidelines given in the comment:
While iterating through array a, if the element of iteration is included in array c, it is passed to the array value within key 'abc'. Otherwise, it is passed to other array value in key 'def'
You can do this:
hash = {}
hash['abc'] = a.select { |x| c.include?(x) }
hash['def'] = a.reject{ |x| c.include?(x) }
See Enumerable#select and Enumerable#reject. Also can take a look at Enumerable#partition which would be another good choice here, where you want to split an array into two arrays based on some condition:
in_a, not_in_a = a.partition { |x| c.include?(x) }
hash = { 'abc' => in_a, 'def' => not_in_a }
You can also do it with regular each if these fancy enumerable methods are bit too much for you:
hash = { 'abc' => [], 'def' => [] }
a.each do |x|
if c.include?(x)
hash['abc'].push(x)
else
hash['def'].push(x)
end
end
Unfortunately this question turned out not to be as interesting as I was hoping. I was hoping that the problem was this:
Knowing the hash key and a value, how can I make sure the key's value is an array and that the given value is appended to that?
For instance, start with h being {}. I have a key name :k and a value 1. I want h[:k], if it doesn't already exist, to be [1]. But if it does already exist, then it's an array and I want to append 1 to that array; for instance, if h[:k] is already [3,2], now it should be [3,2,1].
I can think of various ways to ensure that, but one possibility is this:
(hash[key] ||= []) << value
To see that this works, let's make it a method:
def add_to_hash_array_value(hash:, key:, value:)
(hash[key] ||= []) << value
end
Now I'll just call that a bunch of times:
h = {}
add_to_hash_array_value(hash:h, key:"abc", value:1)
add_to_hash_array_value(hash:h, key:"abc", value:2)
add_to_hash_array_value(hash:h, key:"def", value:4)
add_to_hash_array_value(hash:h, key:"def", value:5)
add_to_hash_array_value(hash:h, key:"abc", value:3)
puts h #=> {"abc"=>[1, 2, 3], "def"=>[4, 5]}
We got the right answer.
This is nice because suppose I have some way of knowing, given a value, what key it should be appended to. I can just repeatedly apply that decision-making process.
However, the trouble with trying to apply that to the original question is that the original question seems not to know exactly how to decide, given a value, what key it should be appended to.

How can I remove an item in a Ruby array based on its content?

I'm making a sort of variable system in Ruby where a user can create a variable. Provided they give a variable name and contents, it adds the name and string given to an array, adding two more items to the array. Here's the code:
$vars = []
def create(name, value)
$vars << name
$vars << value
end
I also want to add a "delete" option where the user types delete("varName") and behind the scenes, it searches for an item with the same value in the $vars array and deletes it. Is there any way to do this? Also, is there any way to get the number of the item (e.g. items in the array are ["a", "b", "c", "d"] and the user wants to delete "d",and then the program returns 3, since that's the location of "d".
A Hash is used to store pairs of items. It is faster, and guarantees names will be unique.
$vars = {}
def create(name, value)
$vars[name] = value
end
Then deleting is trivial.
$vars.delete(name)
As is finding the value.
value = $vars[name]
Hashes remember the order in which keys were added. $vars.keys will return an Array of the names in the order they were added. Then you can use find_index to get where it appears in the list of keys.
index = $vars.keys.find_index(name)
Aside from letting the user know the order in which variables are declared, this isn't of much use. Variables don't have an order.
If you wanted to do this with arrays, first we'd fix create. It's storing both the key and the value in the same Array.
create(a, 23)
create(b, 42)
# $var = ['a', 23, 'b', 42]
Instead, store each pair in its own array.
def create(name, value)
$vars << [name, value]
end
create(a, 23)
create(b, 42)
# $var = [['a', 23], ['b', 42]]
Then to delete, search just the first elements of each pair using index. Then delete that index.
def delete(name)
idx = $vars.map(&:first).index(name)
$vars.delete_at(idx) if idx
return idx
end
Finding the value of a name would work similarly. Find the index of the matching name, look up that pair, return its second element (the value).
def find(name)
idx = $vars.map(&:first).index(name)
pair = $vars[idx]
return pair[1]
end
But don't do this, use a Hash.
$vars = ["name", "value", "item"]
index = $vars.index("name")
$vars.delete_at(index)
Sources:
Duplicate?
Editor
Documentation
Although if this is rails, is it really a good idea to use a GLOBAL variable?

Array of hashes whose key is an array containing ranges

I need to map a number of IDs and ranges of IDs to single values. I would like to store the whole structure in one object.
Is the following a sensible way to do this? What is the best way to search the keys by a single integer value to return the corresponding value?
large array = [
{[3110..3220, 10200, 43680] => 'A0300'},
{[5200, 7100..8990, 9100..9900] => 'B0400'},
{[17110..18121, 20160, 2210..22290] => 'C0600'}
]
Reasonable format to store the data would be a plain old simple hash:
input = { [3110..3220, 10200, 43680] => 'A0300',
[5200, 7100..8990, 9100..9900] => 'B0400',
[17110..18121, 20160, 2210..22290] => 'C0600' }
To lookup the element one might use case-equal aka triple-equal. Luckily it’s implemented for integers as well:
value = 7300
result = input.detect { |k, _| k.any? { |r| r === value } }
result ? result.last : nil
#⇒ "B0400"
No. There is no need to separate each key-value pair into a different hash as you did. Furthermore, you are using the hash in a wrong way. A hash key-value need not be one-to-one. It is better to have something like this:
{
3110..3220 => 'A0300',
10200 => 'A0300',
43680 => 'A0300',
5200 => 'B0400',
7100..8990 => 'B0400',
9100..9900 => 'B0400',
17110..18121 => 'C0600',
20160 => 'C0600',
2210..22290 => 'C0600',
}
But my guess is that you are actually asking a X-Y question, and my answer above is still not the best solution for you.
If the values were hardwired, one could use a case statement.
def look_it_up(n)
case n
when 7100..8990, 9100..9900, 5200
'B0400'
when 17110..18121, 22100..22290, 20160
'C0600'
when 3110..3220, 10200, 43680
'A0300'
else
nil
end
end
look_it_up 10200 #=> "A0300"
look_it_up 9135 #=> "B0400"
look_it_up 22100 #=> "C0600"
look_it_up 3079 #=> nil
Assuming n is somewhat uniformly distributed, I have, for reasons of efficiency, ordered the when clauses by decreasing numbers of numbers included, and within each when clause, I put the ranges first and ordered those ranges by decreasing size.
If the values are not necessarily hardwired, one could use find/detect, as #mudasobwa has done.
If many lookups were to be performed, I would suggest constructing a hash whose keys are integers.
def init_arr(*args)
args.each do |*a, value|
a.each do |e|
if e.is_a?(Range)
e.each { |n| #h[n] = value }
else
#h[e] = value
end
end
end
end
Then for any n, #h[n] gives its value. For example,
#h = {}
init_arr [1..4, 8..10, 11, "cat"]
#h #=> {1=>"cat", 2=>"cat", 3=>"cat", 4=>"cat", 8=>"cat", 9=>"cat",
# 10=>"cat", 11=>"cat"}
init_arr [13..14, 7, "dog"]
#h #=> {1=>"cat", 2=>"cat", 3=>"cat", 4=>"cat", 8=>"cat", 9=>"cat",
# 10=>"cat", 11=>"cat", 13=>"dog", 14=>"dog", 7=>"dog"}
Then (say),
#h[13]
#=> "dog"
#h[11]
#=> "cat"
#h[12]
#=> nil
Naturally, this assumes #h.size is manageable.
One could alternatively construct a look-up array, say #arr, which should be faster than using a hash (though it might consume much more memory than would a hash). #arr[n] would return the value for n or nil if there is no corresponding value. If some values were negative, or if the smallest n were quite large, one would instead write #arr[n+#offset], where #offset is the obvious value.
It is pointless to use arrays and/or ranges as hash key, when you want to look up the data given a single value. The advantage of a hash is that, given a key, you can find a value quickly. In your case, you would have to traverse the hash sequentially to find the entry. In this case, it would make more sense to use an arrays of pairs, the first component describing the range and the second component describing the ID.
If you want to use a hash, you should make a corresponding entry for each possible integer value.
If you know that the integer values can not be higher than a certain size, you could create a sparse array, where the integer value is the index position, and the ID is the content of the array cell. This is fastest, but of course takes up some space.

Create array with key from dict with sorted by dict values

I have an dictionary that I want to iterate trough in a tableview. One way of doing this is to create an array with all the keys and just fetch the key at the given index. But for this array I want the keys to be ordered alphabetically by the values in the dictionary. How do I do this?
static let dict = [
"0P0000KBNA": "Länsförsäkringar Europa Indexnära",
"0P0000YVZ3": "Länsförsäkringar Global Indexnära",
"0P0000J1JM": "Länsförsäkringar Sverige Indexnära",
"0P00013668": "Länsförsäkringar Tillväxtmarknad Indexnära A",
"0P0000K9E7": "Länsförsäkringar USA Indexnära",
"0P00005U1J": "Avanza Zero"
]
static let sortedKeys = Array(dict.keys) // How?
You just need to sort your dictionary by value and map the key
let sortedKeys = dict.sorted{$0.value < $1.value}.map{$0.key} // ["0P00005U1J", "0P0000KBNA", "0P0000YVZ3", "0P0000J1JM", "0P00013668", "0P0000K9E7"]
As a minor variation of #LeoDabus answer, you could access the keys property of your dictionary and sort this array according to the corresponding value for each key
let sortedKeys = dict.keys.sorted { dict[$0]! < dict[$1]! }
print(sortedKeys)
/* ["0P00005U1J",
"0P0000KBNA",
"0P0000YVZ3",
"0P0000J1JM",
"0P00013668",
"0P0000K9E7"] */
Note also that the forced unwrapping operator ! is generally to be avoided, but in this particular case, we know that both shorthand arguments $0 and $1 in the predicate closure supplied to sorted(...) are guaranteed to be valid keys in the dictionary (since your dictionary is immutable: so no asynch risks here).
First, make a new array of strings called keysArray.
Second, loop over your array of dictionaries and add all the keys to the newly created array.
Now keysArray should be an array of the keys.
Third, sort sortedKeysArray as follow
var sortedArray = sortedKeysArray.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending}
Now we have an array of the sorted Keys.
After this if you need to have an array of the dictionaries, create a new array called sortedDictArray. Then loop over sortedKeysArray we made previously and get the dictionary by key and add it to sortedDictArray. Viola!
Hope this helps you!
Ordered Dictionary
I've used the open source OrderedDictionary created by Lukas Kubanek to solve problems like this.
For the usage of this library you can refer to the example playground.

Add key value pair to Array of Hashes when unique Id's match

I have two arrays of hashes
sent_array = [{:sellersku=>"0421077128", :asin=>"B00ND80WKY"},
{:sellersku=>"0320248102", :asin=>"B00WTEF9FG"},
{:sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]
active_array = [{:price=>39.99, :asin1=>"B00ND80WKY"},
{:price=>7.99, :asin1=>"B00YSN9QOG"},
{:price=>10, :asin1=>"B00HXZLB4E"}]
I want to loop through sent_array, and find where the value in :asin is equal to the value in :asin1 in active_array, then copy the key & value of :price to sent_array. Resulting in this:
final_array = [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99},
{:sellersku=>"0320248102", :asin=>"B00WTEF9FG"},
{:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]
I tried this, but I get a TypeError - no implicit conversion of Symbol into Integer (TypeError)
sent_array.each do |x|
x.detect { |key, value|
if value == active_array[:asin1]
x[:price] << active_array[:price]
end
}
end
For reasons of both efficiency and readability, it makes sense to first construct a lookup hash on active_array:
h = active_array.each_with_object({}) { |g,h| h[g[:asin1]] = g[:price] }
#=> {"B00ND80WKY"=>39.99, "B00YSN9QOG"=>7.99, "B00HXZLB4E"=>10}
We now merely step through sent_array, updating the hashes:
sent_array.each { |g| g[:price] = h[g[:asin]] if h.key?(g[:asin]) }
#=> [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99},
# {:sellersku=>"0320248102", :asin=>"B00WTEF9FG"},
# {:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]
Retrieving a key-value pair from a hash (h) is much faster, of course, than searching for a key-value pair in an array of hashes.
This does the trick. Iterate over your sent array and attempt to find a record in your active_array that has that :asin. If you find something, set the price and you are done.
Your code I believe used detect/find incorrectly. What you want out of that method is the hash that matches and then do something with that. You were trying to do everything inside of detect.
sent_array.each do |sent|
item = active_array.find{ |i| i.has_value? sent[:asin] }
sent[:price] = item[:price] if item
end
=> [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99}, {:sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, {:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]
I am assuming second element of both sent_array and active_array has B00WTEF9FG as asin and asin1 respectively. (seeing your final result)
Now:
a = active_array.group_by{|a| a[:asin1]}
b = sent_array.group_by{|a| a[:asin]}
a.map { |k,v|
v[0].merge(b[k][0])
}
# => [{:price=>39.99, :asin1=>"B00ND80WKY", :sellersku=>"0421077128", :asin=>"B00ND80WKY"}, {:price=>7.99, :asin1=>"B00WTEF9FG", :sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, {:price=>10, :asin1=>"B00HXZLB4E", :sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]
Why were you getting TypeError?
You are doing active_array[:asin1]. Remember active_array itself is an Array. Unless you iterate over it, you cannot look for keys.
Another issue with your approach is, you are using Hash#detect
find is implemented in terms of each. And each, when called on a
Hash, returns key-value pairs in form of arrays with 2 elements
each. That's why find returns an array.
source
Same is true for detect. So x.detect { |key, value| .. } is not going to work as you are expecting it to.
Solution without assumption
a.map { |k,v|
b[k] ? v[0].merge(b[k][0]) : v[0]
}.compact
# => [{:price=>39.99, :asin1=>"B00ND80WKY", :sellersku=>"0421077128", :asin=>"B00ND80WKY"}, {:price=>7.99, :asin1=>"B00YSN9QOG"}, {:price=>10, :asin1=>"B00HXZLB4E", :sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]
Here since asin1 => "B00ND80WKY" has no match, it cannot get sellersku from other hash.

Resources