So I have this code which takes input and output file from command line, then writes a certain output to the output file (Only the relevant portion shown here due to privacy issues):
use strict;
use warnings;
use autodie;
# check that two arguments have been passed
die "usage: $0 input output\n" unless #ARGV == 2;
my $infile = shift;
my $outfile = shift;
open my $in, "<", $infile;
open(DATA, $in);
open my $out, ">", $outfile;
my %DEF = (
I => [ qw( P Pl P.P P.Pl Pl.P Pl.Pl P.P.P P.P.Pl P.Pl.P P.Pl.Pl Pl. +P.P Pl.P.Pl Pl.Pl.P Pl.Pl.Pl ) ],
II => [ qw( E P.E Pl.E P.P.E P.Pl.E Pl.P.E Pl.Pl.E ) ],
III => [ qw( E.P E.Pl P.E.P P.E.Pl Pl.E.P Pl.E.Pl E.P.P E.P.Pl E.Pl.P + E.Pl.Pl ) ],
IV => [ qw( E.E P.E.E Pl.E.E E.P.E E.Pl.E E.E.P E.E.Pl E.E.E ) ]
);
# Hash table/dictionary for all the groups
my #rank = map #$_, #DEF{qw(I II III IV)};
my %rank = map { $rank[$_ - 1] => $_ } 1 .. #rank;
my #group = map { ($_) x #{ $DEF{$_} } } qw(I II III IV);
my %group = map { $rank[$_ - 1] => $group[$_ - 1] . "_" . $_ } 1 .. #group;
sub rank {
$rank{ $a->[2] } <=> $rank{ $b->[2] }
}
my %T;
sub oh {
map values %$_, #_;
}
sub ab {
my ($b, $a) = #_;
[$b->[0], $a->[1], qq($a->[2].$b->[2]), qq($b->[3]<-$a->[3])];
}
sub xtend {
my $a = shift;
map { ab $_, $a } oh #{ $T{ $a->[0] } }{#_};
}
sub ins {
$T{ $_[3] //= $_[1] }{ $_[2] }{ $_[0] } = \#_;
}
ins split /,\s*/ for <DATA>;
#ins split /,\s*/ for $filename;
ins #$_ for map { xtend $_, qw(P E Pl) } (oh oh oh \%T);
ins #$_ for map { xtend $_, qw(P E Pl) } (oh oh oh \%T);
for (sort { rank } grep { $_->[1] eq 'Q' } (oh oh oh \%T)) {
print $out "%-4s: %20s, %-8s %6s\n",
$_->[0],
qq($_->[0]$_->[3]),
$_->[2],
$group{ $_->[2] };
close $in;
close $out;
}
The problem is that it isnt writing anything to the output file.
perl program.pl input_file output_file
Due to certain reasons I want to read in the input file in format, so that cant be done away with.
Please help
input_file
M19,Q,P,
M31,M19,Pl,
M420,M31,E,
M421,M31,E,
M33,M31,E,
M438,M33,Pl,
M445,M33,E,
M437,M33,E,
M444,M33,E,
M73,M33,E,
M552,M73,Pl,
M553,M73,Pl,
M569,M73,E,
M549,M73,E,
M550,M73,E,
The major problems I can see are these
The line open(DATA, $in) is meaningless. I presume you want to test your program with data from the DATA file handle, in which case you want
my $in = \*DATA;
You are closing both file handles inside the final for loop. That means only one line will ever be written to the output, and thereafter you will get the warning
print() on closed filehandle
You are using print with a format. You need printf instead
This variant of your program fixes these things, and produces some output. Is it what you expected?
use strict;
use warnings;
use autodie;
# check that two arguments have been passed
# die "usage: $0 input output\n" unless #ARGV == 2;
my ($infile, $outfile) = #ARGV;
# open my $in_fh, '<', $infile;
# open my $out_fh, '>', $outfile;
my $in_fh = \*DATA;
my $out_fh = \*STDOUT;
my %DEF = (
I => [ qw( P Pl P.P P.Pl Pl.P Pl.Pl P.P.P P.P.Pl P.Pl.P P.Pl.Pl Pl. +P.P Pl.P.Pl Pl.Pl.P Pl.Pl.Pl ) ],
II => [ qw( E P.E Pl.E P.P.E P.Pl.E Pl.P.E Pl.Pl.E ) ],
III => [ qw( E.P E.Pl P.E.P P.E.Pl Pl.E.P Pl.E.Pl E.P.P E.P.Pl E.Pl.P + E.Pl.Pl ) ],
IV => [ qw( E.E P.E.E Pl.E.E E.P.E E.Pl.E E.E.P E.E.Pl E.E.E ) ]
);
# Hash table/dictionary for all the groups
my #rank = map { #$_ } #DEF{qw(I II III IV)};
my %rank = map { $rank[$_ - 1] => $_ } 1 .. #rank;
my #group = map { ($_) x #{ $DEF{$_} } } qw(I II III IV);
my %group = map { $rank[$_ - 1] => $group[$_ - 1] . "_" . $_ } 1 .. #group;
my %T;
sub rank {
$rank{ $a->[2] } <=> $rank{ $b->[2] }
}
sub oh {
map values %$_, #_;
}
sub ab {
my ($b, $a) = #_;
[ $b->[0], $a->[1], qq($a->[2].$b->[2]), qq($b->[3]<-$a->[3]) ];
}
sub xtend {
my $a = shift;
map { ab $_, $a } oh #{ $T{ $a->[0] } }{#_};
}
sub ins {
$T{ $_[3] //= $_[1] }{ $_[2] }{ $_[0] } = \#_;
}
ins split /,\s*/ for <$in_fh>;
close $in_fh;
ins #$_ for map { xtend $_, qw(P E Pl) } (oh oh oh \%T);
ins #$_ for map { xtend $_, qw(P E Pl) } (oh oh oh \%T);
for (sort { rank } grep { $_->[1] eq 'Q' } (oh oh oh \%T)) {
printf $out_fh "%-4s: %20s, %-8s %6s\n",
$_->[0],
qq($_->[0]$_->[3]),
$_->[2],
$group{ $_->[2] };
}
close $out_fh;
__DATA__
M19,Q,P,
M31,M19,Pl,
M420,M31,E,
M421,M31,E,
M33,M31,E,
M438,M33,Pl,
M445,M33,E,
M437,M33,E,
M444,M33,E,
M73,M33,E,
M552,M73,Pl,
M553,M73,Pl,
M569,M73,E,
M549,M73,E,
M550,M73,E,
output
M19 : M19Q, P I_1
M31 : M31M19<-Q, P.Pl I_4
M421: M421M31<-M19<-Q, P.Pl.E II_20
M420: M420M31<-M19<-Q, P.Pl.E II_20
M33 : M33M31<-M19<-Q, P.Pl.E II_20
Related
I have a text file of the following format:
1 4730 1031782 init
4 0 6 events
2190 450 0 top
21413 5928 1 sshd
22355 1970 2009 find
I want to print rows of this file only if the second column of data meets the requirement >= 2000 - how can I do this?
Currently I am reading the file and printing it like so:
sub read_file{
my $data_failed = 1;
my $file = 'task_file';
if(open (my $file, "task_file" || die "$!\n")){
my #COLUMNS = qw( memory cpu program );
my %sort_strings = ( program => sub { $a cmp $b } );
my (%process_details, %sort);
while (<$file>) {
$data_failed = 0;
my ($process_id, $memory_size, $cpu_time, $program_name) = split;
$process_details{$process_id} = { memory => $memory_size,
cpu => $cpu_time,
program => $program_name };
undef $sort{memory}{$memory_size}{$process_id};
undef $sort{cpu}{$cpu_time}{$process_id};
undef $sort{program}{$program_name}{$process_id};
}
if($option_a == 1){
if (-z $file){print "No tasks found\n";}
for my $column ($COLUMNS[2]) {
my $cmp = $sort_strings{$column} || sub { $a <=> $b };
for my $value (sort $cmp keys %{ $sort{$column} }
) {
my #pids = keys %{ $sort{$column}{$value} };
say join ' ', $_, #{ $process_details{$_} }{#COLUMNS}
for #pids;
}
}
}
} else { print "No tasks found\n"}
}
The if($option_a == 1) bit is just reading values from another function that parses command line options.
my ($process_id, $memory_size, $cpu_time, $program_name) = split;
At this point, you can complete the loop, or you can continue to the next line. Just add the line:
next if $memory_size < 2000;
right after the split, and you'll eliminate all the records in memory that fail to meet your requirements.
Filtering a list is easily done with grep:
#!/usr/bin/perl
use strict;
use feature qw{ say };
use warnings;
my #COLUMNS = qw( memory cpu program );
my (%process_details, %sort);
while (<DATA>) {
my ($process_id, $memory_size, $cpu_time, $program_name) = split;
$process_details{$process_id} = { memory => $memory_size,
cpu => $cpu_time,
program => $program_name };
undef $sort{memory}{$memory_size}{$process_id};
undef $sort{cpu}{$cpu_time}{$process_id};
undef $sort{program}{$program_name}{$process_id};
}
for my $value (sort { $a cmp $b } keys %{ $sort{program} }) {
my #pids = grep $process_details{$_}{memory} > 2000,
keys %{ $sort{program}{$value} };
say join ' ', $_, #{ $process_details{$_} }{#COLUMNS}
for #pids;
}
__DATA__
...
Something like this perhaps:
#!/usr/bin/perl
use strict;
use warnings;
while (<DATA>) {
print if (split)[1] > 2000;
}
__DATA__
1 4730 1031782 init
4 0 6 events
2190 450 0 top
21413 5928 1 sshd
22355 1970 2009 find
With no arguments, split() splits $_ on whitespace (which is what we want). We can then use a list slice to look at the second element of that and print the line if that value is greater than 2000.
I am very new at perl and had discovered the solution at:
Perl: Compare Two CSV Files and Print out differences
I have gone through dozens of other solutions and this comes closest, except that instead of finding the differences between 2 CSV files, I want to find where the second CSV file matches the first one in column and row. How could I modify the following script to find the matches in column/row instead of the differences. I am hoping to dissect this code and learn arrays from there, but wanted to find out the solution to this application. Much thanks.
use strict;
my #arr1;
my #arr2;
my $a;
open(FIL,"a.txt") or die("$!");
while (<FIL>)
{chomp; $a=$_; $a =~ s/[\t;, ]*//g; push #arr1, $a if ($a ne '');};
close(FIL);
open(FIL,"b.txt") or die("$!");
while (<FIL>)
{chomp; $a=$_; $a =~ s/[\t;, ]*//g; push #arr2, $a if ($a ne '');};
close(FIL);
my %arr1hash;
my %arr2hash;
my #diffarr;
foreach(#arr1) {$arr1hash{$_} = 1; }
foreach(#arr2) {$arr2hash{$_} = 1; }
foreach $a(#arr1)
{
if (not defined($arr2hash{$a}))
{
push #diffarr, $a;
}
}
foreach $a(#arr2)
{
if (not defined($arr1hash{$a}))
{
push #diffarr, $a;
}
}
print "Diff:\n";
foreach $a(#diffarr)
{
print "$a\n";
}
# You can print to a file instead, by: print FIL "$a\n";
ok, I realize that this was more what I was looking for:
use strict;
use warnings;
use feature qw(say);
use autodie;
use constant {
FILE_1 => "file1.txt",
FILE_2 => "file2.txt",
};
#
# Load Hash #1 with value from File #1
#
my %hash1;
open my $file1_fh, "<", FILE_1;
while ( my $value = <$file1_fh> ) {
chomp $value;
$hash1{$value} = 1;
}
close $file1_fh;
#
# Load Hash #2 with value from File #2
#
my %hash2;
open my $file2_fh, "<", FILE_2;
while ( my $value = <$file2_fh> ) {
chomp $value;
$hash2{$value} = 1;
}
close $file2_fh;
Now I want to search file2's hash to check if there are ANY matches from file1's hash. That is where I am stuck
With new code suggestion, code now looks like this
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use autodie;
use constant {
FILE_1 => "masterlist.csv",
FILE_2 => "pastebin.csv",
};
#
# Load Hash #1 with value from File #1
#
my %hash1;
open my $file1_fh, "<", FILE_1;
while ( my $value = <$file1_fh> ) {
chomp $value;
$hash1{$value} = 1;
}
close $file1_fh;
my %hash2;
open my $file2_fh, "<", FILE_2;
while ( my $value = <$file2_fh> ) {
chomp $value;
if ( $hash1{$value} ) {
print "Match found $value\n";
$hash2{$value}++;
}
}
close $file2_fh;
print "Matches found:\n";
foreach my $key ( keys %hash2 ) {
print "$key found $hash2{$key} times\n";
}
I updated one part with split() and it seems to work, but have to test more to confirm if it fits the solution I'm looking for or I have more work to do one it
#
# Load Hash #1 with value from File #1
#
my %hash1;
open my $file1_fh, "<", FILE_1;
while ( my $value = <$file1_fh> ) {
chomp $value;
$hash1{$value} = ( %hash1, (split(/,/, $_))[1,2] );
}
close $file1_fh;
So, with your code there - you've read in 'file1' to a hash.
Why not instead of reading file 2 into a hash, do instead:
my %hash2;
open my $file2_fh, "<", FILE_2;
while ( my $value = <$file2_fh> ) {
chomp $value;
if ( $hash1{$value} ) {
print "Match found $value\n";
$hash2{$value}++;
}
}
close $file2_fh;
print "Matches found:\n";
foreach my $key ( keys %hash2 ) {
print "$key found $hash2{$key} times\n";
}
I think this code identifies every place that a data field in file A matches a data field in file B (at least it does on my limited test data):
use strict;
use warnings;
my #arr1;
my #arr2;
# a.txt -> #arr1
my $file_a_name = "poster_a.txt";
open(FIL,$file_a_name) or die("$!");
my $a_line_counter = 0;
while (my $a_line = <FIL>)
{
$a_line_counter = $a_line_counter + 1;
chomp($a_line);
my #fields = (split /,/,$a_line);
my $num_fields = scalar(#fields);
s{^\s+|\s+$}{}g foreach #fields;
push #arr1, \#fields if ( $num_fields ne 0);
};;
close(FIL);
my $file_b_name = "poster_b.txt";
open(FIL,$file_b_name) or die("$!");
while (my $b_line = <FIL>)
{
chomp($b_line);
my #fields = (split /,/,$b_line);
my $num_fields = scalar(#fields);
s{^\s+|\s+$}{}g foreach #fields;
push #arr2, \#fields if ( $num_fields ne 0)
};
close(FIL);
# b.txt -> #arr2
#print "\n",#arr2, "\n";
my #match_array;
my $file_a_line_ctr = 1;
foreach my $file_a_line_fields (#arr1)
{
my $file_a_column_ctr = 1;
foreach my $file_a_line_field (#{$file_a_line_fields})
{
my $file_b_line_ctr = 1;
foreach my $file_b_line_fields(#arr2)
{
my $file_b_column_ctr = 1;
foreach my $file_b_field (#{$file_b_line_fields})
{
if ( $file_b_field eq $file_a_line_field )
{
my $match_info =
"$file_a_name line $file_a_line_ctr column $file_a_column_ctr" .
" (${file_a_line_field}) matches: " .
"$file_b_name line $file_b_line_ctr column $file_b_column_ctr ";
push(#match_array, $match_info);
print "$match_info \n";
}
$file_b_column_ctr = $file_b_column_ctr + 1;
}
$file_b_line_ctr = $file_b_line_ctr + 1;
}
$file_a_column_ctr = $file_a_column_ctr + 1;
}
$file_a_line_ctr = $file_a_line_ctr + 1;
}
print "there were ", scalar(#match_array)," matches\n";
I have a two tab separated files that I need to align together. for example:
File 1: File 2:
AAA 123 BBB 345
BBB 345 CCC 333
CCC 333 DDD 444
(These are large files, potentially thousands of lines!)
What I would like to do is to have the output look like this:
AAA 123
BBB 345 BBB 345
CCC 333 CCC 333
DDD 444
Preferably I would like to do this in perl, but not sure how. any help would be greatly appreaciated.
If its just about making a data structure, this can be quite easy.
#!/usr/bin/env perl
# usage: script.pl file1 file2 ...
use strict;
use warnings;
my %data;
while (<>) {
chomp;
my ($key, $value) = split;
push #{$data{$key}}, $value;
}
use Data::Dumper;
print Dumper \%data;
You can then output in any format you like. If its really about using the files exactly as they are, then its a little bit more tricky.
Assuming the files are sorted,
sub get {
my ($fh) = #_;
my $line = <$fh>;
return () if !defined($line);
return split(' ', $line);
}
my ($key1, $val1) = get($fh1);
my ($key2, $val2) = get($fh2);
while (defined($key1) && defined($key2)) {
if ($key1 lt $key2) {
print(join("\t", $key1, $val1), "\n");
($key1, $val1) = get($fh1);
}
elsif ($key1 gt $key2) {
print(join("\t", '', '', $key2, $val2), "\n");
($key2, $val2) = get($fh2);
}
else {
print(join("\t", $key1, $val1, $key2, $val2), "\n");
($key1, $val1) = get($fh1);
($key2, $val2) = get($fh2);
}
}
while (defined($key1)) {
print(join("\t", $key1, $val1), "\n");
($key1, $val1) = get($fh1);
}
while (defined($key2)) {
print(join("\t", '', '', $key1, $val1), "\n");
($key2, $val2) = get($fh2);
}
Similar to Joel Berger's answer, but this approach allows to you keep track of whether files did or did not contain a given key:
my %data;
while (my $line = <>){
chomp $line;
my ($k) = $line =~ /^(\S+)/;
$data{$k}{line} = $line;
$data{$k}{$ARGV} = 1;
}
use Data::Dumper;
print Dumper(\%data);
Output:
$VAR1 = {
'CCC' => {
'other.dat' => 1,
'data.dat' => 1,
'line' => 'CCC 333'
},
'BBB' => {
'other.dat' => 1,
'data.dat' => 1,
'line' => 'BBB 345'
},
'DDD' => {
'other.dat' => 1,
'line' => 'DDD 444'
},
'AAA' => {
'data.dat' => 1,
'line' => 'AAA 123'
}
};
As ikegami mentioned, it assumes that the files' contents are arranged as shown in your example.
use strict;
use warnings;
open my $file1, '<file1.txt' or die $!;
open my $file2, '<file2.txt' or die $!;
my $file1_line = <$file1>;
print $file1_line;
while ( my $file2_line = <$file2> ) {
if( defined( $file1_line = <$file1> ) ) {
chomp $file1_line;
print $file1_line;
}
my $tabs = $file1_line ? "\t" : "\t\t";
print "$tabs$file2_line";
}
close $file1;
close $file2;
Reviewing your example, you show some identical key/value pairs in both files. Given this, it looks like you want to show the pair(s) unique to file 1, unique to file 2, and show the common pairs. If this is the case (and you're not trying to match the files' pairs by either keys or values), you can use List::Compare:
use strict;
use warnings;
use List::Compare;
open my $file1, '<file1.txt' or die $!;
my #file1 = <$file1>;
close $file1;
open my $file2, '<file2.txt' or die $!;
my #file2 = <$file2>;
close $file2;
my $lc = List::Compare->new(\#file1, \#file2);
my #file1Only = $lc->get_Lonly; # L(eft array)only
for(#file1Only) { print }
my #bothFiles = $lc->get_intersection;
for(#bothFiles) { chomp; print "$_\t$_\n" }
my #file2Only = $lc->get_Ronly; # R(ight array)only
for(#file2Only) { print "\t\t$_" }
I have the following sequence of data in Perl:
143:0.0209090909090909
270:0.0909090909090909
32:0.0779090909090909
326:0.3009090909090909
Please, how can I sort them based on the numbers before the colon, to get this as my output?
32:0.0779090909090909
143:0.0209090909090909
270:0.0909090909090909
326:0.3009090909090909
It does not matter that there are colons there.
Perl's rules for converting strings to numbers
will just do The Right Thing:
#!/usr/bin/perl
use warnings;
use strict;
my #nums = qw(
143:0.0209090909090909
270:0.0909090909090909
32:0.0779090909090909
326:0.3009090909090909
);
{ no warnings 'numeric';
#nums = sort {$a <=> $b} #nums;
}
print "$_\n" for #nums;
The built-in sort function can be used:
Program
#!/usr/bin/env perl
use strict;
use warnings;
my #data = qw(
143:0.0209090909090909
270:0.0909090909090909
32:0.0779090909090909
326:0.3009090909090909
);
my $match = qr/^(\d+):/;
#data = sort { ( $a =~ $match )[0] <=> ( $b =~ $match )[0] } #data;
print join( "\n", #data ), "\n";
Output
32:0.0779090909090909
143:0.0209090909090909
270:0.0909090909090909
326:0.3009090909090909
Something along the lines of:
my #sorted = sort { my ($a1) = split(/:/,$a);
my ($b1) = split(/:/,$b);
$a1 <=> $b1 } #data ;
$a1 and $b1 will be the first element of each of the sorting inputs, split on the colon character.
I'd simply use
sort -n < input.txt
Otherwise:
use strict;
use warnings;
my #lines = (<>);
print for sort {
my #aa = split(/:/, $a);
my #bb = split(/:/, $b);
1*$aa[0] <=> 1*$bb[0]
} #lines;
What, no Schwhartzian transform yet?
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my #data = qw(143:0.0209090909090909
270:0.0909090909090909
32:0.0779090909090909
326:0.3009090909090909);
my #sorted = map $_->[0], sort {$a->[1] <=> $b->[1]} map {[$_, m/^(.+):/]} #data;
print Dumper \#sorted;
Output:
$VAR1 = [
'32:0.0779090909090909',
'143:0.0209090909090909',
'270:0.0909090909090909',
'326:0.3009090909090909'
];
Given the variety, I figured some benchmarks might be appropriate. Note, please double-check the benchmarking code before trusting these numbers: I whipped the script up in a hurry.
#!/usr/bin/env perl
use 5.012;
use strict;
use warnings;
use Benchmark qw( cmpthese );
use constant DATA_SIZE => 1000;
cmpthese( -1, {
right_thing => sub { do_the_right_thing ( make_data(rt => DATA_SIZE) ) },
re_extract => sub { re_extract ( make_data(re => DATA_SIZE) ) },
split_extract => sub { split_extract ( make_data(se => DATA_SIZE) ) },
schxfrom_re => sub { schxform_re ( make_data(sx => DATA_SIZE) ) },
nop => sub { nop ( make_data(nl => DATA_SIZE) ) },
});
sub do_the_right_thing {
my ($DATA) = #_;
no warnings 'numeric';
[ sort { $a <=> $b } #$DATA ];
}
sub re_extract {
my ($DATA) = #_;
my $re = qr/^([0-9]+):/;
[ sort { ($a =~ $re)[0] <=> ($b =~ $re)[0] } #$DATA ];
}
sub split_extract {
my ($DATA) = #_;
[
sort {
my ($x, $y) = map split(/:/, $_, 2), $a, $b;
$x <=> $y
} #$DATA
];
}
sub schxform_re {
my ($DATA) = #_;
[
map $_->[0],
sort { $a->[1] <=> $b->[1] }
map { [ $_, m/^([0-9]+):/ ] } #$DATA
];
}
sub nop {
my ($DATA) = #_;
[ #$DATA ];
}
sub make_data {
state %cache;
my ($k, $n) = #_;
unless (exists $cache{$k}) {
$cache{ $k } = [
map
sprintf('%d:%f', int(rand 10_000), rand),
1 .. $n
];
}
return $cache{ $k };
}
Results
Rate re_extract schxfrom_re split_extract right_thing nop
re_extract 32.1/s -- -85% -92% -98% -99%
schxfrom_re 213/s 565% -- -46% -87% -94%
split_extract 392/s 1121% 84% -- -76% -89%
right_thing 1614/s 4933% 657% 312% -- -53%
nop 3459/s 10685% 1522% 783% 114% --
I've a set of strings with variable sizes, for example:
AAA23
AB1D1
A1BC
AAB212
My goal is have in alphabetical order and unique characters collected for COLUMNS, such as:
first column : AAAA
second column : AB1A
and so on...
For this moment I was able to extract the posts through a hash of hashes. But now, how can I sort data? Could I for each hash of hash make a new array?
Thank you very much for you help!
Al
My code:
#!/usr/bin/perl
use strict;
use warnings;
my #sessions = (
"AAAA",
"AAAC",
"ABAB",
"ABAD"
);
my $length_max = 0;
my $length_tmp = 0;
my %columns;
foreach my $string (#sessions){
my $l = length($string);
if ($l > $length_tmp){
$length_max = $l;
}
}
print "max legth : $length_max\n\n";
my $n = 1;
foreach my $string (#sessions){
my #ch = split("",$string);
for my $col (1..$length_max){
$columns{$n}{$col} = $ch[$col-1];
}
$n++;
}
foreach my $col (keys %columns) {
print "colonna : $col\n";
my $deref = $columns{$col};
foreach my $pos (keys %$deref){
print " posizione : $pos --> $$deref{$pos}\n";
}
print "\n";
}
exit(0);
What you're doing is rotating the array. It doesn't need a hash of hash or anything, just another array. Surprisingly, neither List::Util nor List::MoreUtils supplies one. Here's a straightforward implementation with a test. I presumed you want short entries filled in with spaces so the columns come out correct.
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use List::Util qw(max);
my #Things = qw(
AAA23
AB1D1
A1BC
AAB212
);
sub rotate {
my #rows = #_;
my $maxlength = max map { length $_ } #rows;
my #columns;
for my $row (#rows) {
my #chars = split //, $row;
for my $colnum (1..$maxlength) {
my $idx = $colnum - 1;
$columns[$idx] .= $chars[$idx] || ' ';
}
}
return #columns;
}
sub print_columns {
my #columns = #_;
for my $idx (0..$#columns) {
printf "Column %d: %s\n", $idx + 1, $columns[$idx];
}
}
sub test_rotate {
is_deeply [rotate #_], [
"AAAA",
"AB1A",
"A1BB",
"2DC2",
"31 1",
" 2",
];
}
test_rotate(#Things);
print_columns(#Things);
done_testing;
You can sort the output of %columns in your code with
foreach my $i (sort { $a <=> $b } keys %columns) {
print join(" " => sort values %{ $columns{$i} }), "\n";
}
This gives
A A A A
A A A C
A A B B
A A B D
But using index numbers as hash keys screams that you should use an array instead, so let's do that. To get the columns, use
sub columns {
my #strings = #_;
my #columns;
while (#strings) {
push #columns => [ sort map s/^(.)//s ? $1 : (), #strings ];
#strings = grep length, #strings;
}
#columns;
}
Given the strings from your question, it returns
A A A A
1 A A B
1 A B B
2 2 C D
1 1 3
2
As you can see, this is unsorted and repeats characters. With Perl, when you see the word unique, always think of hashes!
sub unique_sorted_columns {
map { my %unique;
++$unique{$_} for #$_;
[ sort keys %unique ];
}
columns #_;
}
If you don't mind destroying information, you can have columns sort and filter duplicates:
sub columns {
my #strings = #_;
my #columns;
while (#strings) {
my %unique;
map { ++$unique{$1} if s/^(.)//s } #strings;
push #columns => [ sort keys %unique ];
#strings = grep length, #strings;
}
#columns;
}
Output:
A
1 A B
1 A B
2 C D
1 3
2