<?xml version="1.0" encoding="windows-1252"?>
<node id="358837" title="crenz's scratchpad" created="2004-06-01 19:44:32" updated="2005-08-13 00:03:13">
<type id="182711">
scratchpad</type>
<author id="186684">
crenz</author>
<data>
<field name="doctext">
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=153046"&gt;Professional Employees and Works for Hire&lt;/a&gt;
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=147962"&gt;Server Status/ Thank you Pair.com&lt;/a&gt;
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=107771"&gt;Win32::OLE Type Library Browser&lt;/a&gt;


&lt;p&gt;from [no_slogan]:&lt;/p&gt;
&lt;code&gt;
# randomly choose subsets of items from a set, without replacement.
# items in the set are not equally probable.

use strict;
use warnings;

# recursively build a balanced binary search tree of weighted items.
# this is not terribly efficient, and could be improved.
sub build_tree {
  my ($weights) = @_;
  my @items = sort keys %$weights;
  return build_tree_rec($weights, @items);
} # build_tree
sub build_tree_rec {
  my ($weights, @items) = @_;
  return unless @items;
  my $mid = int(@items / 2);
  my $tree = { };
  $tree-&gt;{item}   = $items[$mid];
  $tree-&gt;{left}   = build_tree_rec($weights, @items[0..($mid-1)]);
  $tree-&gt;{right}  = build_tree_rec($weights, @items[($mid+1)..$#items]
+);
  $tree-&gt;{weight} = $weights-&gt;{ $tree-&gt;{item} };
  calc_weight($tree);
  return $tree;
} # build_tree_rec

# change the weight of an item in the tree.
# this is written non-recursively for speed.
sub change_weight {
  my ($item, $weight, $tree) = @_;
  my @path;
  while ($tree &amp;&amp; $item ne $tree-&gt;{item}) {
    push @path, $tree;
    if ($item lt $tree-&gt;{item}) {
      $tree = $tree-&gt;{left};
    }
    else {
      $tree = $tree-&gt;{right};
    }
  }
  return unless $tree; # item not found
  $tree-&gt;{weight} = $weight;
  # recalculate weights as necessary
  calc_weight($tree);
  while (@path) {
    $tree = pop @path;
    calc_weight($tree);
  }
} # change_weight

# calculate the total weight of a tree. assumes that the weights
# of the left and right subtrees have already been calculated.
sub calc_weight {
  my ($tree) = @_;
  my $left   = $tree-&gt;{left};
  my $right  = $tree-&gt;{right};
  $tree-&gt;{total_weight}  = $tree-&gt;{weight};
  $tree-&gt;{total_weight} += $left-&gt;{total_weight}  if $left;
  $tree-&gt;{total_weight} += $right-&gt;{total_weight} if $right;
} # calc_weight

# randomly choose an item from the tree.
sub choose_item {
  my ($tree) = @_;
  my $val = rand($tree-&gt;{total_weight});
  while ($val &gt;= $tree-&gt;{weight}) {
    $val -= $tree-&gt;{weight};
    my $left = $tree-&gt;{left};
    if ($left &amp;&amp; $val &lt; $left-&gt;{total_weight}) {
      $tree = $left;
    }
    else {
      $val -= $left-&gt;{total_weight} if $left;
      my $right = $tree-&gt;{right};
      if ($right &amp;&amp; $val &lt; $right-&gt;{total_weight}) {
        $tree = $right;
      }
      else {
        # if we get here, there has been some funny round-off in $val.
        # this should be very unlikely. don't search any further
        # or we might move into a branch with zero weight.
        last;
      }
    }
  } # while $val
  return $tree-&gt;{item};
} # choose_item

# randomly choose several different items from the tree.
# you need to pass in the weights of the items, so they can be
# restored at the end. this is inconvenient, but could be fixed.
# this runs in O(k*log n) time, where there are n items
# in the tree and k are being chosen.
sub choose_items {
  my ($num, $weights, $tree) = @_;
  my @items;
  for (1 .. $num) {
    my $item = choose_item($tree);
    push @items, $item;
    change_weight($item, 0, $tree); # don't pick this item again!
  }
  foreach my $item (@items) {
    change_weight($item, $weights-&gt;{$item}, $tree);
  }
  return @items;
} # choose_items

# exercise the trees a bit.

my %weights = (
    a =&gt; 1,
    b =&gt; 1,
    c =&gt; 1.5,
    d =&gt; .5,
);
my $tree = build_tree(\%weights);

for (1..20) {
  my @items = choose_items(2, \%weights, $tree);
  print "@items\n";
}
&lt;/code&gt;
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=242974"&gt;Fun with Hook::LexWrap and code instrumentation&lt;/a&gt;
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=158482"&gt;Rolling a biased die&lt;/a&gt;
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=242995"&gt;Re: Re: Perl program for updating code parts from web ?&lt;/a&gt;
&lt;br /&gt;&lt;a HREF="/index.pl?node_id=222465"&gt;TinyPerl for Win32&lt;/a&gt;

&lt;P&gt;[id://72241] pmxml stuff&lt;/p&gt;

http://www.perlmonks.org/index.pl?node_id=258312 -- loc counter


http://www.perlmonks.org/?node_id=444844 -- untaint</field>
</data>
</node>
