Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Solver for the game "Set" matches three times

by skrapasor (Novice)
on Jul 15, 2008 at 05:30 UTC ( [id://697639]=perlquestion: print w/replies, xml ) Need Help??

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

wikipedia article on Set for convenience. It's a simple game, and I wrote a small script to find matches in a group of cards. My script finds all the combinations of 2 cards, finds the potential match for each combination, and looks to see if that match is in the group of cards. The problem is that each match will be found three times because it finds 2 cards which can match one card, and then it finds 2 other cards, which could both be part of the previous match, and would make the same match. Basically, my script thinks order matters but it doesnt, and making order unimportant requires *extra* work. Here is my script:
#!/usr/bin/perl use warnings; sub third { #this returns the the same number if the inputs #are the same, or if they are different it returns the #number that was not input (of the set {1,2,3}) my @numbers = @_; if ( $numbers[0] == $numbers[1] ) { return $numbers[0]; } else { if ( ($numbers[0] + $numbers[1]) == 3 ) { return 3; } elsif ( ( $numbers[0] + $numbers[1]) == 4 ) { return 2; } else { return 1; } } } sub find_match { #this returns the match of two cards in an array of the form #(a,b,c,d) of which a,b,c,d are each a number 1-3 to #represent shape, color, pattern, and number of shapes #on a card my @cards = @_; my @return; $return[0] = third($cards[0]->[0],$cards[1]->[0]); $return[1] = third($cards[0]->[1],$cards[1]->[1]); $return[2] = third($cards[0]->[2],$cards[1]->[2]); $return[3] = third($cards[0]->[3],$cards[1]->[3]); return @return; } sub combo { #this i stole from a perlmonks question to #find every combination of 2 cards in a group my $by = shift; return sub { () } if ! $by || $by =~ /\D/ || @_ < $by; my @list = @_; my @position = (0 .. $by - 2, $by - 2); my @stop = @list - $by .. $#list; my $end_pos = $#position; my $done = undef; return sub { return () if $done; my $cur = $end_pos; { if ( ++$position[ $cur ] > $stop[ $cur ] ) { $position[ --$cur ]++; redo if $position[ $cur ] > $stop[ $cur ]; my $new_pos = $position[ $cur ]; @position[ $cur .. $end_pos ] = $new_pos .. $new_pos + + $by; } } $done = 1 if $position[0] == $stop[0]; return @list[ @position ]; } } #example cards @card1=(2,2,1,1); @card2=(2,2,3,3); @card3=(2,2,2,2); @card4=(2,2,3,2); #example group of cards @cards=(\@card1, \@card2, \@card3, \@card4); #$iter finds a subset of length 2 of @cards my $iter = combo( 2, @cards); #this loop loops through every combo of two cards #and looks for a potential match #then checks to see if the match exists in the group of #@cards #the problem occurs because each match is found thrice while ( my @combo = $iter->() ) { my @match = find_match(@combo); my $first = $combo[0]; my $second = $combo[1]; my @altered_cards = @cards; foreach $card (@cards) { if(join("",@match)==join("",@$card)){ print "Match =", @match, "\n"; print "Card =", @$card, "\n"; print "Match!\n"; @$first, "\n", @$second, "\n", @match, "\n"; last; } } }
Any other coding suggestions would also be helpful. To be clear, I can't figure out how I would check to see if a match was already found. But have another question about dereferencing the arrays of references. When I tried @$combo[0] it didn't work, but when I put $combo[0] into $first, and then used @$first, it worked, which seemed to me like the same thing, so I don't understand that either. Edit: BTW, some of the things I did served no purpose (my @altered_cards = @cards)

Replies are listed 'Best First'.
Re: Solver for the game "Set" matches three times (octal)
by tye (Sage) on Jul 15, 2008 at 14:29 UTC

    Here is a cute trick that you might want to contemplate.

    If you make each card a 4-"digit" octal number, then three cards form a "set" if and only if ($a|$b|$c) equals ($a^$b^$c). You can use octal strings or octal numbers. I use octal strings below:

    #!/usr/bin/perl -w use strict; my %Desc= qw( 0001 One 0002 Two 0004 Three 0010 Red 0020 Blue 0040 Green 0100 Hollow 0200 Striped 0400 Solid 1000 Diamond 2000 Squiggle 4000 Oval ); my @deck; for my $a ( 1,2,4 ) { for my $b ( 1,2,4 ) { for my $c ( 1,2,4 ) { for my $d ( 1,2,4 ) { push @deck, "$a$b$c$d"; } } } } sub isSet { my( $a, $b, $c )= @_; return ($a|$b|$c) eq ($a^$b^$c); } sub showCard { my( $card )= @_; my @desc; for my $bit ( sort keys %Desc ) { if( $bit eq ( $bit & $card ) ) { push @desc, $Desc{$bit}; } } return "@desc"; } while( 1 ) { for my $n ( reverse @deck-2..@deck ) { my $r = rand $n; @deck[$n-1,$r]= @deck[$r,$n-1]; } if( isSet( @deck[-3..-1] ) ) { print showCard( $_ ), $/ for @deck[-3..-1]; exit; } }

    Update: Replaced nested maps with clearer, shorter for()s.

    - tye        

      This might sound stupid, but what does      return ($a|$b|$c) eq ($a^$b^$c); do?

        For each bit, ($a^$b^$c) returns

        • 1 if all three cards share the value represented by that bit (e.g. all three cards are green),
        • 0 if exactly two of three cards share the value represented by that bit (e.g. two of the three cards are green, and the one others isn't),
        • 1 if exactly one of three cards share the value represented by that bit (e.g. one of the three cards is green, and the two others aren't),
        • 0 if none of the three cards share the value represented by that bit (e.g. none of the cards are green).

        We want

        • 1 if all three cards share the value represented by that bit (e.g. all three cards are green),
        • 0 if exactly two of three cards share the value represented by that bit (e.g. two of the three cards are green, and the one others isn't),
        • 1 if exactly one of three cards share the value represented by that bit (e.g. one of the three cards is green, and the two others aren't),
        • 1 if none of the three cards share the value represented by that bit (e.g. none of the cards are green).

        ($a|$b|$c) eq serves that purpose and to "and" the results of every bit into a single boolean value.

Re: Solver for the game "Set" matches three times
by ChOas (Curate) on Jul 15, 2008 at 06:09 UTC
    I personally would try to keep track of the unique combinations of the cards using a hash, the keys being the card
    sorted contactenated shape/color/pattern values of the cards.

    so if I have 2 cards 2,3,1,2 and 1,2,3,1 first create two keys,
    2312 and 1231, sort them 1231,2312, concat them as a key ++$unique_combo{"12312312"};

    After been over all the cards the keys in my hash will be the unique combinations.

    Further, @{$combo->[0]} should just dereference your array

    GreetZ!,
      ChOas

    print "profeth still\n" if /bird|devil/;
Re: Solver for the game "Set" matches three times
by GrandFather (Saint) on Jul 15, 2008 at 06:43 UTC
Re: Solver for the game "Set" matches three times
by pc88mxer (Vicar) on Jul 15, 2008 at 16:04 UTC
    You can use PDL to simplify these calculations. As you have it, a card is just an array of four numbers mod 3. You are using 1, 2 and 3 but you can also use 0, 1 and 2.

    Then a set is just three cards X, Y, Z where X+Y+Z = (0,0,0,0) mod 3.

    use PDL; sub is_a_set { all (($_[0] + $_[1] + $_[2]) % 3) == 0; } sub third_card { (pdl(3,3,3,3)-$_[0]-$_[1]) % 3; } my $X = pdl(0,1,2,2); my $Y = pdl(0,2,2,1); my $Z = pdl(0,0,2,0); if (is_a_set($X, $Y, $Z)) { print "$X, $Y and $Z form a set\n"; } else { print "$X, $Y and $Z do not form a set\n"; } print "To form a set with $X and $Y you need: ", third_card($X, $Y), " +\n";
Re: Solver for the game "Set" matches three times
by jethro (Monsignor) on Jul 15, 2008 at 13:14 UTC
    Here is an easy filter: Print only those sets where the three cards are in ascending order. This presumes that the cards have some sort of order/number associated with them. So you would print cards 0 5 and 12, but not cards 5, 12 and 0 or cards 12, 5 and 0.

    If you don't have the card number available at that time you can construct one: If the card is (a,b,c,d), the number is a+b*3+c*9+d*27.

    Obviously you can do this filtering not only as late as when you print but also when you generate the combos and when you search for the third card (i.e. start with the search one card after the second of the two combo cards and expect to find no third card in some cases).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://697639]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (3)
As of 2024-04-25 19:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found