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.

Like this:

LikeLoading...

Related

This entry was posted on December 15, 2009 at 12:00 am and is filed under 2009. You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.

.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…):

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.

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…

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 ;-).

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?