fseng has asked for the wisdom of the Perl Monks concerning the following question:

NEW Q::Now I want to refer to one of the values to do sth with it. E.G. Add a new transaction '-37' to member 'Feynman'. How can I do this? I tried sth like this
my $account_hash = $accounts->{78}; foreach my $transactions ($account_hash){ push @{ $account_hash{ transactions } }, '-42.37'; } print Dumper $account_hash{ transactions };
Hi guys, I have a data file like this
A,01,Newton,Issac,Physics, 5.6 B,144.56 B,1034.65 B,12.23 A,34,Einstein,Albert,Physics,12.22 B,2346.89 B,876.23 B,7656.81 B,745.21 B,25.63 B,89.56 B,7789.23
This is multiple record data with the definition of the record types being:
Record type A: Type,member number,Surname,Firstname,Job,Balance
Record type B: Type,TransactionAmount
Write a program to: Read the data from the file and populate a data structure that looks like this:
%accounts = ( ’78’ => { ’surname’ => ’Feynman’, ’firstname’ => ’Richard’, ’job’ => ’Physics’, ’balance’ => -17.34, ’transactions => [ 7412.36, 2589.35, 95.32, 12.85, 789.68 ], }, ’89’ => { ’surname’ => ’Merx’, ... },
I have writen code to read the data in diff arrays but I dont know how to use the 'member number' as the key and also add the transationAmount from 2nd array to the addtional field of the 1st array.
use strict; use warnings; use Data::Dumper; use Fatal qw/ open close / ; my $data = "accounts.txt"; open (DAT, $data) or die "\"$data\" not existed or can't be opened!\n" +; my @accounts; my @transactionamount; my @fields1 = ( 'Type', 'member number', 'Surname', 'Firstname', 'Job', 'Balance', ); my @fields2 = qw/Type TransactionAmount/; my $fields1; my $fields2; my $values; while (<DAT>) { chomp; my @values = split(/,/); my %entry = map { $fields1[$_] => $values[$_] } ( 0..$#fields1 ); #print Dumper %entry; if ($entry{"Type"} eq 'B'){ my %transactionamount = map { $fields2[$_] => $values[$_] } ( +0..$#fields2 ); push @transactionamount, \%transactionamount; } else { push @accounts, \%entry; } } print Dumper @transactionamount;

Replies are listed 'Best First'.
Re: read multiple record data and populate in a new data stucture
by jwkrahn (Abbot) on Jun 30, 2009 at 02:03 UTC

    This appears to do what you want:

    #!/usr/bin/perl use warnings; use strict; use Data::Dumper; my ( $number, %accounts ); while ( <DATA> ) { chomp; if ( /^A,/ ) { ( undef, $number, my @fields ) = split /,/, $_, -1; @{ $accounts{ $number } }{ qw/ surname firstname job balance / + } = @fields; } elsif ( /^B,/ ) { my $transaction = ( split /,/ )[ 1 ]; push @{ $accounts{ $number }{ transactions } }, $transaction; } } print Dumper \%accounts; __DATA__ A,01,Newton,Issac,Physics, 5.6 B,144.56 B,1034.65 B,12.23 A,34,Einstein,Albert,Physics,12.22 B,2346.89 B,876.23 B,7656.81 B,745.21 B,25.63 B,89.56 B,7789.23 A,78,Feynman,Richard,Physics,-17.34 B,7412.36 B,2589.35 B,95.32 B,12.85 B,789.68
      Thanks jwkrahn. You are a legend.But I dont really understand a few places. what does this mean
      ( undef, $number, my @fields ) = split /,/, $_, -1;
      why do u use undef? I understand u r spliting the records with "," and what is $_, and -1, what do they do here?

        new at Perl myself but I couldn't see that anyone had answered your query "what is $_?".

        $_ is one of Perl's built-in variables - it is the default variable (i.e. what's currently being input or matched) and frequently if not explicitly set it will be assumed.

        These pairs are all the same: (these examples are from perlvar (http://perldoc.perl.org/perlvar.html)

        $_ The default input and pattern-searching space. The following pairs are + equivalent: while (<>) {...} # equivalent only in while! while (defined($_ = <>)) {...} /^Subject:/ $_ =~ /^Subject:/ tr/a-z/A-Z/ $_ =~ tr/a-z/A-Z/ chomp chomp($_)

        It's a very important variable to get your head around so do ask in the Chatterbox if you're unsure. (at least that's what I would do).

        This reply wasn't addressed to me, but here is an explanation for you:

        undef as an lvalue is one way of "throwing away" a value from the right hand side of the "=". This is actually very similar to the way that 'C' does it with an input format statement.

        my @a = (1,2,3,4,5); (undef, my @b) = (@a); print "@b\n"; #prints: 2 3 4 5 (the 1 is gone)
        Perl has another way, a "list slice" and I prefer this in my Perl code to the 'C' way. For example to "get rid" of the "2":
        my @a = (1,2,3,4,5); my @b = @a[0,2..4]; print "@b\n"; #prints: 1 3 4 5
        As far as the third parameter to split being -1, I see no reason for this at all as this means "no limit", the default. There are reasons to "limit" the number of things returned from split(), but I don't see it here.
        my $text = "1 2 3 4 5 6"; my @tokens = split(/\s+/,$text,2); foreach (@tokens) { print "$_\n"; } #prints: 1 2 3 4 5 6
        The above code limits the split to 2 things.
Re: read multiple record data and populate in a new data stucture
by Marshall (Canon) on Jun 30, 2009 at 02:36 UTC
    Here was my "go at it". Very similar to jwkrahn's solution. He posted while I was doing some debug, but I thought that there is enough difference to post it. The key to this thing is the use of hash slice and also that you can create another hash key "on the fly" via a push. Have fun!!
    #!/usr/bin/perl -w use strict; use Data::Dumper; my %accounts; #a hash of hash my $member_number; while (<DATA>) { chomp; my ($type, @data) = split (/,/,$_); if ($type eq 'A') { $member_number = shift @data; @{$accounts{$member_number}}{qw/Surname Firstname Job Balance/} = @data; #extra line here so text won't wrap } elsif ($type eq 'B') { push @{$accounts{$member_number}{'transactions'}},@data; } else {die "database corrupted";} #optional.... } print Dumper \%accounts; #prints: #$VAR1 = { # '34' => { # 'Surname' => 'Einstein', # 'Job' => 'Physics', # 'Firstname' => 'Albert', # 'transactions' => [ # '2346.89', # '876.23', # '7656.81', # '745.21', # '25.63', # '89.56', # '7789.23' # ], # 'Balance' => '12.22' # }, # '01' => { # 'Surname' => 'Newton', # 'Job' => 'Physics', # 'Firstname' => 'Issac', # 'transactions' => [ # '144.56', # '1034.65', # '12.23' # ], # 'Balance' => ' 5.6' # } # }; __DATA__ A,01,Newton,Issac,Physics, 5.6 B,144.56 B,1034.65 B,12.23 A,34,Einstein,Albert,Physics,12.22 B,2346.89 B,876.23 B,7656.81 B,745.21 B,25.63 B,89.56 B,7789.23
      Thanks Marshall, a Q here.
      @{$accounts{$member_number}}{qw/Surname Firstname Job Balance/} = @data;
      Are you singing the keys as the member number here?
        Ha! I like your wit. You have the right temperament to be a great engineer!
Re: read multiple record data and populate in a new data stucture
by bichonfrise74 (Vicar) on Jul 01, 2009 at 02:17 UTC
    Here's a possible solution...
    #!/usr/bin/perl # http://perlmonks.org/index.pl?node_id=775872 use strict; use Data::Dumper; my %record; my @fields = qw( member_number Surname Firstname Job Balance ); my $type; while (<DATA>) { if (/^A/) { my @cols = split( "," ); $type = $cols[1]; for (0..$#fields) { $record{$type}->{$fields[$_]} = $cols[$_]; } } else { chomp; my $val = (split( "," ))[1]; push( @{ $record{$type}->{"transactions"} }, $val ); } } print Dumper \%record; __DATA__ A,01,Newton,Issac,Physics, 5.6 B,144.56 B,1034.65 B,12.23 A,34,Einstein,Albert,Physics,12.22 B,2346.89 B,876.23 B,7656.81 B,745.21 B,25.63 B,89.56 B,7789.23
Re: read multiple record data and populate in a new data stucture
by fseng (Novice) on Jul 01, 2009 at 00:02 UTC
    Thanks everyone. That was great help.Now I want to refer to one of the values to do sth with it. E.G.
    Add a new transaction '-37' to member 'Feynman'. How can I do this? I tried sth like this
    my $account_hash = $accounts->{78}; foreach my $transactions ($account_hash){ push @{ $account_hash{ transactions } }, '-42.37'; } print Dumper $account_hash{ transactions };