; docformat = 'rst'
;+
; Returns true if all rows of a matrix are unique.
;
; :categories:
; Linear algebra
;
; :author:
; Mats Löfdahl, Institute for Solar Physics, mats@astro.su.se.
;
; :params:
; M : in, type=array
; 2D matrix
;
; :keywords:
;
; duplicateindx : out, optional
;
; An array of row indices corresponding to duplicate rows.
; (Excluding the first occurence.)
;
; uniqindx : out, optional
;
; An array of row indices corresponding to unique rows.
;
; Muniq : out, optional
;
; A version of M with duplicate rows removed.
;
; OP : in, optional, type=string, default="'##'"
;
; The assumed matrix multiplication operator, "#" or "##". This
; specifies your matrix conventions, (row,column) or (column,row).
; The default is (column,row), the convention that goes with using
; the ## operator for matrix multiplication.
;
; COLUMN : in, optional, type=boolean, default=FALSE
;
; Set this if you want to operate on columns instead.
;
; :returns:
; Boolean, true if there are only unique rows, false otherwise.
;
; :examples:
; IDL> print, 'Matrix M: ', M
;
; IDL> if ~UniqRows(M,Muniq=Muniq) then print, 'Matrix M with duplicate rows removed: ', Muniq
;
; :history:
; 2012-03-23: Written by Mats Löfdahl, Institute for Solar Physics.
;
; 2012-11-21 : Switched to docformat 'rst'.
;
; 2013-01-25 : Added the OP keyword and logic for taking care of
; scalars and one-row matrices. Also added keyword
; duplicateindx.
;
; 2013-01-29 : Added the COLUMN keyword.
;-
function UniqRows, M $
, DUPLICATEINDX = duplicateindx $
, UNIQINDX = uniqindx $
, MUNIQ = Muniq $
, OP = op $
, COLUMN = column
if n_elements(op) eq 0 then op = '##' ; Default ##
if keyword_set(column) then begin
;; Easiest way to implement operation on columns is to switch the
;; assumed operator. So from here on, whenever it says "rows",
;; think "columns".
if op eq "#" then op = "##" else op = "#"
endif
if op eq '#' then begin
Nrows = (size(M, /dim))[0]
endif else begin
if size(M, /n_dim) lt 2 then begin
Nrows = 1
endif else begin
Nrows = (size(M, /dim))[1]
endelse
endelse
;; print, 'Number of rows:', Nrows
;; If M is a scalar or has just a single row, there are by
;; definition no duplicate rows. So it makes some sense to return
;; TRUE in this case.
if Nrows lt 2 then return, 1
;; We can return as soon as we find a duplicate row if we don't have
;; to return uniqindx or Muniq.
FastExit = ~(arg_present(uniqindx) or $
arg_present(Muniq) or $
arg_present(duplicateindx))
if ~FastExit then duplicateindx = lonarr(Nrows)
k = 0 ; Initialize the number of duplicates
for i = 0, Nrows-2 do begin ; Check all rows
for j = i+1, Nrows-1 do begin ; Compare with higher index rows
; print, i, j
if op eq '#' then begin
equalrows = array_equal(M[i, *], M[j, *])
endif else begin
equalrows = array_equal(M[*, i], M[*, j])
endelse
if equalrows then begin
if FastExit then begin
;; Early exit if possible
return, 0
endif else begin
duplicateindx[k] = j
k += 1 ; Increment the number of duplicates
endelse
endif
endfor
endfor
if k eq 0 then return, 1 ; All unique
;; If we got this far, there are duplicates and we need to return
;; either Muniq, duplicateindx, or uniqindx.
duplicateindx = duplicateindx[0:k-1]
uniqindx = SetDifference(indgen(Nrows), duplicateindx)
if arg_present(Muniq) then begin
if op eq '#' then begin
Muniq = M[uniqindx, *]
endif else begin
Muniq = M[*, uniqindx]
endelse
endif
return, 0 ; All not unique
end