Day 15: .pick your game

Another college semester has ended, or is soon ending, for many of us in the United States. I feel it’s appropriate that this gift will involve some fun. The gift is the ability to .pick things.

.pick allows for picking random elements from a list. Perl 5 allowed doing so through this syntax:

my @dice = (1, 2, 3, 4, 5, 6);
my $index = int (rand() * scalar @dice);
print $dice[$index] . "\n"; 
> 5

Perl 6 allows simplifying this, while at the same time picking more than one element.

my @dice = 1..6;
say @dice.pick(2).join(" ");
> 3 4

With just a set of dice, it is already possible to have a role playing session with your friends. Now, let’s see how much attack I can do with 10 d6s…

my @dice = 1..6;
say @dice.pick(10).join(" ");
> 5 3 1 4 2 6

For those wondering, the above result is not a typo. .pick‘s behavior is actually consistent with its name. When you pick something out, you generally keep it out. If you want to put the item back in, allowing the same item to be drawn again, use the :replace adverb in the second parameter.

my @dice = 1..6;
say @dice.pick(10, :replace).join(" ");
> 4 1 5 6 4 3 3 5 1 1

Note to game masters: don’t invite me to your D&D games unless you need someone with terrible dice luck. ;)

There is no specific order the list items have to be in for .pick to work its magic. Take the values of monopoly money, for instance:

my @dice = <1 5 10 20 50 100 500>;
say @dice.pick(10, :replace).join(" ");
> 20 50 100 500 500 10 20 5 50 20

When dice aren’t available, a deck of cards is usually on hand. This version is very basic, but is meant to get ideas going.

use v6;
class Card
  has $.rank;
  has $.suit;

  multi method Str()
    return $.rank ~ $.suit;

my @deck;
for <A 2 3 4 5 6 7 8 9 T J Q K> -> $rank
  for <♥ ♣ ♦ ♠> -> $suit
    @deck.push($rank, :$suit));
# Shuffle the cards.
@deck .= pick(*);
say @deck.Str;
> Not outputting the results here.

What does the pick(*) do? Call that a sneak peak for another gift. For now, see if you can improve on the card code and make a deck class.

With that, I hope I have proven that Perl 6 is fun. It certainly gets a high mark from me. ✓

15 thoughts on “Day 15: .pick your game

  1. That is a fun example. Seems like Perl 6 has a large vocabulary. Kind of like a language … hey, wait a second … isn’t this where the linguists hang out?

  2. my $index = int (rand() * scalar @dice); That’s not fair to Perl 5. The single pick can be written as $dice[rand @dice].

    Now, a comparison with Perl 5 would be interesting for the example that follows, where multiple are picked. In Perl 5 you have to do some bookkeeping to avoid the same thing being picked twice.

  3. .pick() seem to pick one element at random, and all elements in the list do have the same probability of being picked. Is there any elegant way of assigning different elements different “weight” in the sense that “heavier” elements in the list have a higher probability of being picked? Elements of weight zero would be exempt from being picked at all (unless, perhaps, the list only contains zero weight elements).

    If a piece of code could be supplied to calculate that ‘weight’ for each element one could easily create code
    that picks an element from a list of lists. Then something like this can pick an element (warning, pseudo-perl6, I might have got a few syntactic details wrong…):

    @list_of_lists = [ [1,2,3],[4],[5,6,7],[8,9]];

    my $picked_element = @list_of_lists.pick(:replace,:weight_func -> $list {@$list.elems}).pick;

    Here is what happens: the first .pick() selects one of the four top level elements (which are lists themselves), but supplies a weight_function which for each top element which skews which of them is picked. The weight calculated is simply the number of elements in each top level list, so at the end of the day, each second level integer element has the same chance of being selected once the last pick is executed (this time without :replace, which means the pick actually removes the element from the nested list.).

    The nice thing with this is that slightly a more elaborate weight functions can return zero as the weight for
    certain sub-lists, to dynamically “hide” them from the selection process.

    1. The spec calls for a Bag.pick function which uses the (unsigned Int) value in the Bag to weight the pick (and returns the keys). However, it’s NYI in Rakudo, and I think it has been semi-seriously suggested that Bag be removed from the spec altogether…

      1. I gave this some additional thought…

        Bag’s does not really do what I want. When picking from a bag, elements existing in large numbers have a high probability of being selected (naturally) which is what I want but when picking them, you just decrease the number of elements of that kind by one. What I want is the weighted pick to have an adjusted probability while still having the item totally removed if picked without :replace in effect. Also, I suspect that a Bag won’t allow you to have a probability of zero for certain items you (for the moment) don’t want to be picked.

        That said, implementing a list with a (dynamically calculated) probability weight opens up a can of worms, implementation wise. Probably some sort of derived class (or role or whatever is the best practice) with a special purpose list is the best way to go.

        Pitfalls: for every single pick operation, one has to calculate the probability weight for every element before carrying out the pick, as well as the sum of all weights. Of course, one might have schemes of caching some of those calculations (if the weight function yields the same value for elements between calls). The latter might work well in some conditions, very bad in others. Whether or not this performance penalty is a problem depends on the situation.

        So, all in all, what I want is a nice feature, but the need it solves is far to specialized given the implementation complexity to warrant becoming part of the core language. (grumble, as I would have loved it..)

        I’d better get a Perl6 module written for it. Would happen any day, year or decade ;-).

  4. At a guess,
    @deck .= pick(*)
    is equivalent to
    @deck = @deck.pick(*)

    and pick(*) will pick cards until the deck is empty, e.g. * = size of @deck.

    Does List have a shuffle method? That would be cool!

  5. Combining this with the day 5 post on metaoperators, calculating a 10d6 roll is simply

    my $roll = [+] (1..6).pick(10, :replace);


    1. So what if I wanted to roll five six-sided dice and ignore the two lowest rolls, and sum the remainder? I mean, obviously you can do it with a sort and a couple of pops, but is there a clean one-line form?

  6. Shame on you for just looking at cubic dice! Dice can be 4-sided, 8-sided, 10-sided, 12-sided, 20-sided, 30-sided, 100-sided, and many, many different shapes. :D

  7. Your dice luck isn’t that bad. The expected value of d6 roll is 3.5 and for 10 rolls it’s 35. You scored 33 so it’s very close to the average. Don’t worry :)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.