I have an array of hashes (AoH) which looks like this:
$VAR1 = [
{
'Unit' => 'M',
'Size' => '321',
'User' => 'test'
}
{
'Unit' => 'M'
'Size' => '0.24'
'User' => 'test1'
}
...
];
How do I write my AoH to a CSV file with separators, to get the following result:
test;321M
test1;0.24M
I've already tried this code:
my $csv = Text::CSV->new ( { sep_char => ';' } );
$csv->print( $fh1, \#homefoldersize );
But I get
HASH(0x....)
in my CSV file.
Pretty fundamentally - CSV is an array based data structure - it's a vaguely enhanced version of join. But the thing you need for this job is print_hr from Text::CSV.
First you need to set your header order:
$csv->column_names (#names); # Set column names for getline_hr ()
Then you can use
$csv -> print_hr ( *STDOUT, $hashref );
E.g.
$csv -> column_names ( qw ( User Size Unit ) );
foreach my $hashref ( #homefoldersize ) {
$csv -> print_hr ( *STDOUT, $hashref );
}
As you want to concatenate a couple of your columns though, that's slightly harder - you'll need to transform the data first, because otherwise Size and Unit are separate columns.
foreach my $hashref ( #homefoldersize ) {
$hashref -> {Size} .= $hashref -> {Unit};
delete $hashref -> {Unit};
}
Additionally -as another poster notes - you'll need to set sep_char to change the delimiter to ;.
As an alternative to that - you could probably use a hash slice:
#values = #hash{#keys};
But print_hr does pretty much the same thing.
All that is necessary is
printf "%s;%s%s\n", #{$_}{qw/ User Size Unit /} for #homefoldersize;
Try using the example for Text::CSV posted here: http://search.cpan.org/~makamaka/Text-CSV-1.33/lib/Text/CSV.pm
You will need to set sep_char = ';' to make it semicolon-separated.
Related
I have a question regarding duplicate keys in hashes.
Say my dataset looks something like this:
>Mammals
Cats
>Fish
Clownfish
>Birds
Parrots
>Mammals
Dogs
>Reptiles
Snakes
>Reptiles
Snakes
What I would like to get out of my script is a hash that looks like this:
$VAR1 = {
'Birds' => 'Parrots',
'Mammals' => 'Dogs', 'Cats',
'Fish' => 'Clownfish',
'Reptiles' => 'Snakes'
};
I found a possible answer here (https://www.perlmonks.org/?node_id=1116320). However I am not sure how to identify the values and the duplicates with the format of my dataset.
Here's the code that I have been using:
use Data::Dumper;
open($fh, "<", $file) || die "Could not open file $file $!/n";
while (<$fh>) {
chomp;
if($_ =~ /^>(.+)/){
$group = $1;
$animals{$group} = "";
next;
}
$animals{$group} .= $_;
push #{$group (keys %animals)}, $animals{$group};
}
print Dumper(\%animals);
When I execute it the push function does not seem to work as the output from this command is the same as when the command is absent (in the duplicate "Mammal" group, it will replace the cat with the dog instead of having both as arrays within the same group).
Any suggestions as to what I am doing wrong would be highly appreciated.
Thanks !
You're very close here. We can't get exactly the output you want from Data::Dumper because hashes can only have one value per key. The easiest way to fix that is to assign a reference to an array to the key and add things to it. But since you want to eliminate the duplicates as well, it's easier to build hashes as an intermediate representation then transform them to arrays:
use Data::Dumper;
my $file = "animals.txt";
open($fh, "<", $file) || die "Could not open file $file $!/n";
while (<$fh>) {
chomp;
if(/^>(.+)/){
$group = $1;
next;
}
$animals{$group} = {} unless exists $animals{$group};
$animals{$group}->{$_} = 1;
}
# Transform the hashes to arrays
foreach my $group (keys %animals) {
# Make the hash into an array of its keys
$animals{$group} = [ sort keys %{$animals{$group}} ];
# Throw away the array if we only have one thing
$animals{$group} = $animals{$group}->[0] if #{ $animals{$group} } == 1;
}
print Dumper(\%animals);
Result is
$VAR1 = {
'Reptiles' => 'Snakes',
'Fish' => 'Clownfish',
'Birds' => 'Parrots',
'Mammals' => [
'Cats',
'Dogs'
]
};
which is as close as you can get to what you had as your desired output.
For ease in processing the ingested data, it may actually be easier to not throw away the arrays in the one-element case so that every entry in the hash can be processed the same way (they're all references to arrays, no matter how many things are in them). Otherwise you've added a conditional to strip out the arrays, and you have to add another conditional test in your processing code to check
if (ref $item) {
# This is an anonymous array
} else {
# This is just a single entry
}
and it's easier to just have one path there instead of two, even if the else just wraps the single item into an array again. Leave them as arrays (delete the $animals{$group} = $animals{$group}->[0] line) and you'll be fine.
Given:
__DATA__
>Mammals
Cats
>Fish
Clownfish
>Birds
Parrots
>Mammals
Dogs
>Reptiles
Snakes
>Reptiles
Snakes
(at the end of the source code or a file with that content)
If you are willing to slurp the file, you can do something with a regex and a HoH like this:
use Data::Dumper;
use warnings;
use strict;
my %animals;
my $s;
while(<DATA>){
$s.=$_;
}
while($s=~/^>(.*)\R(.*)/mg){
++$animals{$1}{$2};
}
print Dumper(\%animals);
Prints:
$VAR1 = {
'Mammals' => {
'Cats' => 1,
'Dogs' => 1
},
'Birds' => {
'Parrots' => 1
},
'Fish' => {
'Clownfish' => 1
},
'Reptiles' => {
'Snakes' => 2
}
};
Which you can arrive to your format with this complete Perl program:
$s.=$_ while(<DATA>);
++$animals{$1}{$2} while($s=~/^>(.*)\R(.*)/mg);
while ((my $k, my $v) = each (%animals)) {
print "$k: ". join(", ", keys($v)) . "\n";
}
Prints:
Fish: Clownfish
Birds: Parrots
Mammals: Cats, Dogs
Reptiles: Snakes
(Know that the output order may be different than file order since Perl hashes do not maintain insertion order...)
I am a beginner programmer who is writing a program using perl that will eventually allow me to search for name, and have it tell me the early steps. So far (with the help of many nice people on here) I have this code for array format.
#!/usr/local/bin/perl
use strict;
use warnings;
my #M_array;
my #F_array;
open (my $input, "<", 'ssbn1898.txt');
while ( <$input> ) {
chomp;
my ( $name, $id ) = split ( /,/ );
if ( $id eq "M" ) {
push ( #M_array, $name );
}
else {
push ( #F_array, $name );
}
}
close ( $input );
print 'M: ' . join("\t", #M_array) . "\n";
print 'F: ' . join("\t", #F_array) . "\n";
And I attempted to use the same code to put it into a hash.
#!/usr/local/bin/perl
use strict;
use warnings;
my %M_hash;
my %F_hash;
open (my $input, "<", 'ssbn1898.txt');
while ( <$input> ) {
chomp;
my ( $name, $id ) = split ( /,/ );
if ( $id eq "M" ) {
push ( %M_hash, $name );
}
else {
push ( %F_hash, $name );
}
}
close ( $input );
print 'M: ' . join("\t", %M_hash) . "\n";
print 'F: ' . join("\t", %F_hash) . "\n";
But I get an error on the "push" function. I would assume then that this function is just for arrays. Is there an equivalent function for a hash? And what does the "push" function really do?
Thank you all for your help
http://www.ourbabynamer.com/popular-baby-names.php?year=1898&top=1000&country=US&order=0&page=1
This is the data I am working with
Push adds an element to the back of an array.
#a = ( 1, 2, 3 );
push #a, 4; # #a is now ( 1, 2, 3, 4 )
Insert adds an element to a hash.
%h = ( foo => 1, bar => 2 );
$h{ qux } = 3; # %h is now ( foo => 1, bar => 2, qux => 3 );
Take a look at perldoc perlintro
http://perldoc.perl.org/perlintro.html
push adds an element at the end of an array. Hashes don't have an end, so there's no equivalent for hashes. You need to specify the key of the element you wish to set.
$hash{$key} = $val;
I don't know why you changed the array into a hash. It makes no sense to use a hash here. The solution is to revert your change.
In a comment, you say that you "must use this data as an array and a hash". I'm not really sure what you mean, but one possible interpretation is that your teacher wants you do use both hashes and arrays in your code.
One way to do that would be to store your data in an hash of arrays. It would look something like this.
#!/usr/local/bin/perl
use strict;
use warnings;
use 5.010;
my %data;
while ( <> ) { # Use <> to read from STDIN. Makes life easier :-)
chomp;
my ( $name, $gender ) = split /,/;
push #{$data{$gender}}, $name;
}
foreach (keys %data) {
say "$_: " . join("\t", #{$data{$_}_);
}
But that would involve using array references, which sounds like it might be a little advanced for your current course.
One advantage of this approach is that it will continue to work (without code changes) should you wish to add new genders to your input data!
push works on arrays, read more at: http://perldoc.perl.org/functions/push.html
Hashes are different than arrays, they are like associate arrays. They are un-ordered group of key-value pairs. To add some key to hash you do something like below:
my %hash = (key1 => "value1", key2 => "value2");
$hash{"key3"} = "value3";
Note that keys must be unique.
See also:
Hashes in Perl - Gabor's blog
keys - perldoc
values - perldoc
There is no specific function to push the element to the hash, you just need assign the value to hash key as below -
$hash{key} = 'value';
Though it will not sure that this item will be appended as the last element as the hash stores it keys in random fashion,
Am I doing this right?
my $variable = "hello,world";
my #somearray = split(',',$variable);
my %hash = ( 'somevalue' => #somearray);
foreach my $key ( keys %hash ) {
print $key;
foreach my $value ( #{$hash{$key}} ) {
print $value; #the value is not being read/printed
}
}
I don't know if I'm accessing the array that is stored in the hash for the particular value
You've been bitten by perl's flattening nature of lists. You see, when you do:
my %hash = ('somekey' => #somearray), perl converts that into a list form of hash assignment. So, what perl actually sees is:
my %hash = ('somekey' => 'hello', 'world' => ''); # I have put '' for simplicity, though it might well be `undef`
So, the next time you look up by 'somekey', you end up getting the string 'hello' and not the array "['hello', 'world']"
To fix this, you can use references. perlref can help you there for more information.
my %hash = ('somekey' => \#somearray);
# $hash{'somekey'} is an array reference now.
# So you use the pointy lookup syntax.
print $hash{'somekey'}->[0];
Another useful tool in visualising data structures is using the module Data::Dumper. It's available in the perl core distribution. Using it is as simple as doing:
use Data::Dumper;
print Dumper \%hash; # remember to pass in a reference to the actual datastructure, not the data structure itself.
Have fun!
This is wrong:
my %hash = ( 'somevalue' => #somearray);
The array is "flattened" to a list, so the line is equivalent to
my %hash = qw( somevalue hello world );
You need an array reference to create the inner array:
my %hash = ( 'somevalue' => \#somearray);
So you wanted to create a hash of array data structure. Something like this will also work.
my $variable = "hello,world";
my #somearray = split(',',$variable);
my %hash;
#my %hash = ( 'somevalue' => #somearray);
push (#{$hash{'somevalue'}},#somearray); #Storing it in a hash of array
foreach my $key ( keys %hash ) {
print $key;
foreach my $value ( #{$hash{$key}} ) {
print $value; #the value is not being read/printed
}
}
I have a file that looks like this:
[options42BuySide]
logged-check-times=06:01:00
logged-check-address=192.168.3.4
logged-check-reply=192.168.2.5
logged-check-vac-days=sat,sun
start-time=06:01:00
stop-time=19:00:00
falldown=logwrite after 10000
failtolog=logwrite after 10000
listento=all
global-search-text=Target Down. This message is stored;
[stock42BuySide]
logged-check-times=06:01:00
logged-check-address=192.168.2.13
logged-check-reply=192.168.2.54
logged-check-vac-days=sat,sun
start-time=06:01:00
stop-time=18:00:00
The script grinds the list down to just the name, start and stop time.
sellSide40, start-time=07:05:00, stop-time=17:59:00
SellSide42, start-time=07:06:00, stop-time=17:29:00
SellSide44, start-time=07:31:00, stop-time=16:55:00
42SellSide, start-time=09:01:00, stop-time=16:59:00
The problem is that I would like to filter out specific names from the file with comand line parameters.
I am trying to use the #ARGV array and grep the command line values out of the #nametimes array. Something like :
capser#capser$ ./get_start_stop SorosSellSide42 ETFBuySide42
The script works fine for parsing the file - I just need help on the command line array
#!/usr/bin/perl
use strict ;
use warnings ;
my ($name , $start, $stop, $specific);
my #nametimes;
my $inifile = "/var/log/Alert.ini";
open ( my $FILE, '<', "$inifile") or die ("could not open the file -- $!");
while(<$FILE>) {
chomp ;
if (/\[(\w+)\]/) {
$name = $1;
} elsif (/(start-time=\d+:\d+:\d+)/) {
$start = $1;
} elsif (/(stop-time=\d+:\d+:\d+)/) {
$stop = $1;
push (#nametimes, "$name, $start, $stop");
}
}
for ($a = 0; $a >= $#ARGV ; $a++) {
$specific = (grep /$ARGV[$a]/, #nametimes) ;
print "$specific\n";
}
It is probably pretty easy - however I have worked on it for days, and I am the only guy that uses perl in this shop. I don't have anyone to ask and the googlize is not panning out. I apologize in advance for angering the perl deities who are sure to yell at me for asking such and easy question.
Your construct for looping over #ARGV is a bit unwieldy - the more common way of doing that would be:
for my $name (#ARGV) {
#do something
}
But really, you don't even need to loop over it. You can just join them all directly into a single regular expression:
my $names = join("|", #ARGV);
my #matches = grep { /\b($names)\b/ } #nametimes;
I've used \b in the regex here - that indicates a word boundary, so the argument SellSide4 wouldn't match SellSide42. That may or may not be what you want...
Use an array to store the results from the grep(), not a scalar. Push them, not assign. Otherwise the second iteration of the for loop will overwrite results. Something like:
for my $el ( #ARGV ) {
push #specific, grep { /$el/ } #nametimes);
};
print join "\n", #specific;
The easiest thing to do is to store your INI file as a structure. Then, you can go through your structure and pull out what you want. The simplest structure would be a hash of hashes. Where your heading is the key to the outer hash, and the inner hash is keyed by the parameter:
Here's is creating the basic structure:
use warnings;
use strict;
use autodie;
use feature qw(say);
use Data::Dumper;
use constant INI_FILE => "test.file.txt";
open my $ini_fh, "<", INI_FILE;
my %ini_file;
my $heading;
while ( my $line = <$ini_fh> ) {
chomp $line;
if ( $line =~ /\[(.*)\]/ ) { #Headhing name
$heading = $1;
}
elsif ( $line =~ /(.+?)\s*=\s*(.+)/ ) {
my $parameter = $1;
my $value = $2;
$ini_file{$heading}->{$parameter} = $value;
}
else {
say "Invalid line $. - $line";
}
}
After this, the structure will look like this:
$VAR1 = {
'options42BuySide' => {
'stop-time' => '19:00:00',
'listento' => 'all',
'logged-check-reply' => '192.168.2.5',
'logged-check-vac-days' => 'sat,sun',
'falldown' => 'logwrite after 10000',
'start-time' => '06:01:00',
'logged-check-address' => '192.168.3.4',
'logged-check-times' => '06:01:00',
'failtolog' => 'logwrite after 10000',
'global-search-text' => 'Target Down. This message is stored;'
},
'stock42BuySide' => {
'stop-time' => '18:00:00',
'start-time' => '06:01:00',
'logged-check-reply' => '192.168.2.54',
'logged-check-address' => '192.168.2.13',
'logged-check-vac-days' => 'sat,sun',
'logged-check-times' => '06:01:00'
}
};
Now, all you have to do is parse your structure and pull the information you want out of it:
for my $heading ( sort keys %ini_file ) {
say "$heading " . $ini_file{$heading}->{"start-time"} . " " . $ini_file{$heading}->{"stop-time"};
}
You could easily modify this last loop to skip the headings you want, or to print out the exact parameters you want.
I would also recommend using Getopt::Long to parse your command line parameters:
my_file -include SorosSellSide42 -include ETFBuySide42 -param start-time -param stop-time
Getopt::Long could store your parameters in arrays. For example. It would put all the -include parameters in an #includes array and all the -param parameters in an #parameters array:
for my $heading ( #includes ) {
print "$heading ";
for my $parameter ( #parameters ) {
print "$ini_file{$heading}->{$parameter} . " ";
}
print "\n;
}
Of course, there needs to be lots of error checking (does the heading exist? What about the requested parameters?). But, this is the basic structure. Unless your file is extremely long, this is probably the easiest way to process it. If your file is extremely long, you could use the #includes and #parameters in the first loop as you read in the parameters and headings.
I am novice in php development and I need your help.
I have read the column of a file and I want to keep in an array
only the distinct values
I do it with this code in a while (fgetcsv) loop
$sex_array = array();
isset($sex_array[$sex])?$sex_array[$sex]++:$sex_array[$sex]=1;
my array is in format
Array ( [man] => 33 [woman] => 141 )
How can I make a variable and when I print it to take
man:33,woman:141
I tried implode but I take 33,141
look at foreach taken from the page:
foreach ($a as $k => $v) {
echo "\$a[$k] => $v.\n";
}