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

I apologize in advance; this is both my first question and my first post.

It's not that I'm desperate, but I'm frustrated.

The problem is simple: Iterate across two lists, both in the format name:stock:shown (where name is a text name and stock and shown are both numerical). As you iterate, check for duplicates (and, upon finding them, increment the current entry so that it reflects current stock). If there's not a duplicate, add the whole name:stock:shown to the array as a new entry.

However, this being probably the first reasonably interesting program I've had to write in Perl, the problem's solution is nothing but simple for me.

What I have just attempted is nested foreach loops; the outer one iterates across the new inventory, and the inner iterates across current inventory. However, before that I tried it the other way around (outer foreach current inventory, inner foreach new inventory) and also tried it in while loops. The first two resulted in neverending programs, and the third didn't work. I cannot, apparently, visualize this thing in Perl-space.

I didn't want my first question to be this stupid, but it is. Thanks for your time, in advance.

chaoticset

  • Comment on Ludicrously Stupid Foreach Loop Question

Replies are listed 'Best First'.
Re: Ludicrously Stupid Foreach Loop Question
by pjf (Curate) on Oct 09, 2001 at 08:17 UTC
    G'day chaoticset,

    I'm assuming that we have lists that look like this:

    @oldstock = ("widgets:10:5", "wodgets:4:2", "spanners:5:3"); @newstock = ("screwdrivers:8:3", "hammers:5:1", "widgets:3:1");
    And we want to end up with a list like this:
    @totalstock = ("widgets:13:6", # Both widget entries joined "wodgets:4:2", "spanners:5:3", "screwdrivers:8:3", "hammers:5:1");
    I'm also assuming that the lists are un-ordered, and the ordering of the final list does not matter.

    This is a perfect opportunity to use hashes. We have an obvious key (the name), and values (stock and shown). Given that all we really want to do is join the lists together, we should just be able to iterate through them, use some hashes to build our results, and them print them. Here's an example using the arrays above:

    my %stock = (); my %shown = (); # Build our records foreach my $record (@newstock @oldstock) { my ($name, $stock, $shown) = split(/:/$record); $stock{$name} += $stock; $shown{$name} += $shown; } # Print the result. foreach my $name (keys %stock) { print join(":",$name,$stock{name},$shown{name}),"\n"; }
    You could do the same thing using a single hash and references, but I believe the above is more simple to understand.

    Hope that the above helps.

    Cheers,
    Paul

    Update: Updated to not check for the existance of entries in the hashes, but just += them anyway. I was doing an explicit test with exists() in order to avoid warnings, but as it happens using += with an undefined lvalue doesn't produce a warning anyway. Thanks to cLive ;-) for pointing this out.

      ah, oops, my point exactly (blush). Small point:
      if (exists($stock{$name})) { $stock{$name} += $stock; $shown{$name} += $shown; } else { $stock{$name} = $stock; $shown{$name} = $shown; }
      You don't need to check it exists:
      $stock{$name} += ($stock + $shown);
      will do.

      cLive ;-)

        G'day cLive,

        The code that you've given above is slightly different from what I used in my example. I was keeping stock and shown seperated, whereas your code is adding them together. I was uncertain which the original poster required, so I erred on the side of caution and went with the most flexible result.

        My original post mentions that checking for the existance of hash entries is intentional, in order to avoid warnings. However after checking with perl 5.6.1 and 5.005_03, += does not produce a warning if used with an undefined lvalue, so you're right, it is un-necessary even for avoiding warnings.

        Thanks for the feedback, I'll update my original post accordingly.

        Cheers,
        Paul

Re: Ludicrously Stupid Foreach Loop Question
by cLive ;-) (Prior) on Oct 09, 2001 at 08:06 UTC
    You need a hash:
    my @old_stock1 = ('products','here'); my @old_stock2 = ('products','here'); # define hash to store quantities my %new_stock; # iterate through old for (@old_stock1, @old_stock2) { # add one to count for this element # (creating it if it doesn't exist) $new_stock{$_}++; }
    Then do what you need to do with the hash, eg:
    for (keys %new_stock) { print "$_ = $new_stock{$_}\n"; }
    would list name and quantity of each item.

    cLive ;-)

    ps - Update - oops, just re-read the question. See below (but the principal still stands :) - pjf seems to have covered it.

Re: Ludicrously Stupid Foreach Loop Question
by Zaxo (Archbishop) on Oct 09, 2001 at 08:34 UTC

    One way is to make current inventory a hash of arrays. Assuming you get inventory on a filehandle which returns one record at a time:

    my %current_inventory; while (<RECORDS>) { my @ary = split ':'; $current_inventory{$ary[0]} = [@ary[1,2]]; } close(RECORDS) or die $!; # now inventory is in memory as a hash, can look things up directly
    Now we assume that a NEW_RECORDS handle carries the new stock. This looks almost the same:
    while (<NEW_RECORDS>) { my @ary = split ':'; $current_inventory{$ary[0]}->[0] += ary[1]; $current_inventory{$ary[0]}->[1] += $ary[2]]; } close(NEW_RECORDS) or die $!; # now update is done, save with proper file locking
    I'm not sure if the last line in the update loop matches the business logic you need.

    This approach will be faster than nested loops. It requires keeping the inventory in memory, but it would be a remarkably varied inventory to make that a problem.

    After Compline,
    Zaxo

Re: Ludicrously Stupid Foreach Loop Question
by blakem (Monsignor) on Oct 09, 2001 at 08:11 UTC
    If you want to loop through two arrays at the same time without modifying them, you could use something like:
    #!/usr/bin/perl -wT use strict; my @a = (1,2,3,4,5,6,7); # make a couple of arrays my @b = reverse @a; my $maxlen = $#a > $#b ? $#a : $#b; # find the last index of the larg +est one for my $index (0..$maxlen) { # loop through the indexes my $aval = $a[$index]; my $bval = $b[$index]; print "\$a[$index]=$aval \$b[$index]=$bval\n"; } =OUTPUT $a[0]=1 $b[0]=7 $a[1]=2 $b[1]=6 $a[2]=3 $b[2]=5 $a[3]=4 $b[3]=4 $a[4]=5 $b[4]=3 $a[5]=6 $b[5]=2 $a[6]=7 $b[6]=1

    -Blake

Already been thoroughly answered, but . . .
by Fletch (Bishop) on Oct 09, 2001 at 18:34 UTC

    perldoc -q intersection might be of marginal interest.

Re: Ludicrously Stupid Foreach Loop Question
by chaoticset (Chaplain) on Oct 10, 2001 at 00:18 UTC
    I apologize for being so vague in the original post. I didn't want to make it long, considering how simple it seems.

    I should have explained that $shown doesn't need to change; it's the number of products that we will appear to have, and if it's zero, then the amount we have will show as a default. I should've explained that.

    However, I do think I'm going to be able to get this put together, so thank you all again for your time. :)