import java.util.Map;
import java.util.TreeMap;

public class Liang2DArrayShuffleTest {

	public static void main(String[] args) {
		// Show frequencies of letter orders for the text's naive shuffle algorithm:
		Map<String, Integer> naiveFreq = new TreeMap<>();
		for (int trial = 0; trial < 10000000; trial++) {
			char[][] matrix = {{'a', 'b'}, {'c', 'd'}};

			// "Naive" shuffle
			for (int i = 0; i < matrix.length; i++) {
				for (int j = 0; j < matrix[i].length; j++) {
					int i1 = (int)(Math.random() * matrix.length);
					int j1 = (int)(Math.random() * matrix[i].length);
					// Swap matrix[i][j] with matrix[i1][j1]
					char temp = matrix[i][j];
					matrix[i][j] = matrix[i1][j1];
					matrix[i1][j1] = temp;
				}
			}

			StringBuilder sb = new StringBuilder();
			for (char[] row : matrix)
				for (char ch : row)
					sb.append(ch);
			String order = sb.toString();
			naiveFreq.put(order, naiveFreq.containsKey(order) ? naiveFreq.get(order) + 1 : 1);
		}
		System.out.println("Naive shuffle order frequencies are non-uniform with predictable bias:");
		for (String order : naiveFreq.keySet())
			System.out.println(order + " " + naiveFreq.get(order));

		// My unbiased Knuth/Fisher-Yates shuffle generalization:
		Map<String, Integer> knuthFreq = new TreeMap<>();
		for (int trial = 0; trial < 10000000; trial++) {
			char[][] matrix = {{'a', 'b'}, {'c', 'd'}};

			// Knuth shuffle
			int rows = matrix.length;
			int cols = matrix[0].length; // assumes rectangular array
			for (int i = rows * cols - 1; i > 0; i--) {
				int j = (int)(Math.random() * (i + 1));
				int iRow = i / cols, iCol = i % cols;
				int jRow = j / cols, jCol = j % cols; 
				char temp = matrix[iRow][iCol];        
				matrix[iRow][iCol] = matrix[jRow][jCol];
				matrix[jRow][jCol] = temp;
			}

			StringBuilder sb = new StringBuilder();
			for (char[] row : matrix)
				for (char ch : row)
					sb.append(ch);
			String order = sb.toString();
			knuthFreq.put(order, knuthFreq.containsKey(order) ? knuthFreq.get(order) + 1 : 1);
		}
		System.out.println("Knuth order frequencies are uniform and unbiased:");
		for (String order : knuthFreq.keySet())
			System.out.println(order + " " + knuthFreq.get(order));

		// My unbiased Knuth/Fisher-Yates shuffle generalization for ragged arrays (without using ArrayList):
		Map<String, Integer> knuthFreq2 = new TreeMap<>();
		for (int trial = 0; trial < 10000000; trial++) {
			char[][] raggedArr = {{'a'}, {'b', 'c'}, {'d'}};

			// Copy ragged array to 1D array (by row)
			int size = 0;
			for (char[] row : raggedArr)
				size += row.length;
			char[] myList = new char[size];
			int idx = 0;
			for (char[] row : raggedArr)
				for (char ch : row)
					myList[idx++] = ch;
			// Knuth shuffle					
			for (int i = myList.length - 1; i > 0; i--) {
				int j = (int)(Math.random() * (i + 1));
				char temp = myList[i];        
				myList[i] = myList[j];
				myList[j] = temp;
			}
			// Copy shuffled 1D array back to ragged array
			idx = 0;
			for (int row = 0; row < raggedArr.length; row++)
				for (int col = 0; col < raggedArr[row].length; col++)
					raggedArr[row][col] = myList[idx++];

			StringBuilder sb = new StringBuilder();
			for (char[] row : raggedArr)
				for (char ch : row)
					sb.append(ch);
			String order = sb.toString();
			knuthFreq2.put(order, knuthFreq2.containsKey(order) ? knuthFreq2.get(order) + 1 : 1);
		}
		System.out.println("Knuth order frequencies are uniform and unbiased (ragged array):");
		for (String order : knuthFreq2.keySet())
			System.out.println(order + " " + knuthFreq2.get(order));

	}


}
