This kind of bugged me for a while so I had to come see if it was solved. Here is my idea. From scratch, not an application of any algorithm I am aware of. This would be a rather expensive brute force algorithm, but it should be fairly effective. It is assuming you are dealing with the realtively small data set you described (100 rows of 4 columns) and are working on modern computer with sufficient ram.
Overview: We use a recursive algorithm on a sorted list to disperse similar records to their maxiumum distance within similar records. After each call all records with the same parent are at their maximum distance. The top call includes all records. So it unsorts from the inside out.
Data structures:
newIndexes
is an array<integer>
. The index of the array is the existing index of the row. The value will be the new index, starts with -1
data
is a array<array<string>>
. The key is the index, the inner array is a string representation of the values in one row. Doesn't need to be a string if you have some way of grouping your data. The first array element is the one with the greatest weight.
Sort data
by order of weight. Sort it first by the column with greatest weight, within that by column with 2nd greatest weight, etc. The result is the inverse of what you want. Index sequentially.
Here is the algorythm (in psudo code).
// siblingCount: On first call is the number of rows in the table,
// on recursive calls it is the number of elements with the same parent
// index: the index of current row in `data` - starts 0
// depth: The element index - starts 0
void unsort(int siblingCount, int index, int depth)
{
int count = 1;
string hash = concatColumns(index, depth + 1);
while ((index + count < data.count) && (hash == concatColumns(index + count, depth + 1)))
{
count++;
}
if (depth < columnCount)
unsort(count, index, depth);
else if (index < data.count)
unsort(count, index + count, 0);
int spacing = siblingCount / count;
for (int i = 0; i < count; i++)
{
var offset = 0;
while ((newIndexes[index + i + offset] > -1) & (index + i + offset + 1 < newIndexes.count))
offset++;
if (newIndexes[index + i + offset] > -1) throw new Exception("Shouldn't happen.");
newIndexes[index + i + offset] = index + spacing * i;
}
}
string concatColumns(int index, int count) // returns count columns concatinated
{
// 1,1 = "1"
// 1,2 = "1, blue"
// 1,3 = "1, blue, apple"
return "1, blue, apple";
}
Then apply the newIndexes to the data to be unsorted.
Thoughts on approach: Didn't test this, but the storing of the newIndexes and resolving of conflicts may be problematic since first indexes are assigned based on least significant columns, so if there are a lot of conflicts then the greater significant columns may cluster. Might try applying offset as positive first, then negative. Or possibly do so sort of insertion in a linked list instead of an array.