code: package dotnet54.util;
  
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Random;
  
  import dotnet54.tscore.data.ArrayIndex;
  import dotnet54.tscore.data.Dataset;
  import dotnet54.tscore.data.MTSDataset;
  import gnu.trove.map.TIntObjectMap;
  
  public class Sampler {
  	
  	private static Random rand = new Random(System.nanoTime());
  
  //	static {
  //		rand = AppConfig.getRand();
  //	}
  
  //	public Sampler(Random rand) {
  //		this.rand = rand;
  //	}
  //
  //	public Sampler(long randSeed) {
  //		this.rand = new Random(randSeed);
  //	}
  
  	public static void setRand(Random rand){
  		Sampler.rand = rand;
  	}
  
  	public static void setRandSeed(long randSeed){
  		Sampler.rand = new Random(randSeed);
  	}
  	
  	
  // Reference
  // https://stackoverflow.com/questions/4702036/take-n-random-elements-from-a-liste?
  // utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
  	public static <E> List<E> pickNRandomElements(List<E> list, int n) {
  	    int length = list.size();
  
  	    if (length < n) return null;
  
  	    //We don't need to shuffle the whole list
  	    for (int i = length - 1; i >= length - n; --i)
  	    {
  	        Collections.swap(list, i , rand.nextInt(i + 1));
  	    }
  	    return list.subList(length - n, length);
  	}
  
  	
  	/**
  	 * An improved version (Durstenfeld) of the Fisher-Yates algorithm with O(n) time complexity
  	 * Permutes the given array
  	 * @param array array to be shuffled
  	 * reference
  	 * @url http://www.programming-algorithms.net/article/43676/Fisher-Yates-shuffle
  	 */
  	public static void fisherYatesKnuthShuffle(int[] array) {
  	    for (int i = array.length - 1; i > 0; i--) {
  	        int index = rand.nextInt(i);
  	        //swap
  	        int tmp = array[index];
  	        array[index] = array[i];
  	        array[i] = tmp;
  	    }
  	}
  
  	/**
  	 * An improved version (Durstenfeld) of the Fisher-Yates algorithm with O(n) time complexity
  	 * Permutes the given array
  	 * @param array array to be shuffled
  	 * reference
  	 * @url http://www.programming-algorithms.net/article/43676/Fisher-Yates-shuffle
  	 */
  	public static void fisherYatesKnuthShuffle(int[] array, Random rand) {
  		for (int i = array.length - 1; i > 0; i--) {
  			int index = rand.nextInt(i);
  			//swap
  			int tmp = array[index];
  			array[index] = array[i];
  			array[i] = tmp;
  		}
  	}
  
  	public static int[] sampleNRandomInts(int[] population, int n) {
  		fisherYatesKnuthShuffle(population);
  		int[] sample = new int[n];
  		for (int i = 0; i < n; i++) {
  			sample[i] = population[i];
  		}
  		return sample;
  	}
  
  	public static int[] sampleNRandomIntsFromRange(int min, int max, int n) {
  		int[] population = new int[max-min];
  		for (int i = min; i < max; i++) {
  			population[i] = i;
  		}
  		fisherYatesKnuthShuffle(population);
  		if (n > population.length){
  			n = population.length;
  		}
  		int[] sample = new int[n];
  		for (int i = 0; i < n; i++) {
  			sample[i] = population[i];
  		}
  		return sample;
  	}
  
  	public static int[] getIntsFromRange(int min, int max, int step) {
  		int[] range = new int[max-min];
  		for (int i = min; i < max; i += step) {
  			range[i] = i;
  		}
  		return range;
  	}
  
  	public static Dataset uniformSample(Dataset dataset, int sampleSize){
  		return uniformSample(dataset, sampleSize, rand);
  	}
  
  	public static Dataset uniformSample(Dataset dataset, int sampleSize, Random rand){
  		int datasetSize = dataset.size();
  		sampleSize = Math.min(sampleSize, datasetSize);
  		// TODO use copy constructor to copy other attribs of dataset
  		Dataset sample = new MTSDataset(sampleSize);
  
  		int[] indices = new int[datasetSize];
  		for (int i = 0; i < indices.length; i++) {
  			indices[i] = i;
  		}
  		// inplace array shuffle
  		Sampler.fisherYatesKnuthShuffle(indices, rand);
  		for (int i = 0; i < sampleSize; i++) {
  			sample.add(dataset.getSeries(indices[i]));
  		}
  		return sample;
  	}
  
  	public static ArrayIndex idxUniformSample(Dataset dataset, int sampleSize, Random rand){
  		int datasetSize = dataset.size();
  		sampleSize = Math.min(sampleSize, datasetSize);
  		int[] indices = new int[datasetSize];
  		for (int i = 0; i < indices.length; i++) {
  			indices[i] = i;
  		}
  		// inplace array shuffle
  		Sampler.fisherYatesKnuthShuffle(indices, rand);
  		ArrayIndex sampledIndices = new ArrayIndex(dataset, sampleSize);
  		for (int i = 0; i < sampleSize; i++) {
  			sampledIndices.indices[i] = indices[i];
  		}
  		return sampledIndices;
  	}
  
  	//TODO naive implementation, quick fix, check only dimension 0 to exclude
  	public static Dataset uniformSample(Dataset dataset, int n, double[][] exclude) throws Exception {
  		Dataset sample = Sampler.uniformSample(dataset, n);
  		int size = sample.size();
  		
  		for (int i = 0; i < size; i++) {
  			for (int j = 0; j < exclude.length; j++) {
  				if (sample.getSeries(i).data()[0] == exclude[j]) {
  					sample.remove(i);
  				}
  			}
  		}
  		
  		return sample;
  	}
  	
  	public static Dataset stratified_sample(TIntObjectMap<Dataset> data_per_class,
  			int n_per_class, boolean shuffle, double[][] exclude) throws Exception {
  		Dataset sample = new MTSDataset(data_per_class.size() * n_per_class);
  		Dataset class_sample;
  		int class_sample_size;
  		
  		for (int key : data_per_class.keys()) {
  			
  			if (exclude == null) {
  				class_sample = Sampler.uniformSample(data_per_class.get(key), n_per_class);
  			}else {
  				class_sample = Sampler.uniformSample(data_per_class.get(key), n_per_class, exclude);
  			}
  			class_sample_size = class_sample.size();
  
  			for (int i = 0; i < class_sample_size; i++) {
  				sample.add(class_sample.getSeries(i));
  			}
  		}
  		
  		if (shuffle) {
  			sample.shuffle(rand); //NOTE seed
  		}		
  		
  		return sample;
  	}
  	
  	public static Map<Integer, Dataset> stratified_sample_per_class(
  			TIntObjectMap<Dataset> data_per_class, int n_per_class,
  			boolean shuffle_each_class, double[][] exclude) throws Exception {
  		Map<Integer, Dataset> sample = new HashMap<Integer, Dataset> ();
  		Dataset class_sample;
  		
  		for (int key: data_per_class.keys()) {
  			if (exclude == null) {
  				class_sample = Sampler.uniformSample(data_per_class.get(key), n_per_class);
  			}else {
  				class_sample = Sampler.uniformSample(data_per_class.get(key), n_per_class, exclude);
  			}			
  			
  			if (shuffle_each_class) {
  				class_sample.shuffle(rand);
  			}	
  			
  			sample.put(key, class_sample);
  		}
  		
  		return sample;
  	}
  
  
  	public static Dataset stratifiedSample2(Dataset data, int sampleSize, Random rand)  {
  		Dataset sample = new MTSDataset(sampleSize);
  		TIntObjectMap<List<Integer>> classDataIdx = data.splitByClassIdx();
  		int[] classes = classDataIdx.keys();
  		int sampleClass;
  		int seriesIdx;
  		int nextClass = 0;
  
  		for (int i = 0; i < sampleSize;) {
  
  			sampleClass = classes[nextClass];
  			if (classDataIdx.get(sampleClass).size() > 0){
  				List<Integer> classIndices = classDataIdx.get(sampleClass);
  				seriesIdx = rand.nextInt(classIndices.size());
  				sample.add(data.getSeries(classIndices.get(seriesIdx)));
  				classIndices.remove(seriesIdx);
  				i++;
  			}else{
  				// do not increment i, retry for next iteration
  			}
  			nextClass++;
  			if (nextClass >= classDataIdx.size()){
  				nextClass = 0;
  			}
  		}
  
  		return sample;
  	}
  
  }
  