use strict; use warnings; sub RandFlat { #Return a rand () value with a flat distribution about the $mean +- $stdDev my ($mean, $stdDev) = @_; my $range = 2.0 * $stdDev; my $value = rand ($range); return $value + ($mean-$stdDev); } sub generate_set { my ($remainder, @constraints) = @_; my @results; my ($sum_of_minima, $sum_of_maxima); for my $c ( @constraints ) { $sum_of_minima += $c->{mid} - $c->{sd}; $sum_of_maxima += $c->{mid} + $c->{sd}; } # iterate through N-1 constraints in descending order my @descending = sort { $b->{mid} <=> $a->{mid} } @constraints; my $last_value = pop @descending; for my $c ( @descending ) { my $n = RandFlat( $c->{mid}, $c->{sd} ); # repeat if remainder outside sum of the remaining # minima and maxima contraints my $new_remainder = $remainder - $n; my $new_sum_of_minima = $sum_of_minima - ( $c->{mid} - $c->{sd} ); my $new_sum_of_maxima = $sum_of_maxima - ( $c->{mid} + $c->{sd} ); redo if ( $new_remainder < $new_sum_of_minima) || ( $new_remainder > $new_sum_of_maxima); # otherwise save number and update the remainder and constraints push @results, [ $c->{mid}, $c->{sd}, $n ]; $remainder = $new_remainder; $sum_of_minima = $new_sum_of_minima; $sum_of_maxima = $new_sum_of_maxima; } # the remainder must now satisfy the final constraint return @results, [ $last_value->{mid}, $last_value->{sd}, $remainder ]; } my $total = 100; my @constraints = ( {mid => 20, sd => 15}, {mid => 30, sd => 25}, {mid => 50, sd => 10}, ); for ( 1 .. 5 ) { for my $result ( generate_set($total, @constraints) ) { my ($mid, $sd, $value) = @$result; printf "%5.1f +-%5.1f: %5.1f", $mid, $sd, $value; print " - bad" if ($value < ($mid - $sd)) || ($value > ($mid + $sd)); print "\n"; } print "\n"; }