This is an interesting problem, which I think deserves more than one vote. I took the liberty of generalising it to n
by m
matrices, where n > 1
(in your example n = 4
) and 1 < m <= n
(in your example m = 3
). I also wanted to get an algorithm that couldn't go wrong and need to backtrack or start over.
The function
The function generatematrix
takes as arguments n
(the length of list = Range[n]
) and m
(the number of columns in the output matrix).
The basic concept is to create the output matrix row by row, while keeping track of how many more of each number are yet to be placed (in the imaginatively named yettoplace
variable) and how many available positions there are (in the availablepos
variable). Some numbers must be placed in all rows after a certain point to avoid running out of space.
generatematrix[n_, m_] := Module[{
yettoplace = ConstantArray[m - 1, n],
availablepos = ConstantArray[n - 1, n],
unfinished = Range[n],
mustplace, placeinrow},
SortBy[Table[
mustplace =
Select[unfinished,
availablepos[[#]] - yettoplace[[#]] == 0 && # != i &];
placeinrow = RandomSample[
Join[mustplace,
RandomSample[
Select[unfinished, # != i && ! MemberQ[mustplace, #] &],
m - 1 - Length[mustplace]]
], m - 1];
yettoplace[[placeinrow]]--;
unfinished = Select[unfinished, yettoplace[[#]] > 0 &];
availablepos[[unfinished]]--;
availablepos[[i]]++;
Join[{i}, placeinrow],
{i, RandomSample[Range[n], n]}], First]
]
This is clearly not optimized for speed and/or golfed, but its memory footprint seems pretty minimal for large lists.
Examples
To check that the returned matrices are valid (according to the criteria in the question) define
validate[matrix_] :=
With[{n = Length[matrix], m = Length[matrix[[1]]]},
Length /@ Split@Sort@Flatten@matrix ==
Length /@ DeleteDuplicates /@ matrix == ConstantArray[m, n]]
Then
mat = generatematrix[4, 3]
validate[mat]
(* {{1, 3, 2},
{2, 4, 1},
{3, 4, 1},
{4, 2, 3}}
True *)
or
mat = generatematrix[9, 9]
validate[mat]
(* {{1, 7, 4, 9, 8, 2, 5, 3, 6},
{2, 4, 6, 5, 9, 3, 7, 1, 8},
{3, 5, 6, 7, 8, 2, 9, 1, 4},
{4, 2, 6, 3, 1, 5, 9, 7, 8},
{5, 8, 9, 2, 3, 6, 4, 7, 1},
{6, 7, 4, 5, 1, 8, 3, 2, 9},
{7, 2, 6, 3, 8, 5, 9, 1, 4},
{8, 7, 4, 1, 5, 3, 2, 9, 6},
{9, 5, 3, 2, 6, 4, 1, 7, 8}}
True *)
To illustrate its robustness, try it on 10,000 random integer pairs {n, m}
.
nmlist = {#, RandomInteger[{2, #}]} & /@ RandomInteger[{2, 10}, 10000];
And @@ validate /@ (generatematrix @@@ nmlist)
(* True *)
(Of course, this doesn't prove it can't fail, but I've never seen it do so and suspect it can't.)
Statistics
The OP doesn't make specify much about the statistical properties of the algorithm, but out of curiosity I wanted to check the distributions of the numbers. Generating 10,000 4 by 3 matrices and plotting the occurrences of each number in each position (other than the first position of each row), it turns out that the distribution is pretty uniform (thanks, in large part, to randomizing the order that the rows are taken in).
matrixlist = Table[generatematrix[4, 3], 10000];
counts = Table[
Count[matrixlist[[;; , i, j]], #] & /@ Range[4],
{i, 4}, {j, 2, 3}];
GraphicsRow[
BarChart3D[counts[[;; , ;; , #]], ChartLayout -> "Grid",
ChartLabels -> {Range[4], {2, 3}}, ViewPoint -> {1, -1, 1},
PlotLabel -> ToString[#] <> "s by position"] & /@ Range[4],
ImageSize -> 750]
