135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
//
|
|
// sort.go
|
|
// Copyright (C) 2023 tiglog <me@tiglog.com>
|
|
//
|
|
// Distributed under terms of the MIT license.
|
|
//
|
|
|
|
package table
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
)
|
|
|
|
// SortBy defines What to sort (Column Name or Number), and How to sort (Mode).
|
|
type SortBy struct {
|
|
// Name is the name of the Column as it appears in the first Header row.
|
|
// If a Header is not provided, or the name is not found in the header, this
|
|
// will not work.
|
|
Name string
|
|
// Number is the Column # from left. When specified, it overrides the Name
|
|
// property. If you know the exact Column number, use this instead of Name.
|
|
Number int
|
|
|
|
// Mode tells the Writer how to Sort. Asc/Dsc/etc.
|
|
Mode SortMode
|
|
}
|
|
|
|
// SortMode defines How to sort.
|
|
type SortMode int
|
|
|
|
const (
|
|
// Asc sorts the column in Ascending order alphabetically.
|
|
Asc SortMode = iota
|
|
// AscNumeric sorts the column in Ascending order numerically.
|
|
AscNumeric
|
|
// Dsc sorts the column in Descending order alphabetically.
|
|
Dsc
|
|
// DscNumeric sorts the column in Descending order numerically.
|
|
DscNumeric
|
|
)
|
|
|
|
type rowsSorter struct {
|
|
rows []rowStr
|
|
sortBy []SortBy
|
|
sortedIndices []int
|
|
}
|
|
|
|
// getSortedRowIndices sorts and returns the row indices in Sorted order as
|
|
// directed by Table.sortBy which can be set using Table.SortBy(...)
|
|
func (t *Table) getSortedRowIndices() []int {
|
|
sortedIndices := make([]int, len(t.rows))
|
|
for idx := range t.rows {
|
|
sortedIndices[idx] = idx
|
|
}
|
|
|
|
if t.sortBy != nil && len(t.sortBy) > 0 {
|
|
sort.Sort(rowsSorter{
|
|
rows: t.rows,
|
|
sortBy: t.parseSortBy(t.sortBy),
|
|
sortedIndices: sortedIndices,
|
|
})
|
|
}
|
|
|
|
return sortedIndices
|
|
}
|
|
|
|
func (t *Table) parseSortBy(sortBy []SortBy) []SortBy {
|
|
var resSortBy []SortBy
|
|
for _, col := range sortBy {
|
|
colNum := 0
|
|
if col.Number > 0 && col.Number <= t.numColumns {
|
|
colNum = col.Number
|
|
} else if col.Name != "" && len(t.rowsHeader) > 0 {
|
|
for idx, colName := range t.rowsHeader[0] {
|
|
if col.Name == colName {
|
|
colNum = idx + 1
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if colNum > 0 {
|
|
resSortBy = append(resSortBy, SortBy{
|
|
Name: col.Name,
|
|
Number: colNum,
|
|
Mode: col.Mode,
|
|
})
|
|
}
|
|
}
|
|
return resSortBy
|
|
}
|
|
|
|
func (rs rowsSorter) Len() int {
|
|
return len(rs.rows)
|
|
}
|
|
|
|
func (rs rowsSorter) Swap(i, j int) {
|
|
rs.sortedIndices[i], rs.sortedIndices[j] = rs.sortedIndices[j], rs.sortedIndices[i]
|
|
}
|
|
|
|
func (rs rowsSorter) Less(i, j int) bool {
|
|
realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j]
|
|
for _, col := range rs.sortBy {
|
|
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], col.Number-1
|
|
if colIdx < len(rowI) && colIdx < len(rowJ) {
|
|
shouldContinue, returnValue := rs.lessColumns(rowI, rowJ, colIdx, col)
|
|
if !shouldContinue {
|
|
return returnValue
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (rs rowsSorter) lessColumns(rowI rowStr, rowJ rowStr, colIdx int, col SortBy) (bool, bool) {
|
|
if rowI[colIdx] == rowJ[colIdx] {
|
|
return true, false
|
|
} else if col.Mode == Asc {
|
|
return false, rowI[colIdx] < rowJ[colIdx]
|
|
} else if col.Mode == Dsc {
|
|
return false, rowI[colIdx] > rowJ[colIdx]
|
|
}
|
|
|
|
iVal, iErr := strconv.ParseFloat(rowI[colIdx], 64)
|
|
jVal, jErr := strconv.ParseFloat(rowJ[colIdx], 64)
|
|
if iErr == nil && jErr == nil {
|
|
if col.Mode == AscNumeric {
|
|
return false, iVal < jVal
|
|
} else if col.Mode == DscNumeric {
|
|
return false, jVal < iVal
|
|
}
|
|
}
|
|
return true, false
|
|
}
|