I have an array of strings that are hex color codes like so:
["#121427", "#291833", "#4B2E4D", "#5D072F", "#BB2344", "#ED9F90"]
and I want to convert them into a hash with distinct key name where there is a distinct name "color" and then a integer value which increments as it transverses the array adding them like so:
{"color1" => "#121427", "color2" => "#291833", "color3" => "#4B2E4D", "color4" => "#5D072F", "color5" => "#BB2344", "color6" => "#ED9F90"}
The integer value can be 0 based or 1 based, it doesn't matter which ever is cleaner.
I've tried using the map method along with the to_h method, although I cannot figure out how to create an incremental key name as described.
It's not too hard to do this using the each_with_index method which is zero-indexed by default:
Hash[colors.each_with_index.map { |c, i| [ 'color%d' % i, c ] }]
You were close with map, you just needed to expand it into value/index pairs.
Another possible way:
colors.each_with_object({}).with_index(1){|(e, h), i| h["color#{i}"] = e}
It uses:
Enumerable#each_with_object to skip one temporary array and modify the resulting hash directly
Enumerator#with_index to also supply an auto-incrementing integer (it can even start from 1)
If arr is your array, you could do this:
idx = 1.step
# => #<Enumerator: 1:step>
arr.each_with_object({}) { |s,h| h["color#{idx.next}"] = s }
#=> {"color1"=>"#121427", "color2"=>"#291833", "color3"=>"#4B2E4D",
# "color4"=>"#5D072F", "color5"=>"#BB2344", "color6"=>"#ED9F90"}
Related
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.
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.
I have two arrays, the first array contains field name, type and id.
arr1 = [
{
"n" => "cat",
"t" => 0,
"id" => "42WTd5"
},
{
"n" => "dog",
"t" => 0,
"id" => "1tM5T0"
}
]
Second array contains field, id and value.
arr2 = [
{
"42WTd5"=>"meow",
"1tM5T0"=>"woof"
}
]
How can I join them by id to produce the following result.
cat: meow
dog: woof
Any help is appreciated.
I think you want your result to be a Hash, in which case this would do the job:
def match_animals_to_sounds(animal_array, sound_array)
sounds = sound_array.first
animal_array.map { |animal| [animal['n'], sounds[animal['id']]] }.to_h
end
>> match_animals_to_sounds(arr1, arr2)
=> {"cat"=>"meow", "dog"=>"woof"}
Your arr2 is unusual in that it is an Array of a single element. I'm just calling #first on it to pull out the Hash inside. If you expect some version of this Array to have more than one element in the future, you'll need to rethink the first line of this method.
The second line is standard Ruby Array manipulation. The first part maps each animal to a new Array of two-element Arrays containing each animal's name and sound. At the end, #to_h converts this array of two-element arrays to a single Hash, which is much more useful than an array of strings. I don't know what you intended in your question, but this is probably what you want.
If you prefer to work with Symbols, you can change the second line of the method to:
animal_array.map { |animal| [animal['n'].to_sym, sounds[animal['id']].to_sym] }.to_h
In which case you will get:
>> match_animals_to_sounds(arr1, arr2)
=> {:cat=>:meow, :dog=>:woof}
This is a way to do it.
sounds = arr2[0]
results = arr1.map do |animal|
"#{animal["n"]}: #{sounds[animal["id"]]}"
end
puts results
# => cat: meow
# => dog: woof
Seems like the second array should just be a hash instead. There's no point creating an array if there's only one element in it and that number won't change.
pointless one-liner (don't use this)
puts arr1.map { |x| "#{x["n"]}: #{arr2[0][x["id"]]}" }
You can also get the join result by following code
arr1.collect{ |a| {a["n"] => arr2[0][a["id"]]} }
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.
Say you have an array #a = qw/ a b c d/;
and a hash %a = ('a' => 1, 'b' => 1, 'c' => 1, 'd' => 1);
Is there any situation where creating the array version is better than creating the hash (other than when you have to iterate over all the values as in something like
for (#a){
....
in which case you would have to use keys %a if you went with the hash)? Because testing whether a specific value is in a hash is always more efficient than doing so in an array, correct?
Arrays are indexed by numbers.
Hashes are keyed by strings.
All indexes up to the highest index exist in an array.
Hashes are sparsely indexed. (e.g. "a" and "c" can exist without "b".)
There are many emergent properties. Primarily,
Arrays can be used to store ordered lists.
It would be ugly an inefficient to use hashes that way.
It's not possible to delete an element from an array unless it's the highest indexed element.
You can delete from an ordered list implemented using an array, though it is inefficient to remove elements other than the first or last.
It's possible to delete an element from a hash, and it's efficient.
Arrays are ordered lists of values. They can contain duplicate values.
#array = qw(a b c a);
Hashes are a mapping between a key (which must be unique) and a value (which can be duplicated). Hashes are (effectively) unordered, which means that keys come out in apparently random order rather than the order in which they are entered.
%hash = (a => 1, b => 2, c => 3);
Hashes can also be used as sets when only the key matters. Sets are unordered and contain only unique "values" (the hash's keys).
%set = (a => undef, b => undef, c => undef);
Which one to use depends on your data and algorithm. Use an array when order matters (particularly if you can't sort to derive the order) or if duplicate values are possible. Use a set (i.e. use a hash as a set) when values must be unique and don't care about order. Use a hash when uniqueness matters, order doesn't (or is easily sortable), and look-ups are based on arbitrary values rather than integers.
You can combine arrays and hashes (via references) to create arbitrarily complex data structures.
#aoa = ([1, 2, 3], [4, 5, 6]); # array of arrays ("2D" array)
%hoh = (a => { x => 1 }, b => { x => 2 }); # hash of hashes
#aoh = ({a => 1, b => 2}, {a => 3, b => 4}); # array of hashes
%hoa = (a => [1, 2], b => [3, 4]); # hash of arrays
...etc.
This about using numbers as hash keys. It doesn't answer the question directly as it doesn't compare the facilities that arrays provide, but I thought it would be a good place to put the information.
Suppose a hash with ten elements is built using code like this
use strict;
use warnings;
my %hash;
my $n = 1000;
for (1 .. 10) {
$hash{$n} = 1;
$n *= 1000;
}
and then we query it, looking for keys that are powers of ten. Of course the easiest way to multiply an integer by ten is to add a zero, so it is fine to write
my $m = '1';
for (1 .. 100) {
print $m, "\n" if $hash{$m};
$m .= 0;
}
which has the output
1000
1000000
1000000000
1000000000000
1000000000000000
1000000000000000000
We entered ten elements but this shows only six. What has happened? Let's take a look at what's in the hash.
use Data::Dump;
dd \%hash;
and this outputs
{
"1000" => 1,
"1000000" => 1,
"1000000000" => 1,
"1000000000000" => 1,
"1000000000000000" => 1,
"1000000000000000000" => 1,
"1e+021" => 1,
"1e+024" => 1,
"1e+027" => 1,
"1e+030" => 1,
}
so the hash doesn't use the keys that we imagined. It stringifies the numbers in a way that it would be foolish to try to emulate.
For a slightly more practical example, say we had some circles and wanted to collect into sets by area. The obvious thing is to use the area as a hash key, like this program which creates 100,000 circles with random integer diameters up to 18 million.
use strict;
use warnings;
use 5.010;
package Circle;
use Math::Trig 'pi';
sub new {
my $class = shift;
my $self = { radius => shift };
bless $self, $class;
}
sub area {
my $self = shift;
my $radius = $self->{radius};
pi * $radius * $radius;
}
package main;
my %circles;
for (1 .. 100_000) {
my $circle = Circle->new(int rand 18_000_000);
push #{ $circles{$circle->area} }, $circle;
}
Now let's see how many of those hash keys use scientific notation
say scalar grep /e/, keys %circles;
which says (randomly, of course)
861
so there really isn't a tidy way of know what string perl will use if we specify a number as a hash index.
In Perl an #array is an ordered list of values ($v1, $v2, ...) accessed by an integer (both positive and negative),
while a %hash is an unordered list of 'key => value' pairs (k1 => $v1, k2 => $v2, ...) accessed by a string.
There are modules on CPAN that implement ordered hashes, like: Hash::Ordered and Tie::IxHash
You might want to use an array when you have ordered 'items' presumably a great number as well, for
which using a %hash and sorting the keys and/or the values would be inefficient.