All,
When solving a problem with a limited toolbox, you often have to use other tools creatively or learn new ones. Recently, I have been solving old problems by intentionally limiting my toolbox to see what I learn. Here is my latest:

Generate all numbers 0 .. N randomly using as little memory as possible as fast as possible

The general problem here is that the more numbers you choose, the larger the number of possible misses becomes.
My ideas for a solution were numerous variations on:

I did learn a bit about bitstrings and bit operators, but not enough to be succesful at my task. The following code is my latest variation which is the most memory efficient, but not the fastest.

#!/usr/bin/perl use strict; use warnings; my $next = Gen_Iter( $ARGV[0] || 10 ); while ( defined( my $rand = $next->() ) ) { print "$rand\n"; } sub Gen_Iter { my $wrk = shift; # Create initial bitstring my $bytes = int ( $wrk / 8 ); my $pad = 8 - $wrk % 8; $pad += $pad == 8 ? -8 : 0; $bytes++ if $pad; my $str = "\0" x $bytes; vec($str, $_, 1) = 1 for $wrk .. $bytes * 8 - 1; # Establish initial boundaries my $beg = 0; my $end = $wrk; # return magic sub return sub { return () if ! $wrk--; my $num = Get_Random->($str, $beg, $end); $beg = Post_Zero( $str, $num ) if $num == $beg; $end = Prev_Zero( $str, $num ) if $num == $end; vec($str, $num, 1) = 1; return $num; }; } # Count leading 1s in a bitstring sub Leading { my $str = shift; $str =~ /^(\xFF*)/; my $skip = length $1; return -1 if $skip == length $str; my $byte = unpack("x${skip}A1", $str); my $extra = -1; while ( $extra++ < 6 ) { last if ! vec($byte, $extra, 1); } return $skip * 8 + $extra; } # Count trailing 1s in a bitstring sub Trailing { my $str = shift; my $len = length $str; $str =~ /(\xFF*)$/; my $match = length $1; return -1 if $len == $match; my $skip = $len - $match - 1; my $byte = unpack("x${skip}A1", $str); my $extra = 8; while ( $extra-- ) { last if ! vec($byte, $extra, 1); } return ($len - $skip - 1) * 8 + (7 - $extra); } # Find a 0 before current location (-1 if none) sub Prev_Zero { my ($str, $orig) = @_; my $cur = $orig; my $pos = $cur % 8; if ( $pos ) { while ( $pos-- ) { return $cur if ! vec($str, --$cur, 1); } } my $length = int($orig / 8); my $prev = substr($str, 0, $length); return -1 if ! $prev; my $gap = Trailing( $prev ); return -1 if $gap == -1; return $orig - (1 + $gap + $orig % 8); } # Find a 0 after current location (-1 if none) sub Post_Zero { my ($str, $orig) = @_; my $cur = $orig; my $pos = $cur % 8; if ( $pos < 7 ) { for ( $pos + 1 .. 7 ) { return $cur if ! vec($str, ++$cur, 1); } } my $byte = int($orig / 8); return -1 if $byte == length( $str ) - 1; my $tail = substr($str, $byte + 1); my $gap = Leading( $tail ); return -1 if $gap == -1; return $orig + $gap + ( 8 - $orig % 8 ); }; # Generate a random number within a specified range sub Gen_Random { return $_[0] + int( rand( $_[1] - $_[0] ) ); } # Select an available number randomly accounting for ghost sub Get_Random { my ($str, $beg, $end) = @_; my $guess = Gen_Random($beg, $end); my $dir = int rand 2; while ( $guess != -1 && vec($str, $guess, 1) ) { $guess = $dir ? Post_Zero( $str, $guess ) : Prev_Zero( $st +r, $guess ) ; } $guess = $dir ? $beg : $end if $guess == -1; return $guess; }

The idea was to keep shrinking the search space and to not keep guessing at random on a unsuccessful match.

It also has a bug or two that I didn't bother tracking down as I have given up. I was hoping I might learn a few things from how others solved the same problem using the same restrictions. It will also be interesting to see if people have the same general idea (bitstring), but are capable of making it work.

Cheers - L~R

Update:
This post is not a request for a code critique, but a solicitation for your ideas on how to solve the same problem with the same constraints.

While I do appreciate critiques, I have already admitted this is a failure, but not for some of the faulty assumptions others have made. Please take the time to understand the code if you are going to provide a critique.

I fully acknowledge that this approach will not produce a statistically uniform distribution. While not a primary goal, it would have been nice and is one of the reasons I said it was a failure and asked how else someone might try to solve this problem.


In reply to Generating 0 .. N Randomly and Efficiently by Limbic~Region

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.