﻿module RandomSample;

import std.range;
import std.random;
import std.stdio;

struct RandomSample( R ) if ( isInputRange!R ) {
	R range;
	ElementType!( R )[] _front;
	bool empty;
	Random rnd;
	ulong pos;
	
	this( R r, size_t num ) {
		static if ( isForwardRange!R ) {
			range = r.save;
		} else {
			range = r;
		}
		_front = array( take( range, num ) );
		static if ( isForwardRange!R ) {
			popFrontN( range, num );
		}
		
		empty = false;
		rnd = Random( unpredictableSeed );
		pos = num;
	}
	
	@property ElementType!(R)[] front( ) {
		return _front.dup;
	}
	
	void popFront( ) {
		assert( !empty );
		if ( uniform( 0, pos, rnd ) < _front.length ) {
			_front[ uniform( 0, _front.length, rnd ) ] = range.front;
		}
		range.popFront( );
		pos++;
		empty = range.empty;
	}
}

RandomSample!R randomSampleRange( R )( R r, size_t num = 1 ) if ( isInputRange!R ) {
	return RandomSample!R( r, num );
}

T[] randomSampleImpl( T, R )( T[] result, R r ) {
	auto rnd = Random( unpredictableSeed );
	foreach ( i, e; r ) {
		if ( uniform( 0, result.length + i + 1, rnd ) < result.length ) {
			result[ uniform( 0, result.length, rnd ) ] = e;
		}
	}
	return result;
}

ElementType!( R )[] randomSample( R )( R r, size_t num = 1 ) if ( isInputRange!R && !isForwardRange!R ) {
	auto result = array( take( r, num ) );
	return randomSampleImpl( result, rr );
}

ElementType!( R )[] randomSample( R )( R r, size_t num = 1 ) if ( isForwardRange!R ) {
	auto rr = r.save;
	auto result = array( take( rr, num ) );
	popFrontN( rr, num );
	return randomSampleImpl( result, rr );
}

void main( ) {
	writeln( "Range sample:" );
	foreach ( e; randomSampleRange( "Sample text", 3 ) ) {
		writeln( e );
	}
	writeln( "Single sample:" );
	writeln( randomSample( "Sample text", 3 ) );
}