Skip to content

Latest commit

 

History

History

sort-matrix-diagonally

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Sort the Matrix Diagonally

Sort the Matrix Diagonally

The Problem

Given a m * n matrix mat of integers, sort it diagonally in ascending order from the top-left to the bottom-right then return the sorted array.

3311
2212
1112
1111
1222
1233

Constraints:

m == mat.length n == mat[i].length 1 <= m, n <= 100 1 <= mat[i][j] <= 100

Implementation

The problem statement is actually a little limited in the information it gives. It does not specify exactly what diagonal order is. Yes, each diagonal should be sorted, but there’s multiple ways to do that.

So we get to choose. And the most straightforward way I can think of of doing that is to define an order where you start by filling in the top row and leftmost column in alternating order, moving inward as necessary, you should always end up with

In other words, if we are filling a 3x5 matrix from a list of numbers 0-14 we would end up with the following.

01356
2781011
49121314

We would therefore expect the following output

RowColumn
00
01
10
02
20
03
04
11
12
21
13
14
22
23
24
(define (values-get fn) (lambda args (fn args)))
(define (matrix-diagonal-cells row-count column-count)
  (sequence->stream
   (in-generator
    (let recurse ([add-in (vec 0 0)]
                  [dimensions (vec row-count column-count)])
      (define (yield-vec v) (yield (vec+ v add-in)))
      (define (get-dimension fn) (apply fn (vec->list dimensions)) )
      (define overflow-direction (if (> (get-dimension (values-get first)) (get-dimension (values-get second)))
                                     (vec 1 0)
                                     (vec 0 1)))
      (when (< 0 (apply min (vec->list dimensions)))
        (yield-vec (vec 0 0))
        (for ([i (range 1 (get-dimension min))])
          (yield-vec (vec 0 i))
          (yield-vec (vec i 0)))
        (for ([i (range (get-dimension min) (get-dimension max))])
          (yield-vec (vec* overflow-direction i)))
        (recurse (vec+ add-in (vec 1 1)) (vec- dimensions (vec 1 1))))))))
<<requires>>
<<matrix-diagonal-cells>>
(sequence->list (matrix-diagonal-cells 3 5))
(list (vec 0 0) (vec 0 1) (vec 1 0) (vec 0 2) (vec 2 0) (vec 0 3) (vec 0 4) (vec 1 1) (vec 1 2) (vec 2 1) (vec 1 3) (vec 1 4) (vec 2 2) (vec 2 3) (vec 2 4))

This looks good, so now we would only need to join and sort, and populate a matrix according to this sequence

So first lets sort all matrix values and also all positions

(define sorted-matrix-values (lambda~> (apply in-sequences _)
                                       sequence->list
                                       (sort <)))
<<requires>>
<<matrix-diagonal-cells>>
<<sorted-matrix-values>>
(sorted-matrix-values data)
'(1 1 1 1 1 1 2 2 2 2 3 3)

Yup that’s our sequence sorted. So now lets pair these together with our positions, sort the result by position, and chunk the remainder by their position so we get a nice pretty matrix.

(define (list->values lst) (apply values lst)) ;;no idea why this doens't already exist in racket/base

(define/match (is-position-before cell1 cell2)
  [((list (vec row1 col1) _) (list (vec row2 col2) _)) (or (< row1 row2)
                                                           (and (= row1 row2) (< col1 col2)))])
(define (diagonally-sorted-matrix data)
  (define dimensions (list (length data) (length (first data))))
  (define positions (call-with-values (thunk (list->values dimensions)) matrix-diagonal-cells))
  (~>> (sorted-matrix-values data)
       (map list positions) ;;zip with the above
       sequence->list
       (sort _ is-position-before)
       (map second)
       (chunk (second dimensions))))
<<requires>>
<<matrix-diagonal-cells>>
<<sorted-matrix-values>>
<<diagonally-sorted-matrix>>

(~>> (diagonally-sorted-matrix data)
     (map sequence->list)
     sequence->list
     display-table)
1,1,1,1
1,2,2,2
1,2,3,3

And that’s sorted!

Helpers

(require (except-in data/collection sequence->list)) ;;https://stackoverflow.com/a/62505165/5056
(require csv-writing)
(require racket/generator)
(require sfont/geometry)
(require threading)

Playground

(require csv-writing)
(display-table data)
(list (length (first data)) (length data))
3,3,1,1
2,2,1,2
1,1,1,2
'(4 3)
(require racket/match)
(require threading)
(require sfont/geometry)
(require data/collection)
(sequence->list (map + '(5 10 15) #(3 6 9)))
'(8 16 24)
013
256
478

In Python why not

Doing a related problem in python as a demo for a student

def addt(a, b):
    return (a[0]+b[0], a[1]+b[1])
def mult(factor, t):
    return (t[0]*factor, t[1]*factor)

def matrix_diagonal_cells(dimensions, add_in=(0,0)):
    if min(dimensions) <= 0:
        return
    yield add_in # the corner element
    # alternating cells from the edges as long as we can
    for i in range(1, min(dimensions)):
        yield addt((0, i), add_in)
        yield addt((i, 0), add_in)
        # whichever side is longest has a few more cells to go
    overflow_direction = (1, 0) if dimensions[0] > dimensions[1] else (0, 1)
    for i in range(min(dimensions), max(dimensions)):
        yield addt(mult(i, overflow_direction), add_in)
        # same thing but for the inner matrix
    yield from matrix_diagonal_cells(
        addt(dimensions, (-1,-1)),
        addt(add_in, (1,1))
    )
<<matrix_diagonal_cells>>
return list(matrix_diagonal_cells((3,5)))
00
01
10
02
20
03
04
11
12
21
13
14
22
23
24

Now we pair our numbers along with the tuples above, and sort by the tuples

def get_enumerated_sorted_positions(dimensions):
    row_factor = 10*dimensions[1]
    def position_sort_score(enumerated_pos):
        pos = enumerated_pos[1]
        return pos[0]*row_factor + pos[1]

    positions = matrix_diagonal_cells(dimensions)
    sorted_positions = sorted(enumerate(positions), key=position_sort_score)
    return sorted_positions
<<matrix_diagonal_cells>>
<<get_enumerated_sorted_positions>>
return list(get_enumerated_sorted_positions((3,5)))
0(0 0)
1(0 1)
3(0 2)
5(0 3)
6(0 4)
2(1 0)
7(1 1)
8(1 2)
10(1 3)
11(1 4)
4(2 0)
9(2 1)
12(2 2)
13(2 3)
14(2 4)

Now we just pluck out the first value and chunk....which we have to implement. Ugh

from itertools import groupby
from functools import partial

<<matrix_diagonal_cells>>
<<get_enumerated_sorted_positions>>

def chunk(n, coll):
    def getrow(t):
        return int(t[0]/n)
    for _, row in groupby(enumerate(coll), key=getrow):
        yield (v for _, v in row)

dimensions = (3, 5)
values = map(lambda t: t[0], get_enumerated_sorted_positions(dimensions))
matrix = chunk(dimensions[1], values)
return list(map(list, matrix))
01356
2781011
49121314