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

I'm using a book to self teach programming and Perl. I'm stuck on this end-of-chapter exercise and would like advice. Maybe even just rewording it would help. This is from the end of a chapter on Control Structures, Blocks, and Compound Statements and you'll see below the emphasis is on foreach loops.

Part of the guidance I seek is affirmation that I understand the question and then guidance to get me unstuck.

The exercise is this, copied exactly from the book:

Write a script that will print 10 random number cards from a deck.

a. The script will build a deck of 52 cards by using nested foreach loops.
b. The outer loop will iterate through a list consisting of cards for each suit: clubs, diamonds, hearts, spades. The inner loop will iterate through a list for each type of card within the suit: ace, 2 through 10, jack, queen, and king. A card of each suit will be assigned to an array.
c. The rand() function will be used to get a random card from the pack. There should be no duplicates in the 10 cards selected from the deck.


My first question is about statement "b". I understand TIMTOWTDI, but I comprehend this statement as 'the outer foreach loop should create the four suits and the inner foreach loop should create the cards within the suit'.
What I think I need to do is create a multi-dimensional array so that I can access the elements later. Does that sound right?

I can get the suits created and get cards assigned to the current array(suit). Where I'm getting stuck is creating @array0 for the clubs, @array1 for the diamonds, etc. within my inner foreach loop. I'm not totally sure how that will work with the next objective (c) and the rand() function but I'm taking this one step at a time.

After trying many things I remembered the ${var} method but alas that appears to be only for scalars.

I've seen examples on the www of using references, objects, and modules, none of which have been introduced to me yet in the book. At this point the book has covered scalars, arrays, hashes, operators, contol stuctures, loops, blocks. I'm not interested in having someone solve the actual problem. I want to learn this. Maybe another way to think about the problem would help. Here's what I have right now.

#!/usr/bin/perl use strict; use warnings; my @allsuits = ( "clubs", "diamonds", "hearts", "spades" ); my @suitcards = ( "ace", 2 .. 10, "jack", "queen", "king" ); my $card; my @cards; my $suit; my @suit; my @suits; my @allfours; foreach $suit (@allsuits) { push( @suits, $suit ); @suit = $suit; print "$suit\n"; foreach $card (@suitcards) { push( @cards, "$card" ); } print "@cards[0 .. 12]"; push( @allfours, $cards[3] ); print "\n\n"; } print "All the fours of a suit - @allfours\n";
Which produces:
clubs ace 2 3 4 5 6 7 8 9 10 jack queen king diamonds ace 2 3 4 5 6 7 8 9 10 jack queen king hearts ace 2 3 4 5 6 7 8 9 10 jack queen king spades ace 2 3 4 5 6 7 8 9 10 jack queen king All the fours of a suit - 4 4 4 4
And even though this output looks right, I feel the underlying method for generating the cards is wrong because if I move the
print "@cards[0 .. 12]";
statement up to the same level as the inner foreach loop I get a big ugly mess like this:
spades ace 2 3 4 5 6 7 8 9 10 jack queen kingace 2 3 4 5 6 7 8 9 10 jack quee +n kingace 2 3 4 5 6 7 8 9 10 jack queen kingace 2 3 4 5 6 7 8 9 10 ja +ck queen kingace 2 3 4 5 6 7 8 9 10 jack queen kingace 2 3 4 5 6 7 8 +9 10 jack queen kingace 2 3 4 5 6 7 8 9 10 jack queen kingace 2 3 4 5 + 6 7 8 9 10 jack queen kingace 2 3 4 5 6 7 8 9 10 jack queen kingace +2 3 4 5 6 7 8 9 10 jack queen kingace 2 3 4 5 6 7 8 9 10 jack queen k +ingace 2 3 4 5 6 7 8 9 10 jack queen kingace 2 3 4 5 6 7 8 9 10 jack +queen king
Thank-you for your consideration.

Replies are listed 'Best First'.
Re: Incrementing string arrays when used with foreach loops
by tilly (Archbishop) on Sep 09, 2008 at 00:59 UTC
    Not quite. Your homework asks for a single level array created in a double loop. That array should have elements like "2 clubs". For part 3 you probably want to use rand to locate a card together with splice to remove the found cards.

    I won't give you the code to do that since I like to strongly discourage doing other people's homework for them.

Re: Incrementing string arrays when used with foreach loops
by hangon (Deacon) on Sep 09, 2008 at 08:34 UTC

    One observation. People often tend to read too much into a problem and make it overly complicated. Sometimes its better to start with the desired result and work backwards.

    You have focused far too much on how to build a deck of cards, but in the end all you need is a simple list of 52 items, from which you randomly remove ten items and print them.

Re: Incrementing string arrays when used with foreach loops
by GrandFather (Saint) on Sep 09, 2008 at 01:20 UTC

    Update: What utter bollix! Didn't read the OP correctly.

    tilly's suggestion for data structure is the better direction for you to be heading. However, your immediate printing problem can be solved by adding a new line:

    print "@cards[0 .. 12]\n";

    Perl reduces RSI - it saves typing
Re: Incrementing string arrays when used with foreach loops
by JadeNB (Chaplain) on Sep 09, 2008 at 14:51 UTC
    Three questions that you might particularly want to answer:
    1. What is the following line doing?
      @suit = $suit;
    2. How many times, through the whole run of the outer foreach loop, does the single array @cards get modified?
    3. What is the purpose of @suits?
Re: Incrementing string arrays when used with foreach loops
by gctaylor1 (Hermit) on Sep 10, 2008 at 21:05 UTC
    First off, thanks for the responses, it helped me figure this out, eventually.

    I thought I'd update this thread with my final result in order to solicit comments. For my purposes, the script works but since I'm a beginner and don't have an instructor it would be good to get feedback on logic, style, most anything, really.

    One area in particular I'm curious about is the inner foreach loop where I have the list of if tests. It seems like there should be a better way to populate the arrays instead of doing an if statement for each array because I know during each iteration which card is coming. One way I thought about doing this was to increment the array name for each iteration but couldn't figure out how to do it. I also thought about switch statements or using a long if/elseif/else structure.

    foreach $suit (@allsuits) { my $i = 1; foreach my $card (@suitcards) { push( @arraysuit, "$card$suit" ); if ( $i == 1 ) { push( @aces, "$card$suit " ) } if ( $i == 2 ) { push( @twos, "$card$suit " ) } if ( $i == 3 ) { push( @threes, "$card$suit " ) } if ( $i == 4 ) { push( @fours, "$card$suit " ) } if ( $i == 5 ) { push( @fives, "$card$suit " ) } if ( $i == 6 ) { push( @sixes, "$card$suit " ) } if ( $i == 7 ) { push( @sevens, "$card$suit " ) } if ( $i == 8 ) { push( @eights, "$card$suit " ) } if ( $i == 9 ) { push( @nines, "$card$suit " ) } if ( $i == 10 ) { push( @tens, "$card$suit " ) } if ( $i == 11 ) { push( @jacks, "$card$suit " ) } if ( $i == 12 ) { push( @queens, "$card$suit " ) } if ( $i == 13 ) { push( @kings, "$card$suit " ) }
    Here's the whole thing, with output. I apologize if this is long, I can't tell if the <readmore> tags are working.
      I can't tell if the <readmore> tags are working
      Yes, the <readmore> tags are working.
      it would be good to get feedback on logic, style, most anything, really

      As others have strongly hinted, only a single array is necessary. What you have is overly complicated. Rather than answer your pointed questions, consider the following code, which is really tilly's approach:

      use strict; use warnings; my @cards; for my $suit (qw(_clb _dmd _hrt _spd)) { for my $rank (2 .. 10, qw(j q k a)) { push @cards, $rank . $suit; } } for (1 .. 10) { my $index = int rand scalar @cards; print splice(@cards, $index, 1), "\n"; }

      Although the instructions do not explicitly call for using splice, it really is the right tool for the job of preventing random duplicates. Each time through the final for loop, a random element is removed from the @cards array.

        That kinda backwards thinking for a deck of cards. The normal thought process involves shuffling the deck then drawing from the top.

        use strict; use warnings; use List::Util qw( shuffle ); my @cards; for my $suit (qw(_clb _dmd _hrt _spd)) { for my $rank (2 .. 10, qw(j q k a)) { push @cards, $rank . $suit; } } my @deck = shuffle @cards; for (1 .. 10) { print(shift(@deck), "\n"); }
      Wow. I see now I really was making this more complicated than it needed to be.

      I didn't even consider how much easier it would have been to remove the card from the deck and avoid the whole issue of looking for duplicates. Plus it's a more accurate representation of a real world scenario. I guess I needed to step back and evaluate the exercise instead of diving head first in to it. A valuable lesson.