7

I'm looking for some pointers here as I don't quite know where to start researching this one.

I have a 2D matrix with 0 or 1 in each cell, such as:

  1 2 3 4
A 0 1 1 0
B 1 1 1 0
C 0 1 0 0
D 1 1 0 0

And I'd like to sort it so it is as "upper triangular" as possible, like so:

  4 3 1 2
B 0 1 1 1
A 0 1 0 1
D 0 0 1 1
C 0 0 0 1

The rows and columns must remain intact, i.e. elements can't be moved individually and can only be swapped "whole".

I understand that there'll probably be pathological cases where a matrix has multiple possible sorted results (i.e. same shape, but differ in the identity of the "original" rows/columns.)

So, can anyone suggest where I might find some starting points for this? An existing library/algorithm would be great, but I'll settle for knowing the name of the problem I'm trying to solve!

I doubt it's a linear algebra problem as such, and maybe there's some kind of image processing technique that's applicable.

Any other ideas aside, my initial guess is just to write a simple insertion sort on the rows, then the columns and iterate that until it stabilises (and hope that detecting the pathological cases isn't too hard.)

More details: Some more information on what I'm trying to do may help clarify. Each row represents a competitor, each column represents a challenge. Each 1 or 0 represents "success" for the competitor on a particular challenge.

By sorting the matrix so all 1s are in the top-right, I hope to then provide a ranking of the intrinsic difficulty of each challenge and a ranking of the competitors (which will take into account the difficulty of the challenges they succeeded at, not just the number of successes.)

Note on accepted answer: I've accepted Simulated Annealing as "the answer" with the caveat that this question doesn't have a right answer. It seems like a good approach, though I haven't actually managed to come up with a scoring function that works for my problem.

Tom
  • 42,844
  • 35
  • 95
  • 101
  • 1
    Questions: (1) Note that there is nothing you can do with a matrix of all 1s: are you fine with that? (2) Once there are no zeros below the diagonal, do you care about where the 1s are above the diagonal? (3) Is minimizing the number of 1s below the diagonal a good enough criterion? How about simply minimizing the number of rows that have (at least) a 1 below the diagonal? – ShreevatsaR Nov 19 '09 at 02:32
  • Answer 1) Yeah, all-zeros or all-ones are never going to occur, and if they did, they would by definition all be considered equivalent, so sorting them into some other permutation wouldn't be a problem. – Tom Nov 19 '09 at 02:36
  • Answer 2+3) Yes, I want the 1s as close to the top of each column as possible, i.e. as many as possible of the 1s in the top-right corner. Note that there may be 1s below the diagonal and 0s above it, it's not strictly a triangular matrix. – Tom Nov 19 '09 at 02:38
  • what size matrices will you need to sort? can you get away with a brute force method that simply tries every combination of rows and columns? this means n! squared combinations , so 5 rows would still be fairly doable but 6 rows/columns would be a half million combinations. – Peter Recore Nov 19 '09 at 02:40
  • the matrices will be very large, 100s x 100s at least, hopefully they can be solved incrementally as new rows/columns are added. – Tom Nov 19 '09 at 02:46

6 Answers6

5

An Algorithm based upon simulated annealing can handle this sort of thing without too much trouble. Not great if you have small matrices which most likely hae a fixed solution, but great if your matrices get to be larger and the problem becomes more difficult.

(However, it also fails your desire that insertions can be done incrementally.)

Preliminaries

  1. Devise a performance function that "scores" a matrix - matrices that are closer to your triangleness should get a better score than those that are less triangle-y.

  2. Devise a set of operations that are allowed on the matrix. Your description was a little ambiguous, but if you can swap rows then one op would be SwapRows(a, b). Another could be SwapCols(a, b).

The Annealing loop

I won't give a full exposition here, but the idea is simple. You perform random transformations on the matrix using your operations. You measure how much "better" the matrix is after the operation (using the performance function before and after the operation). Then you decide whether to commit that transformation. You repeat this process a lot.

Deciding whether to commit the transform is the fun part: you need to decide whether to perform that operation or not. Toward the end of the annealing process, you only accept transformations that improved the score of the matrix. But earlier on, in a more chaotic time, you allow transformations that don't improve the score. In the beginning, the algorithm is "hot" and anything goes. Eventually, the algorithm cools and only good transforms are allowed. If you linearly cool the algorithm, then the choice of whether to accept a transformation is:

public bool ShouldAccept(double cost, double temperature, Random random) {
    return Math.Exp(-cost / temperature) > random.NextDouble();
}

You should read the excellent information contained in Numerical Recipes for more information on this algorithm.

Long story short, you should learn some of these general purpose algorithms. Doing so will allow you to solve large classes of problems that are hard to solve analytically.

Scoring algorithm

This is probably the trickiest part. You will want to devise a scorer that guides the annealing process toward your goal. The scorer should be a continuous function that results in larger numbers as the matrix approaches the ideal solution.

How do you measure the "ideal solution" - triangleness? Here is a naive and easy scorer: For every point, you know whether it should be 1 or 0. Add +1 to the score if the matrix is right, -1 if it's wrong. Here's some code so I can be explicit (not tested! please review!)

int Score(Matrix m) {
    var score = 0;
    for (var r = 0; r < m.NumRows; r++) {
        for (var c = 0; c < m.NumCols; c++) {
            var val = m.At(r, c);
            var shouldBe = (c >= r) ? 1 : 0;
            if (val == shouldBe) {
                score++;
            }
            else {
                score--;
            }
        }
    }
    return score;
} 

With this scoring algorithm, a random field of 1s and 0s will give a score of 0. An "opposite" triangle will give the most negative score, and the correct solution will give the most positive score. Diffing two scores will give you the cost.

If this scorer doesn't work for you, then you will need to "tune" it until it produces the matrices you want.

This algorithm is based on the premise that tuning this scorer is much simpler than devising the optimal algorithm for sorting the matrix.

Frank Krueger
  • 69,552
  • 46
  • 163
  • 208
  • 3
    Yes, but these "general purpose algorithms" also usually suck wrt finding actually optimal solutions — they can often take a long time to converge, or get stuck in local minima. Can you *prove* anything about the results obtained by simulated annealing for this specific problem? – ShreevatsaR Nov 19 '09 at 02:26
  • This should work, albeit in a non-deterministic fashion. (unlike the other responses so far...) +1. Maybe you can suggest to introduce a "pinch" of analytic/heuristic trick to the mix, for example by identifying rows or column which only have zeros, and place these at the bottom / left respectively and make these unmoveable w/r to the allowed transformations. – mjv Nov 19 '09 at 02:30
  • The sticking point (at least where I'm stuck) is the score function. Given that, simulated annealing could definitely work, but how do you know how "triangle-y" a matrix is? And yes, the two allowable operations are SwapRow(a,b) and SwapCol(a,b). – Tom Nov 19 '09 at 02:34
  • @ShreevatsaR, Calm down my friend. I find that simulated annealing has avoided "really bad" local minima whenever I've used it, but of course it isn't perfect. Any search algorithm with a realistic implementation is going to fall for some traps. These algorithms work best with *large* data sets, I said that in the first paragraph. Have I proven anything? No! I merely suggested a powerful algorithm. – Frank Krueger Nov 19 '09 at 02:43
  • @mjv Good suggestions - but I would hate to add heuristics to such a beautifully naive algorithm. :-) – Frank Krueger Nov 19 '09 at 02:54
  • But why immediately reach for an approximate algorithm with no guarantees, without first trying to either find an optimal algorithm or establish that the problem is hard? (One possible answer is that you don't want to spend much time on it, but…) :-) – ShreevatsaR Nov 19 '09 at 02:55
  • The Score() function looks like a good start, thanks Frank. However, I may not have been clear enough about this, the matrix doesn't have to be strictly triangular, there could be more 1s than 0s or vice versa, and the eventual line dividing them is not likely to be straight, but some kind of monotonic curve... – Tom Nov 19 '09 at 02:58
  • @Tom, but even this simple scoring algorithm will handle the case of an uneven numbers of ones and zeros. If you want, add extra weight to correct answer that are near the upper right. The idea here is that you implement annealing as a generic function, then play around with the scorer until it creates matrices that you want. But I think you'd be surprised at just how nicely even the dumbest scorers can perform. – Frank Krueger Nov 19 '09 at 03:03
  • @ShreevatsaR, Because I don't get paid to work on Tom's problem. I felt like writing a few paragraphs on my favorite algorithm - one that I knew would handle this problem just fine, so I wrote this answer. PLEASE, feel free to analyze the problem and propose an optimal solution. – Frank Krueger Nov 19 '09 at 03:03
  • @Frank: you're right, the score is relative, so the solution with the "most positive score" could still have a negative score. – Tom Nov 19 '09 at 03:10
  • Sorry if that came off as rude: I have enormous, if grudging, respect for general-purpose solutions which work well-enough often-enough in practice; it's just that using a general-purpose hammer always feels against the spirit of the challenge. :-) Of course, the careful analysis might turn up only an NP-completeness proof, which would be utterly useless, but still — since I don't actually need to solve the problem in real life — more interesting. ;-) Anyway, simulated annealing is among the best of the general-purpose solutions, so +1. I just wish the first line didn't say "without trouble". – ShreevatsaR Nov 19 '09 at 03:16
  • @ShreevatsaR, Conceded. I added "too much" in the first line. Thanks for keeping me on my toes. :-) – Frank Krueger Nov 19 '09 at 03:36
2

Look for a 1987 paper by Anna Lubiw on "Doubly Lexical Orderings of Matrices".

There is a citation below. The ordering is not identical to what you are looking for, but is pretty close. If nothing else, you should be able to get a pretty good idea from there.

http://dl.acm.org/citation.cfm?id=33385

  • 1
    Please cite the information in your question as well. If the ACM link changes (happens from time to time, I'm also a member), your answer loses all context. – Tim Post Aug 21 '12 at 12:52
2

I came up with the below algorithm, and it seems to work correctly.

Phase 1: move rows with most 1s up and columns with most 1s right.

  1. First the rows. Sort the rows by counting their 1s. We don't care if 2 rows have the same number of 1s.
  2. Now the columns. Sort the cols by counting their 1s. We don't care if 2 cols have the same number of 1s.

Phase 2: repeat phase 1 but with extra criterions, so that we satisfy the triangular matrix morph.
Criterion for rows: if 2 rows have the same number of 1s, we move up the row that begin with fewer 0s.

Criterion for cols: if 2 cols have the same number of 1s, we move right the col that has fewer 0s at the bottom.


Example:

Phase 1

  1 2 3 4                     1 2 3 4                   4 1 3 2
A 0 1 1 0                   B 1 1 1 0                 B 0 1 1 1
B 1 1 1 0  - sort rows->    A 0 1 1 0  - sort cols->  A 0 0 1 1
C 0 1 0 0                   D 1 1 0 0                 D 0 1 0 1
D 1 1 0 0                   C 0 1 0 0                 C 0 0 0 1

Phase 2

  4 1 3 2                     4 1 3 2
B 0 1 1 1                   B 0 1 1 1
A 0 0 1 1  - sort rows->    D 0 1 0 1  - sort cols-> "completed"
D 0 1 0 1                   A 0 0 1 1
C 0 0 0 1                   C 0 0 0 1

Edit: it turns out that my algorithm doesn't give proper triangular matrices always.
For example:

Phase 1

   1 2 3 4                    1 2 3 4                
A  1 0 0 0                  B 0 1 1 1                
B  0 1 1 1 - sort rows->    C 0 0 1 1  - sort cols-> "completed"
C  0 0 1 1                  A 1 0 0 0                
D  0 0 0 1                  D 0 0 0 1                

Phase 2

   1 2 3 4                    1 2 3 4                   2 1 3 4
B  0 1 1 1                  B 0 1 1 1                 B 1 0 1 1
C  0 0 1 1 - sort rows->    C 0 0 1 1  - sort cols->  C 0 0 1 1
A  1 0 0 0                  A 1 0 0 0                 A 0 1 0 0
D  0 0 0 1                  D 0 0 0 1                 D 0 0 0 1
                           (no change)

(*) Perhaps a phase 3 will increase the good results. In that phase we place the rows that start with fewer 0s in the top.

Nick Dandoulakis
  • 42,588
  • 16
  • 104
  • 136
  • Here's an input where it doesn't work: consider `[1 0 0 0], [0 1 1 1], [0 0 1 1], [0 0 0 1]` (which is already upper-triangular). Using your algorithm on it arrives at `[1 0 1 1], [0 0 1 1], [0 0 0 1], [0 1 0 0]`, which is not. (And if the initial form is not given and you start with the latter matrix, then the algorithm does not change anything: it does not find the upper-triangular form.) – ShreevatsaR Nov 19 '09 at 22:42
  • Or a simpler 3x3 example: `[1 0 0], [0 1 1], [0 0 1]`. – ShreevatsaR Nov 19 '09 at 22:45
  • @ShreevatsaR, you are right, thanks. It doesn't produce always a triangular matrix. However, it doesn't give the matrices you said. Maybe you didn't apply the steps correctly. Check my edit. As for the `[1 0 0], [0 1 1], [0 0 1]` it'll give `[1 0 1], [0 1 0], [0 0 1]`. – Nick Dandoulakis Nov 20 '09 at 05:59
0

Here's a starting point:

Convert each row from binary bits into a number

Sort the numbers in descending order.

Then convert each row back to binary.

jqa
  • 1,340
  • 6
  • 17
0

Basic algorithm:

  1. Determine the row sums and store values. Determine the column sums and store values.
  2. Sort the row sums in ascending order. Sort the column sums in ascending order.

Hopefully, you should have a matrix with as close to an upper-right triangular region as possible.

kafuchau
  • 5,573
  • 7
  • 33
  • 38
  • That sort of works, but wouldn't "finish" sorting the example I've given: row sums are A:2, B:3, C:1, D:2, col sums are 1:2, 2:4, 3:2, 4:0, so it's ambiguous what order rows A,D and cols 1,3 should go in. – Tom Nov 19 '09 at 02:28
  • If I'm understanding the rest of the problem... Maybe after you've done the first two steps, you can look at the new matrix and test rows and columns w/ the same equal sums to see which are more densely packed with 1's to the right/top (so lower row index and higher column index). – kafuchau Nov 19 '09 at 02:34
0

Treat rows as binary numbers, with the leftmost column as the most significant bit, and sort them in descending order, top to bottom

Treat the columns as binary numbers with the bottommost row as the most significant bit and sort them in ascending order, left to right.

Repeat until you reach a fixed point. Proof that the algorithm terminates left as an excercise for the reader.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Note, that this gives different results to sorting by columns first. And both variants converge only to a local optimum, if that is of importance. – Dunkelkoon May 03 '20 at 13:18