-
Notifications
You must be signed in to change notification settings - Fork 127
/
Copy pathsort.go
228 lines (203 loc) · 5.94 KB
/
sort.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package table
import (
"sort"
"strconv"
"strings"
)
// 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
// IgnoreCase makes sorting case-insensitive
IgnoreCase bool
}
// SortMode defines How to sort.
type SortMode int
const (
// Asc sorts the column in Ascending order alphabetically.
Asc SortMode = iota
// AscAlphaNumeric sorts the column in Ascending order alphabetically and
// then numerically.
AscAlphaNumeric
// AscNumeric sorts the column in Ascending order numerically.
AscNumeric
// AscNumericAlpha sorts the column in Ascending order numerically and
// then alphabetically.
AscNumericAlpha
// Dsc sorts the column in Descending order alphabetically.
Dsc
// DscAlphaNumeric sorts the column in Descending order alphabetically and
// then numerically.
DscAlphaNumeric
// DscNumeric sorts the column in Descending order numerically.
DscNumeric
// DscNumericAlpha sorts the column in Descending order numerically and
// then alphabetically.
DscNumericAlpha
)
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,
IgnoreCase: col.IgnoreCase,
})
}
}
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 {
shouldContinue, returnValue := false, false
realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j]
for _, sortBy := range rs.sortBy {
// extract the values/cells from the rows for comparison
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], sortBy.Number-1
iVal, jVal := "", ""
if colIdx < len(rowI) {
iVal = rowI[colIdx]
}
if colIdx < len(rowJ) {
jVal = rowJ[colIdx]
}
// compare and choose whether to continue
shouldContinue, returnValue = less(iVal, jVal, sortBy)
if !shouldContinue {
break
}
}
return returnValue
}
func less(iVal string, jVal string, sb SortBy) (bool, bool) {
if iVal == jVal {
return true, false
}
switch sb.Mode {
case Asc, Dsc:
return lessAlphabetic(iVal, jVal, sb)
case AscNumeric, DscNumeric:
return lessNumeric(iVal, jVal, sb)
default: // AscAlphaNumeric, AscNumericAlpha, DscAlphaNumeric, DscNumericAlpha
return lessMixedMode(iVal, jVal, sb)
}
}
func lessAlphabetic(iVal string, jVal string, sb SortBy) (bool, bool) {
if sb.IgnoreCase {
iLow := strings.ToLower(iVal)
jLow := strings.ToLower(jVal)
// when two strings are case-insensitive identical, compare them casesensitive.
// That makes sure to get a consistent sorting
identical := iLow == jLow
switch sb.Mode {
case Asc, AscAlphaNumeric, AscNumericAlpha:
return identical, (identical && iVal < jVal) || iLow < jLow
default: // Dsc, DscAlphaNumeric, DscNumericAlpha
return identical, (identical && iVal > jVal) || iLow > jLow
}
}
switch sb.Mode {
case Asc, AscAlphaNumeric, AscNumericAlpha:
return false, iVal < jVal
default: // Dsc, DscAlphaNumeric, DscNumericAlpha
return false, iVal > jVal
}
}
func lessAlphaNumericI(sb SortBy) (bool, bool) {
// i == "abc"; j == 5
switch sb.Mode {
case AscAlphaNumeric, DscAlphaNumeric:
return false, true
default: // AscNumericAlpha, DscNumericAlpha
return false, false
}
}
func lessAlphaNumericJ(sb SortBy) (bool, bool) {
// i == 5; j == "abc"
switch sb.Mode {
case AscAlphaNumeric, DscAlphaNumeric:
return false, false
default: // AscNumericAlpha, DscNumericAlpha:
return false, true
}
}
func lessMixedMode(iVal string, jVal string, sb SortBy) (bool, bool) {
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
if iErr != nil && jErr != nil { // both are alphanumeric
return lessAlphabetic(iVal, jVal, sb)
}
if iErr != nil { // iVal is alphabetic, jVal is numeric
return lessAlphaNumericI(sb)
}
if jErr != nil { // iVal is numeric, jVal is alphabetic
return lessAlphaNumericJ(sb)
}
// both values numeric
return lessNumericVal(iNumVal, jNumVal, sb)
}
func lessNumeric(iVal string, jVal string, sb SortBy) (bool, bool) {
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
if iErr != nil || jErr != nil {
return false, false
}
return lessNumericVal(iNumVal, jNumVal, sb)
}
func lessNumericVal(iVal float64, jVal float64, sb SortBy) (bool, bool) {
if iVal == jVal {
return true, false
}
switch sb.Mode {
case AscNumeric, AscAlphaNumeric, AscNumericAlpha:
return false, iVal < jVal
default: // DscNumeric, DscAlphaNumeric, DscNumericAlpha
return false, iVal > jVal
}
}