Looping through an array, displaying elements that match a criteria - arrays

I have this big array that I need to break down and only display specific elements within it that match a criteria.
My array looks like this.
[
{
:id => 9789,
:name => "amazing location",
:priority => 1,
:address_id => 12697,
:disabled => false
},
{
:id => 9790,
:name => "better location",
:priority => 1,
:address_id => 12698,
:disabled => false
},
{
:id => 9791,
:name => "ok location",
:priority => 1,
:address_id => 12699,
:disabled => true
}
]
What I need is to only display the elements within this array that have disabled set to true.
However when I try this, I get the error stating no implicit conversion of Symbol into Integer
array.map do |settings, value|
p hash[:disabled][:true]
end
I'm wondering if there is another way, or if there is a way to do this. If anyone could take a look, I would greatly appreciate it.

By providing two arguments to #map on an array, you're actually getting the first hash and then nil. When in reality you just want to loop for each and select those where disabled is true. You can do that instead with Array#select which will filter all elements of the array where the block returns a truthy value:
print array.select { |hash| hash[:disabled] }
=> [{:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}]

You can try this with a short each or select.
a.each { |k,_v| puts k if k[:disabled] == true }
=> {:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}
This iterates over each element (hash) inside the array you have and checks if the value of the key disabled on each value is true, and puts the key, just for example, you can set it as you want to do.
Or shorter:
puts a.select { |k,_v| k[:disabled] }
=> {:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}

Your error shows up when you are treating an array or string as a Hash.
In PHP, array keys can be either numbers or strings, whereas in Ruby associative arrays are a separate data type, called a hash.
Here’s a cheatsheet for various foreach variants, translated into idiomatic Ruby:
Looping over a numeric array (PHP) :
<?php
$items = array( 'orange', 'pear', 'banana' );
# without indexes
foreach ( $items as $item ) {
echo $item;
}
# with indexes
foreach ( $items as $i => $item ) {
echo $i, $item;
}
Looping over an array (Ruby) :
items = ['orange', 'pear', 'banana']
# without indexes
items.each do |item|
puts item
end
# with indexes
items.each_with_index do |item, i|
puts i, item
end
Looping over an associative array (PHP) :
<?php
$continents = array(
'africa' => 'Africa',
'europe' => 'Europe',
'north-america' => 'North America'
);
# without keys
foreach ( $continents as $continent ) {
echo $continent;
}
# with keys
foreach ( $continents as $slug => $title ) {
echo $slug, $title;
}
Looping over a hash (Ruby):
continents = {
'africa' => 'Africa',
'europe' => 'Europe',
'north-america' => 'North America'
}
# without keys
continents.each_value do |continent|
puts continent
end
# with keys
continents.each do |slug, title|
puts slug, title
end
In Ruby 1.9 hashes were improved so that they preserved their internal order. In Ruby 1.8, the order in which you inserted items into a hash would have no correlation to the order in which they were stored, and when you iterated over a hash, the results could appear totally random. Now hashes preserve the order of insertion, which is clearly useful when you are using them for keyword arguments in method definitions. (thanks steenslag for correcting me on this)

Related

Convert array to multidimensional hash

My task is convert array, containing hash with x keys to x-1 dimensional hash.
Example:
use Data::Dumper;
my $arr = [
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm1',
'value' => 1,
},
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm2',
'value' => 2,
},
];
my $res;
for my $i (#$arr) {
$res->{ $i->{source} } = {};
$res->{ $i->{source} }{ $i->{group} } = {};
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
my $res_expected = {
'source1' => {
'group1' => {
'prm1' => 1, # wasn't added, why ?
'prm2' => 2
}
}
};
However it doesn't work as expected, 'prm1' => 1 wasn't added. What is wrong and how to solve this task ?
The problem is that you are assigning to the source even if something was there, and you lose it. Just do a ||= instead of = and you'll be fine.
Or even easier, just use the fact that Perl autovivifies and leave that out.
my $res;
for my $i (#$arr) {
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
The first 2 lines in the for loop are what is causing your problem. They assign a new hash reference each iteration of the loop (and erase what was entered in the previous iteration). In perl, there is no need to set a reference as you did. Just eliminate the first 2 lines and your data structure will be as you wish.
The method you chose only shows 'prmt' => 2 because that was the last item entered.

How do I append a new hash to an array of hashes?

If I wanted to add a new hash to all the arrays in the mother_hash using a loop, what would be the syntax?
My hash:
my %mother_hash = (
'daughter_hash1' => [
{
'e' => '-4.3',
'seq' => 'AGGCACC',
'end' => '97',
'start' => '81'
}
],
'daughter_hash2' => [
{
'e' => '-4.4',
'seq' => 'CAGT',
'end' => '17',
'start' => '6'
},
{
'e' => '-4.1',
'seq' => 'GTT',
'end' => '51',
'start' => '26'
},
{
'e' => '-4.1',
'seq' => 'TTG',
'end' => '53',
'start' => '28'
}
],
#...
);
If you have a hash of arrays of hashes and want to add a new hash to
the end of each of the arrays, you can do:
push #{ $_ }, \%new_hash for (values %mother_hash);
This loop iterates over the values of %mother_hash (which are array refs in this case) and setting $_ for each iteration. Then in each iteration, we push the reference to the new hash %new_hash to the end of that array.
First I would point out the daughter hashes aren't hashes but arrays of anonymous hashes. To add another daughter hash:
$mother_hash{daughter_hash3} = [ { %daughter_hash3 } ];
This creates an anonymous array that contains an anonymous hash with the contents of %daughter_hash3.
For a loop:
$mother_hash{$daughter_hash_key} = [ { %daughter_hash } ];
where $daughter_hash_key is a string contain the key for the %mother_hash and %daughter_hash is the hash to add.
To add another hash to a daughter array with key $daughter_hash_key:
push #{ $mother_hash{$daughter_hash_key} }, { %daughter_hash };
I know ti's complicated but I suggest you use Data::Dumper to dump the contents of %mother_hash each time thru the loop to see if it grows correctly.
use Data::Dumper;
print Dumper \%mother_hash;
See perldoc Data::Dumper for details..
Data::Dumper is a standard module that comes with Perl. For a list of standard modules, see perldoc perlmodlib.
mother_hash is a hash of arrays of hashes.
To add another top-level array of hashes.
%mother_hash{$key} = [ { stuff }, { stuff } ];
To add another entry to an existing array
push #{%mother_hash{'key'}} { stuff };
To add another entry to the hash in the embedded array
%{#{%mother_hash{'top_key'}}[3]}{'new_inner_key'} = value;
When confused and attempting to match up the "types" of hash / array / scalar containing a hash reference / array reference, you can use the following technique
use Data::Dumper;
$Data::Dumper::Terse = 1;
printf("mother_hash reference = %s\n", Dumper(\%mother_hash));
printf("mother_hash of key 'top_key' = %s\n", Dumper(%mother_hash{top_key}));
and so on to find your way through a large data structure and validate that you are narrowing down to the region you want to access or alter.

Iterate through nested hash to create an array while adding items to it

I have a nested hash and I would like to rearrange the key/val pairs. The example below shows a hash of styles that points to hash of languages, which then points to a hash of the type of language it is. I want to reformat it to look like the new_hash example. I understand to structure it by iterating through the hash through different levels and creating the hash like that, however, the part I'm concerned/confused about is creating the array that :style points to and then pushing the correct style to it.
I assumed the code snippet would work as I expect it to. My new_hash will have a key of :language which points to another hash. This hash has a key of :style that points to an array in which I will store all the styles associated with each respective language. The :javascript hash should have two styles in its array since it exists twice in the original hash, however, when running this code snippet, the array is not adding both styles. It seems that during one iteration when assigning the hash, :javascript is assigned the style of :oo but in another iteration, it gets replaced with :functional. I'm not sure of the syntax to initialize the array and add multiple items to it while iterating through the hash.
hash = {
:oo => {
:ruby => {:type => "Interpreted"},
:javascript => {:type => "Interpreted"},
},
:functional => {
:scala => {:type => "Compiled"},
:javascript => {:type => "Interpreted"}
}
}
new_hash = {
:ruby => {
:type => "Interpreted", :style => [:oo]
},
:javascript => {
:type => "Interpreted", :style => [:oo, :functional]
},
:scala => {
:type => "Compiled", :style => [:functional]
}
}
hash.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new_hash[language] = {:style => [style]}
end
end
end
You could use the forms of Hash#update (aka merge!) and Hash#merge that employ a hash to determine the values of keys that are present in both hashes being merged. See the docs for details.
hash.each_with_object({}) do |(style,language_to_type_hash),h|
language_to_type_hash.each do |language,type_hash|
h.update(language=> { type: type_hash[:type], style: [style] }) do |_,o,_|
o.merge(style: [style]) { |_,ostyle_arr,nstyle_arr| ostyle_arr + nstyle_arr }
end
end
end
#=> {:ruby =>{:type=>"Interpreted", :style=>[:oo]},
# :javascript=>{:type=>"Interpreted", :style=>[:oo, :functional]},
# :scala =>{:type=>"Compiled", :style=>[:functional]}}
Hash::new allows you to specify a default value for a non existent key so in your case the default value would be {type: nil, style: []}
This functionality will allow you to loop only once and implement as follows
programming_languages = {
:oo => {
:ruby => {:type => "Interpreted"},
:javascript => {:type => "Interpreted"},
},
:functional => {
:scala => {:type => "Compiled"},
:javascript => {:type => "Interpreted"}
}
}
programming_languages.each_with_object(Hash.new {|h,k| h[k] = {type: nil, style: []}}) do |(style,languages),obj|
languages.each do |language,type_hash|
obj[language][:style] << style
obj[language][:type] = type_hash[:type]
end
end
Output:
#=> {:ruby=>{:type=>"Interpreted", :style=>[:oo]},
:javascript=>{:type=>"Interpreted", :style=>[:oo, :functional]},
:scala=>{:type=>"Compiled", :style=>[:functional]}}
Realized that this can be solved iterating the hash twice. Once to initialize the array, and the second time to then add the necessary items to it. Though not sure if this can done only iterating the hash once.
new = {}
languages.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new[language] = {:type => nil , :style => []}
end
end
end
languages.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new[language][:type] = value
new[language][:style] << style
end
end
end
new
Once we give the hashes better names, it becomes a bit easier to work it out. I've also made use of sets so we don't have to worry about duplicates.
require 'set'
# Our new hash of language info. _new to differentiate between
# the hash of languages under the hash of styles.
languages_new = {}
# For each style...
styles.each do |style, languages|
# For each language in that style...
languages.each do |language, info|
# Add a new hash for that language if there isn't one already
languages_new[language] ||= {}
# For each bit of info about that language...
info.each do |key, val|
# Add a new set for that info if there isn't one already
# The `var = hash[key] ||= new_var` pattern allows
# conditional initialization while also using either the
# new or existing set.
set = languages_new[language][key] ||= Set.new
# Add the info to it
set.add(val)
end
# Handle the special case of style.
set = languages_new[language][:style] ||= Set.new
set.add(style)
end
end
Note that rather than hard coding the initialization of hashes and sub-hashes, I've done it in each level of looping. This means I don't have to list out all the keys, and it will handle new and unexpected keys.
By using sets for the values I make no assumptions about how many values a bit of language information can have.

Dereferencing an array reference from a nested Perl hash

I hope I've stated that subject correctly. I have a hash of hashes that I've built from reading a file. The outer hash is groups, then the inner hash is parameters within that group. Each parameter value can either be a scalar or array, and the arrays can start at either zero or one.
I've written a subroutine that returns the value of a parameter. The calling function has to figure out whether the returned value is a scalar or an array. Works fine for scalars. Returns a reference to an array for array values (looks like ARRAY(0x004f00)). Using Data::Dumper spits out data that looks like an array, but I can't figure out how to dereference it in the code. Can someone point to what I'm doing wrong?
%HoH = (
flintstones => {
husband => "fred",
possessions => [ undef, "car", "record player", "rock" ],
pal => "barney",
pets => [ "bird", "dinosaur" ],
},
);
get_value("possessions");
sub get_value {
my $what_i_want = shift;
#groups = keys(%HoH);
foreach my $group ( #groups ) {
foreach my $param ( keys( %{ HoH {group} } ) ) {
if ( $param eq $what_i_want ) {
return $HoH{$group}{$param};
}
}
}
}
The caller assigns the return value to an array, #return, so in the case of a scalar it should put the value in $return[0].
In the case of an array, it should populate the array. When I call Dumper, it prints out scalars in single quotes and arrays in square brackets, as it should. However, when I use scalar(#return) to check the size of the array, it returns 1.
I've tried dereferencing the return statement using square brackets at the end to see if I could even get a scalar returned, but no luck.
You don't show the subroutine being called in context, but a quick fix would be to put this after the call
#return = #{ $return[0] } if ref $return[0]
Update
You're missing the point of hashes. You can access the correct element of the parameter hash by using $what_i_want as a hash key
I suggest you change your subroutine code to look like this
for my $group ( keys %HoH ) {
my $ret = $HoH{$group}{$what_i_want};
return unless defined $ret;
return ref $ret ? #$ret : $ret;
}
That way it will never return a reference
Update 2
Here's your complete program modified as I suggested
use strict;
use warnings 'all';
my %HoH = (
flintstones => {
husband => "fred",
possessions => [ undef, "car", "record player", "rock" ],
pal => "barney",
pets => [ "bird", "dinosaur" ],
},
);
my #return = get_value('possessions');
use Data::Dump;
dd \#return;
sub get_value {
my ($wanted) = #_;
for my $group ( keys %HoH ) {
my $ret = $HoH{$group}{$wanted};
if ( defined $ret ) {
return ref $ret ? #$ret : $ret;
}
}
return;
}
output
[undef, "car", "record player", "rock"]

references in perl: hash of array to another array

I have a problem with referencing a hash in an array to another array.
I have an array #result which looks like this:
#result = (
{ "type" => "variable",
"s" => "NGDP",
"variable" => "NGDP" },
{"type" => "subject",
"s" => "USA",
"subject" => "USA",
"variable" => "NGDP" },
{ "type" => "colon",
"s" => ",",
"colon" => "," },
{ "type" => "subject",
"s" => "JPN",
"subject" => "JPN",
"variable" => "NGDP" },
{ "type" => "operator",
"s" => "+",
"operator => "+" },
{"type" => "subject",
"s" => "CHN",
"subject" => "CHN",
"variable" => "NGDP" },
);
I want to divide this array into colons and push elements of the #result array to another array, so i wrote the script:
for ($i = 0; $i <= $#result; $i++) {
if (defined $result[$i]{subject} or $result[$i]{operator} and not defined $result[$i]{colon}) {
push #part_col, \%{$result[$i]};
}
elsif ($i == $#result) {
push #part_col_all, \#part_col;
}
elsif (defined $result[$i]{colon}) {
push #part_col_all, \#part_col;
my #part_col;
}
}
So what I need is that if I print out $part_col_all[0][0]{subject} the result will be "USA",
and for $part_col_all[1][0]{subject} will be "JPN",
and for $part_col_all[1][1]{operator} will be "+" etc.
My result for $part_col_all[0][0]{subject} is "USA"
and for $part_col_all[0][1]{subject} is "JPN" which should be in $part_col_all[1][0]{subject}.
The result for $part_col_all[0][3]{subject} is "CHN", while it should be in $part_col_all[1][2]{subject}.
I'm making an application which is creating graphs from economical data based on a certain economical input. The #result array is my preprocessed input where I know to which country which variable belongs. If I get an input like GDP USA CAN, JPN+CHN I need to split this input to GDP USA CAN and JPN+CHN. That's why I made a condition, if colon is found, push everything in #part_col to the first element of #part_col_all, and then if it's on the end of the input, push JPN+CHN to the second element of #push_col_all.
So #part_col_all should looks like this:
#part_col_all = (
(
{"type" => "subject",
"s" => "USA",
"subject" => "USA",
"variable" => "NGDP" },
{"type" => "subject",
"s" => "CAN",
"subject" => "CAN",
"variable" => "NGDP" },
),
(
{ "type" => "subject",
"s" => "JPN",
"subject" => "JPN",
"variable" => "NGDP" },
{ "type" => "operator",
"s" => "+",
"operator" => "+" },
{"type" => "subject",
"s" => "CHN",
"subject" => "CHN",
"variable" => "NGDP" },
)
);
I dont know what I'm doing wrong. Sorry if there are any basic mistakes, im a beginner. Thanks a lot.
First, you're missing a quote:
{ "type" => "operator",
"s" => "+",
"operator" => "+" },
^ missing
As for printing, you can do the following:
foreach my $part (#part_col){
print $part->{operator}."\n";
}
Or do whatever you want in the print cycle with the values
You should read the Perl Reference Tutorial to help you.
There's no sin in dereferencing to simplify your code:
my #part_col;
my #part_col_all;
for $i ( 0..$#array ) {
my %hash = ${ $result[$i] }; # Make it easy on yourself. Dereference
if ( defined $hash{subject} or defined $hash{operator} and not defined $hash{colon} ) {
push #part_col, \%hash; # or push, #par_col, $result[$i]
}
}
Notice I changed the for from the three part setup you had to a cleaner and easier to understand way of stating it.
Looking closer at your data structure, I notice that $hash{type} will tell you whether or not $hash{operator}, $hash{subject}, or $hash{colon} is defined. Let's just use $hash{type} and simplify that if:
my #part_col;
my #part_col_all;
for my $i ( 0..$#array ) {
my %hash = ${ $result[$i] }; # Make it easy on yourself. Dereference
if ( $hash{type} eq "subject" or $hash{type} eq "operator" ) {
push #part_col, \%hash; # or push, #par_col, $result[$i]
}
}
In fact, since #array is just an array, I'll treat it like one. I'll use a simple for structure to go through each element of my array. Each element is a hash_reference, so:
for my $hash_ref ( #array ) {
my %hash = %{ %hash_ref };
if ( $hash{type} eq "subject" or $hash{type} eq "operator" ) {
push #part_col, \%hash;
}
}
And further simplification, I can dereference and talk about a particular element of my hash all at once by using the -> syntax:
for my $hash_ref ( #array ) {
if ( $hash_ref->{type} eq "subject" or $hash_ref->{type} eq "operator" ) {
push #part_col, $hash_ref;
}
}
I'm trying to understand the rest of your code:
elsif ($i == $#result) {
push #part_col_all, \#part_col;
}
elsif (defined $hash_ref->{colon}) {
push #part_col_all, \#part_col;
my #part_col;
}
}
These pushes of #part_col onto #part_col_all confuse me. Exactly what are you trying to store in #part_col_all? Remember that \#part_col is the location in memory where you're storing #part_col. You're pushing that same memory location over and over onto that hash, so you're storing the same reference over and over again. Is that really what you want? I doubt it.
You need to do is to decide exactly what your data structure really represents. A data structure should have a solid definition. What does the data structure #part_col_all represent? What does the data structure $part_col_all[$i] represent? What does the data structure $part_col_all[$i]->[$j] represent? Without knowing this, it's very hard to answer the rest of your question.
Are you storing elements where the type is colon in one array and everything else in another array? Or are you storing everything in one array, and in another array, storing everything that's not a type colon?
Once I understand this, I can answer the rest of your question.
Addendum
Thank you for your reply, I will try that way and write my results. It is realy helpful. I updated my question with more information about data structure of #part_col_all. I hope that you understand what I'm trying to explain, if not I'll try it again.
If I understand what you're doing, someone enters in NGDP USA , JPN+CNA and that means you're comparing the NGDP between the United States vs. Japan and China combined.
It seems to me that you would want three separate variables:
$parameter - What you are measuring. (GDP, etc.)
#countries_set_1 - The first set of countries
#countries_set_2 - The second set of countries which you're comparing against the first set.
And, what you call the colon (which we would call a comma in the U.S.) as a separator between the first set of countries vs. the second set. Then, you'd simply go through a loop. It could be that the two arrays are merely two elements of the same array, and the sets of countries are array references. I imagine something like this:
#input = qw(GDP USA, JPN CHN); # Compare the GDP of the USA with Japan and China together
my $parameter = shift #input; # Remove what you're measuring
my #country_sets; # An array of arrays
my $set = 0 # Which set you're on
for my $value ( #input ) {
if ( $value eq "," ) {
$set += 1; # Next Set
next;
}
push #{ $country_sets[$set] }, $input;
}
This would create a data structure like this:
#country_sets = (
(
USA,
),
(
JPN,
CHN,
),
)
No need for the complex #results since you're only going to have a single operation (GDP, etc.) for all involved.
However, I think I see what you want. We'll go with an array of arrays. Here's what I had before:
for my $hash_ref ( #array ) {
if ( $hash_ref->{type} eq "subject" or $hash_ref->{type} eq "operator" ) {
push #part_col, $hash_ref;
}
}
We'll combine that and the code I offered right above which splits the countries into two sets:
my #country_sets; # An array of arrays
my $set = 0 # Which set you're on
for my $country_ref ( #array ) {
next if $country_ref->{type} eq "variable"; # We don't want variables
if ( $country_ref{type} eq "colon" ) { # Switch to the other country set
set += 1;
next;
}
push #{ $country_sets[$set] }, $country_ref;
}
The first few entries will go into $country_sets[0] which will be an array reference. After the colon (which won't be input into the set), the second set of countries will go into $country_sets[1] which will be an other array_ref to a reference of hashes:
#country_sets - Contains the input information into two sets
#country_sets[$x] - A particular set of countries (and possibly operator)
#country_sets[$x]->[$y] - A Particular country or operator
#country_sets[$x]->[$y]->{$key} - A particular value from a particular country
Where $x goes from 0 to 1. This will give you something like this:
$country_sets[0] = (
{
"type" => "subject",
"s" => "USA",
"subject" => "USA",
"variable" => "NGDP",
},
)
$country_sets[1] = (
{
"type" => "subject",
"s" => "JPN",
"subject" => "JPN",
"variable" => "NGDP",
},
{
"type" => "operator",
"s" => "+",
"operator => "+",
},
{
"type" => "subject",
"s" => "CHN",
"subject" => "CHN",
"variable" => "NGDP",
},
);

Resources