Perl : change array item that is hashed to a key - perl

I am having some problem with my perl. I hashed a key to an array. Now I want to change things in the array for each key. But I can't find out how this works :
open(DATEBOOK,"<sample.file");
#datebook = <DATEBOOK>;
$person = "Norma";
foreach(#datebook){
#record = ();
#lines = split(":",$_);
$size = #lines;
for ($i=1; $i < $size; $i++){
$record[$i-1] = $lines[$i];
}
$map{$lines[0]}="#record";
}
for(keys%map){ print $map{$_}};
The datebook file :
Tommy Savage:408.724.0140:1222 Oxbow Court, Sunnyvale,CA 94087:5/19/66:34200
Lesle Kerstin:408.456.1234:4 Harvard Square, Boston, MA 02133:4/22/62:52600
JonDeLoach:408.253.3122:123 Park St., San Jose, CA 94086:7/25/53:85100
Ephram Hardy:293.259.5395:235 Carlton Lane, Joliet, IL 73858:8/12/20:56700
Betty Boop:245.836.8357:635 Cutesy Lane, Hollywood, CA 91464:6/23/23:14500
William Kopf:846.836.2837:6937 Ware Road, Milton, PA 93756:9/21/46:43500
Norma Corder:397.857.2735:74 Pine Street, Dearborn, MI 23874:3/28/45:245700
James Ikeda:834.938.8376:23445 Aster Ave., Allentown, NJ 83745:12/1/38:45000
Lori Gortz:327.832.5728:3465 Mirlo Street, Peabody, MA 34756:10/2/65:35200
Barbara Kerz:385.573.8326:832 Ponce Drive, Gary, IN 83756:12/15/46:268500
I tried $map{$_}[1], but that doesn't work. Can anyone give me an example on how this works :) ?
thanks!

First, use strict and use warnings. Always.
Assuming what you want is a hash of arrays, do something like this:
use strict;
use warnings;
open my $datebookfh, '<', 'sample.file' or die $!;
my #datebook = <$datebookfh>;
my %map;
foreach my $row( #datebook ) {
my #record = split /:/, $row;
my $key = shift #record; # throw out first element and save it in $key
$map{$key} = \#record;
}
You can test that you have the correct structure by using Data::Dumper:
use Data::Dumper;
print Dumper( \%map );
The \ operator takes a reference. All hashes and arrays in Perl contain scalars, so compound structures (e.g. hashes of arrays) are really hashes of references to arrays. A reference is like a pointer.
Before going further, you should check out:
Perl reference tutorial
Arrays of arrays
Perl Data Structure Cookbook

Others have given you excellent advice. Here's one other idea to consider: store your data in a hash of hashes rather than a hash of arrays. It makes the data structure more communicative.
# Include these in your Perl scripts.
use strict;
use warnings;
my %data;
# Use lexical files handles, and check whether open() succeeds.
open(my $fh, '<', shift) or die $!;
while (my $line = <$fh>){
chomp $line;
my ($name, $ss, $address, $date, $number) = split /:/, $line;
$data{$name} = {
name => $name,
ss => $ss,
address => $address,
date => $date,
number => $number,
};
}
# Example usage: print info for one person.
my $person = $data{'Betty Boop'};
print $_, ' => ', $person->{$_}, "\n" for keys %$person;

Related

Perl read and write text file with strings

Friends need help. Following my INPUT TEXT FILE
Andrew UK
Cindy China
Rupa India
Gordon Australia
Peter New Zealand
To convert the above into hash and to write back into file when the records exist in a directory. I have tried following (it does not work).
#!/usr/perl/5.14.1/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %hash = ();
my $file = ".../input_and_output.txt";
my $people;
my $country;
open (my $fh, "<", $file) or die "Can't open the file $file: ";
my $line;
while (my $line =<$fh>) {
my ($people) = split("", $line);
$hash{$people} = 1;
}
foreach my $people (sort keys %hash) {
my #country = $people;
foreach my $c (#country) {
my $c_folder = `country/test1_testdata/17.26.6/$c/`;
if (-d $cad_root){
print "Exit\n";
} else {
print "NA\n";
}
}
This is the primary problem:
my ($people) = split("", $line);
Your are splitting using an empty string, and you are assigning the return value to a single variable (which will just end up with the first character of each line).
Instead, you should split on ' ' (a single space character which is a special pattern):
As another special case, ... when the PATTERN is either omitted or a string composed of a single space character (such as ' ' or "\x20" , but not e.g. / /). In this case, any leading whitespace in EXPR is removed before splitting occurs, and the PATTERN is instead treated as if it were /\s+/; in particular, this means that any contiguous whitespace (not just a single space character) is used as a separator.
Limit the number of fields returned to ensure the integrity of country names with spaces:
#!/usr/bin/env perl
use strict;
use warnings;
my #people;
while (my $line = <DATA>) {
$line =~ /\S/ or next;
$line =~ s/\s+\z//;
push #people, [ split ' ', $line, 2 ];
}
use YAML::XS;
print Dump \#people;
__DATA__
Andrew UK
Cindy China
Rupa India
Gordon Australia
Peter New Zealand
The entries are added to an array so 1) The input order is preserved; and 2) Two people with the same name but from different countries do not result in one entry being lost.
If the order is not important, you could just use a hash keyed on country names with people's names in an array reference for each entry. For now, I am going to assume order matters (it would help us help you if you put more effort into formulate a clear question).
One option is to now go through the list of person-country pairs, and print all those pairs for which the directory country/test1_testdata/17.26.6/$c/ exists (incidentally, in your code you have
my $c_folder = `country/test1_testdata/17.26.6/$c/`;
That will try to execute a program called country/test1_testdata/17.26.6/$c/ and save its output in $c_folder if it produces any. To moral of the story: In programming, precision matters. Just because ` looks like ', that doesn't mean you can use one to mean the other.)
Given that your question is focused on hashes, I use an array of references to anonymous hashes to store the list of people-country pairs in the code below. I cache the result of the lookup to reduce the number of times you need to hit the disk.
#!/usr/bin/env perl
use strict;
use warnings;
#ARGV == 2 ? run( #ARGV )
: die_usage()
;
sub run {
my $people_data_file = shift;
my $country_files_location = shift;
open my $in, '<', $people_data_file
or die "Failed to open '$people_data_file': $!";
my #people;
my %countries;
while (my $line = <$in>) {
next unless $line =~ /\S/; # ignore lines consisting of blanks
$line =~ s/\s+\z//;# remove all trailing whitespace
my ($name, $country) = split ' ', $line, 2;
push #people, { name => $name, country => $country };
$countries{ $country } = undef;
}
# At this point, #people has a list of person-country pairs
# We are going to use %countries to reduce the number of
# times we need to check the existence of a given directory,
# assuming that the directory tree is stable while this program
# is running.
PEOPLE:
for my $person ( #people ) {
my $country = $person->{country};
if ($countries{ $country }) {
print join("\t", $person->{name}, $country), "\n";
}
elsif (-d "$country_files_location/$country/") {
$countries{ $country } = 1;
redo PEOPLE;
}
}
}
sub die_usage {
die "Need data file name and country files location\n";
}
Now, there are a bazillion variations on this which is why it is important for you to formulate a clear and concise question so people trying to help you can answer your specific questions, instead of each coming up his/her own solution to the problem as they see it. For example, one could also do this:
#!/usr/bin/env perl
use strict;
use warnings;
#ARGV == 2 ? run( #ARGV )
: die_usage()
;
sub run {
my $people_data_file = shift;
my $country_files_location = shift;
open my $in, '<', $people_data_file
or die "Failed to open '$people_data_file': $!";
my %countries;
while (my $line = <$in>) {
next unless $line =~ /\S/; # ignore lines consisting of blanks
$line =~ s/\s+\z//;# remove all trailing whitespace
my ($name, $country) = split ' ', $line, 2;
push #{ $countries{$country} }, $name;
}
for my $country (keys %countries) {
-d "$country_files_location/$country"
or delete $countries{ $country };
}
# At this point, %countries maps each country for which
# we have a data file to a list of people. We can then
# print those quite simply so long as we don't care about
# replicating the original order of lines from the original
# data file. People's names will still be sorted in order
# of appearance in the original data file for each country.
while (my ($country, $people) = each %countries) {
for my $person ( #$people) {
print join("\t", $person, $country), "\n";
}
}
}
sub die_usage {
die "Need data file name and country files location\n";
}
If what you want is a counter of names in a hash, then I got you, buddy!
I won't attempt the rest of the code because you are checking a folder of records
that I don't have access to so I can't trouble shoot anything more than this.
I see one of your problems. Look at this:
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say'; # Really like using say instead of print because no need for newline.
my $file = 'input_file.txt';
my $fh; # A filehandle.
my %hash;
my $people;
my $country;
my $line;
unless(open($fh, '<', $file)){die "Could not open file $_ because $!"}
while($line = <$fh>)
{
($people, $country) = split(/\s{2,}/, $line); # splitting on at least two spaces
say "$people \t $country"; # Just printing out the columns in the file or people and Country.
$hash{$people}++; # Just counting all the people in the hash.
# Seeing how many unique names there are, like is there more than one Cindy, etc ...?
}
say "\nNow I'm just sorting the hash of people by names.";
foreach(sort{$a cmp $b} keys %hash)
{
say "$_ => $hash{$_}"; # Based on your file. The counter is at 1 because nobody has the same names.
}
Here is the output. As you can see I fixed the problem by splitting on at least two white-spaces so the country names don't get cut out.
Andrew UK
Cindy China
Rupa India
Gordon Australia
Peter New Zealand
Andrew United States
Now I'm just sorting the hash of people by names.
Andrew => 2
Cindy => 1
Gordon => 1
Peter => 1
Rupa => 1
I added another Andrew to the file. This Andrew is from the United States
as you can see. I see one of your problems. Look at this:
my ($people) = split("", $line);
You are splitting on characters as there is no space between those quotes.
If you look at this change now, you are splitting on at least one space.
my ($people) = split(" ", $line);

Perl : Trying to deference an array after sorting it

I am currently writing a perl script where I have a reference to an array (students) of references. After adding the hash references to the array. Now I add the references to the array of students and then ask the user how to sort them. This is where it gets confusing. I do not know how to deference the sorted array. Using dumper I can get the sorted array but in a unorganized output. How can I deference the array of hash references after sorting?
#!bin/usr/perl
use strict;
use warnings;
use Data::Dumper;
use 5.010;
#reference to a var $r = \$var; Deferencing $$r
#reference to an array $r = \#var ; Deferencing #$r
#referenc to a hash $r = \%var ; deferencing %$r
my $filename = $ARGV[0];
my $students = [];
open ( INPUT_FILE , '<', "$filename" ) or die "Could not open to read \n ";
sub readLines{
while(my $currentLine = <INPUT_FILE>){
chomp($currentLine);
my #myLine = split(/\s+/,$currentLine);
my %temphash = (
name => "$myLine[0]",
age => "$myLine[1]",
GPA => "$myLine[2]",
MA => "$myLine[3]"
);
pushToStudents(\%temphash);
}
}
sub pushToStudents{
my $data = shift;
push $students ,$data;
}
sub printData{
my $COMMAND = shift;
if($COMMAND eq "sort up"){
my #sortup = sort{ $a->{name} cmp $b->{name} } #$students;
print Dumper #sortup;
}elsif($COMMAND eq "sort down"){
my #sortdown = sort{ $b->{name} cmp $a->{name} } #$students;
print Dumper #sortdown;
//find a way to deference so to make a more organize user friendly read.
}else{
print "\n quit";
}
}
readLines();
#Output in random, the ordering of each users data is random
printf"please choose display order : ";
my $response = <STDIN>;
chomp $response;
printData($response);
The problem here is that you're expected Dumper to provide an organised output. It doesn't. It dumps a data structure to make debugging easier. The key problem will be that hashes are explicitly unordered data structures - they're key-value mappings, they don't produce any output order.
With reference to perldata:
Note that just because a hash is initialized in that order doesn't mean that it comes out in that order.
And specifically the keys function:
Hash entries are returned in an apparently random order. The actual random order is specific to a given hash; the exact same series of operations on two hashes may result in a different order for each hash.
There is a whole section in perlsec which explains this in more detail, but suffice to say - hashes are random order, which means whilst you're sorting your students by name, the key value pairs for each student isn't sorted.
I would suggest instead of:
my #sortdown = sort{ $b->{name} cmp $a->{name} } #$students;
print Dumper #sortdown;
You'd be better off with using a slice:
my #field_order = qw ( name age GPA MA );
foreach my $student ( sort { $b -> {name} cmp $a -> {name} } #$students ) {
print #{$student}{#field_order}, "\n";
}
Arrays (#field_order) are explicitly ordered, so you will always print your student fields in the same sequence. (Haven't fully tested for your example I'm afraid, because I don't have your source data, but this approach works with a sample data snippet).
If you do need to print the keys as well, then you may need a foreach loop instead:
foreach my $field ( #field_order ) {
print "$field => ", $student->{$field},"\n";
}
Or perhaps the more terse:
print "$_ => ", $student -> {$_},"\n" for #field_order;
I'm not sure I like that as much though, but that's perhaps a matter of taste.
The essence of your mistake is to assume that hashes will have a specific ordering. As #Sobrique explains, that assumption is wrong.
I assume you are trying to learn Perl, and therefore, some guidance on the basics will be useful:
#!bin/usr/perl
Your shebang line is wrong: On Windows, or if you run your script with perl script.pl, it will not matter, but you want to make sure the interpreter that is specified in that line uses an absolute path.
Also, you may not always want to use the perl interpreter that came with the system, in which case #!/usr/bin/env perl maybe helpful for one-off scripts.
use strict;
use warnings;
use Data::Dumper;
use 5.010;
I tend to prefer version constraints before pragmata (except in the case of utf8). Data::Dumper is a debugging aid, not something you use for human readable reports.
my $filename = $ARGV[0];
You should check if you were indeed given an argument on the command line as in:
#ARGV or die "Need filename\n";
my $filename = $ARGV[0];
open ( INPUT_FILE , '<', "$filename" ) or die "Could not open to read \n ";
File handles such as INPUT_FILE are called bareword filehandles. These have package scope. Instead, use lexical filehandles whose scope you can restrict to the smallest appropriate block.
There is no need to interpolate $filename in the third argument to open.
Always include the name of the file and the error message when dying from an error in open. Surrounding the filename with ' ' helps you identify any otherwise hard to detect characters that might be causing the problem (e.g. a newline or a space).
open my $input_fh, '<', $filename
or die "Could not open '$filename' for reading: $!";
sub readLines{
This is reading into an array you defined in global scope. What if you want to use the same subroutine to read records from two different files into two separate arrays? readLines should receive a filename as an argument, and return an arrayref as its output (see below).
while(my $currentLine = <INPUT_FILE>){
chomp($currentLine);
In most cases, you want all trailing whitespace removed, not just the line terminator.
my #myLine = split(/\s+/,$currentLine);
split on /\s+/ is different than split ' '. In most cases, the latter is infinitely more useful. Read about the differences in perldoc -f split.
my %temphash = (
name => "$myLine[0]",
age => "$myLine[1]",
GPA => "$myLine[2]",
MA => "$myLine[3]"
);
Again with the useless interpolation. There is no need to interpolate those values into fresh strings (except maybe in the case where they might be objects which overloaded the stringification, but, in this case, you know they are just plain strings.
pushToStudents(\%temphash);
No need for the extra pushToStudents subroutine in this case, unless this is a stub for a method that will later be able to load the data to a database or something. Even in that case, it be better to provide a callback to the function.
sub pushToStudents{
my $data = shift;
push $students ,$data;
}
You are pushing data to a global variable. A program where there can only ever be a single array of student records is not useful.
sub printData{
my $COMMAND = shift;
if($COMMAND eq "sort up"){
Don't do this. Every subroutine should have one clear purpose.
Here is a revised version of your program.
#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use Carp qw( croak );
run(\#ARGV);
sub run {
my $argv = $_[0];
#$argv
or die "Need name of student records file\n";
open my $input_fh, '<', $argv->[0]
or croak "Cannot open '$argv->[0]' for reading: $!";
print_records(
read_student_records($input_fh),
prompt_sort_order(),
);
return;
}
sub read_student_records {
my $fh = shift;
my #records;
while (my $line = <$fh>) {
last unless $line =~ /\S/;
my #fields = split ' ', $line;
push #records, {
name => $fields[0],
age => $fields[1],
gpa => $fields[2],
ma => $fields[3],
};
}
return \#records;
}
sub print_records {
my $records = shift;
my $sorter = shift;
if ($sorter) {
$records = [ sort $sorter #$records ];
}
say "#{ $_ }{ qw( age name gpa ma )}" for #$records;
return;
}
sub prompt_sort_order {
my #sorters = (
[ "Input order", undef ],
[ "by name in ascending order", sub { $a->{name} cmp $b->{name} } ],
[ "by name in descending order", sub { $b->{name} cmp $a->{name} } ],
[ "by GPA in ascending order", sub { $a->{gpa} <=> $b->{gpa} } ],
[ "by GPA in descending order", sub { $b->{gpa} <=> $a->{gpa} } ],
);
while (1) {
print "Please choose the order in which you want to print the records\n";
print "[ $_ ] $sorters[$_ - 1][0]\n" for 1 .. #sorters;
printf "\n\t(%s)\n", join('/', 1 .. #sorters);
my ($response) = (<STDIN> =~ /\A \s*? ([1-9][0-9]*?) \s+ \z/x);
if (
$response and
($response >= 1) and
($response <= #sorters)
) {
return $sorters[ $response - 1][1];
}
}
# should not be reached
return;
}

Perl Hash Count

I have a table with users the gender of their kids in seprate lines.
lilly boy
lilly boy
jane girl
lilly girl
jane boy
I wrote a script to put parse the lines and give me a total at the end
lilly boys=2 girls1
jane boys=1 girls=1
I tried this with a hash, but I dont know how to approach it
foreach $lines (#all_lines){
if ($lines =~ /(.+?)/s(.+)/){
$person = $1;
if ($2 =~ /boy/){
$boycount=1;
$girlcount=0;
}
if ($2 =~ /girl/){
$boycount=0;
$girlcount=1;
}
the next part is, if the person doesn't already exist inside the hash, add the person and then start a count for boy and girl. (i think this is the correct way, not sure)
if (!$hash{$person}){
%hash = (
'$person' => [
{'boy' => "0+$boycount", 'girl' => "0+$girlcount"}
],
);
Now, I dont know how to keep updating the values inside the hash, if the person already exists in the hash.
%hash = (
'$person' => [
{'boys' => $boyscount, 'girls' => $girlscount}
],
);
I am not sure how to keep updating the hash.
You just need to study the Perl Data Structures Cookbook
use strict;
use warnings;
my %person;
while (<DATA>) {
chomp;
my ($parent, $gender) = split;
$person{$parent}{$gender}++;
}
use Data::Dump;
dd \%person;
__DATA__
lilly boy
lilly boy
jane girl
lilly girl
jane boy
use strict;
use warnings;
my %hash;
open my $fh, '<', 'table.txt' or die "Unable to open table: $!";
# Aggregate stats:
while ( my $line = <$fh> ) { # Loop over record by record
chomp $line; # Remove trailing newlines
# split is a better tool than regexes to get the necessary data
my ( $parent, $kid_gender ) = split /\s+/, $line;
$hash{$parent}{$kid_gender}++; # Increment by one
# Take advantage of auto-vivification
}
# Print stats:
for my $parent ( keys %hash ) {
printf "%s boys=%d girls = %d\n",
$parent, $hash{$parent}{boy}, $hash{$parent}{girl};
}

Not getting output while creating Hash of Arrays

I am trying to create hash of arrays. I am taking data from a txt file and converting this into hash of arrays.
Txt file data is as below
group1 : usr1 usr4 usr6
group2 : usr2 usr1 usr5
group3 : usr1 usr2 usr3
so on ......
I am converting this hash of arrays like
%hash = (group1 => [usr1 usr4 usr6], group2 => [usr2 usr1 usr5]);
Following code i am trying
%hash = ();
open (FH, "2.txt") or die "file not found";
while (<FH>) {
#array = split (":", $_);
$array[1] =~ s/^\s*//;
$array[1] =~ s/\s*$//;
#arrayRef = split (" ", $array[1]);
$hash{$array[0]} = [ #arrayRef ];
#print #array;
#print "\n";
}
close FH;
print $hash{group1}[0];
print #{ $hash{group2}};
I am not getting output. There is something wrong in the code. Please help me understanding it better
Your code works for me, but the problem is that you are using the key "group1 " (note the extra space), and not "group1" like you think. When you split on colon :, you remember to strip the fields after from spaces, but not the field before. You should probably do:
my #array = split /\s*:\s*/, $_;
Also, you should always use
use strict;
use warnings;
Coding without these two pragmas is difficult and takes much longer.
use strict;
use warnings;
my %hash;
open (my $FH, "<", "2.txt") or die $!;
while (<$FH>) {
my ($key, #array) = split /[:\s]+/, $_;
$hash{$key} = \#array;
}
close $FH;
use Data::Dumper;
print Dumper \%hash;

How to print an array that it looks like a hash [duplicate]

This question already has an answer here:
I need help in perl, how to write a code to get the output of my csv file in the form of a hash [closed]
(1 answer)
Closed 10 years ago.
I am new to Perl, and have to write a code which takes contents of a file into and array and print the output that it looks like a hash. Here is an example entry:
my %amino_acids = (F => ["Phenylalanine", "Phe", ["TTT", "TTC"]])
Out put should be exactly in above format.
Lines of Files are like this...
"Methionine":"Met":"M":"AUG":"ATG"
"Phenylalanine":"Phe":"F":"UUU, UUC":"TTT, TTC"
"Proline":"Pro":"P":"CCU, CCC, CCA, CCG":"CCT, CCC, CCA, CCG"
I have to take last codons after semicolon and ignore the first group.
Is it your intention to build the equivalent hash? Or do you really want the string format? This program uses Text::CSV to build the hash from the file and then dumps it using Data::Dump so that you have the string format as well.
use strict;
use warnings;
use Text::CSV;
use Data::Dump 'dump';
my $csv = Text::CSV->new({ sep_char => ':' });
open my $fh, '<', 'amino.txt' or die $!;
my %amino_acids;
while (my $data= $csv->getline($fh)) {
$amino_acids{$data->[2]} = [
$data->[0],
$data->[1],
[ $data->[4] =~ /[A-Z]+/g ]
];
}
print '$amino_acids = ', dump \%amino_acids;
output
$amino_acids = {
F => ["Phenylalanine", "Phe", ["TTT", "TTC"]],
M => ["Methionine", "Met", ["ATG"]],
P => ["Proline", "Pro", ["CCT", "CCC", "CCA", "CCG"]],
}
Update
If you really don't want to install modules (it is a very straightforward process and makes the code much more concise and reliable) then this does what you need.
use strict;
use warnings;
open my $fh, '<', 'amino.txt' or die $!;
print "my %amino_acids = (\n";
while (<$fh>) {
chomp;
my #data = /[^:"]+/g;
my #codons = $data[4] =~ /[A-Z]+/g;
printf qq{ %s => ["%s", "%s", [%s]],\n},
#data[2,0,1],
join ', ', map qq{"$_"}, #codons;
}
print ")\n";
output
my %amino_acids = (
M => ["Methionine", "Met", ["ATG"]],
F => ["Phenylalanine", "Phe", ["TTT", "TTC"]],
P => ["Proline", "Pro", ["CCT", "CCC", "CCA", "CCG"]],
)
Assuming you actually want valid perl as the output, this will do it:
open(my $IN, "<input.txt") or die $!;
while(<$IN>){
chomp;
my #tmp = split(':',$_);
if(#tmp != 5){
# error on this line
next;
}
my $group = join('","',split(/,\s*/,$tmp[4]));
print "\$amino_acids{$tmp[2]} = [$tmp[0],$tmp[1],[$group]];\n";
}
close $IN;
Using your sample lines, the output is:
$amino_acids{"M"} = ["Methionine","Met",["ATG"]];
$amino_acids{"F"} = ["Phenylalanine","Phe",["TTT","TTC"]];
$amino_acids{"P"} = ["Proline","Pro",["CCT","CCC","CCA","CCG"]];
#Borodin Thank you very much for your answer, actually I don't have to use Text::csv or Data::dump.I have to open a file and build the equivalent hash from the file.I am trying to do without using both, hopefully it will help.Thanks again!!!
Perl has no special method to print hashes. What you should probably do is create a hash when reading the file:
while (<FILE>) {
my #line = split ':'; # split the line into an array
$amino_acids{$line[0]} = \#line[1..-1]; # take elements 1..end
}
And then print out the hash one entry at a time:
foreach (keys %amino_acids) {
print "$_ => [", (join ",", #$amino_acids{$_}), "]\n";
}
Note that I didn't compile this, so it may need a small amount of work to get it done.