Skip to content

Commit

Permalink
table: Sort supports AlphaNumeric modes
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t committed Mar 14, 2024
1 parent 516c996 commit ff55b4c
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 44 deletions.
99 changes: 87 additions & 12 deletions table/sort.go
Expand Up @@ -25,12 +25,24 @@ 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 {
Expand Down Expand Up @@ -94,41 +106,104 @@ func (rs rowsSorter) Swap(i, j int) {
func (rs rowsSorter) Less(i, j int) bool {
realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j]
for _, sortBy := range rs.sortBy {
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], sortBy.Number-1
// 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 := rs.lessColumns(iVal, jVal, sortBy)
shouldContinue, returnValue := less(iVal, jVal, sortBy.Mode)
if !shouldContinue {
return returnValue
}
}
return false
}

func (rs rowsSorter) lessColumns(iVal string, jVal string, sortBy SortBy) (bool, bool) {
func less(iVal string, jVal string, mode SortMode) (bool, bool) {
if iVal == jVal {
return true, false
} else if sortBy.Mode == Asc {
}

switch mode {
case Asc, Dsc:
return lessAlphabetic(iVal, jVal, mode)
case AscNumeric, DscNumeric:
return lessNumeric(iVal, jVal, mode)
default: // AscAlphaNumeric, AscNumericAlpha, DscAlphaNumeric, DscNumericAlpha
return lessMixedMode(iVal, jVal, mode)
}
}

func lessAlphabetic(iVal string, jVal string, mode SortMode) (bool, bool) {
switch mode {
case Asc, AscAlphaNumeric, AscNumericAlpha:
return false, iVal < jVal
} else if sortBy.Mode == Dsc {
default: // Dsc, DscAlphaNumeric, DscNumericAlpha
return false, iVal > jVal
}
}

func lessAlphaNumericI(mode SortMode) (bool, bool) {
// i == "abc"; j == 5
switch mode {
case AscAlphaNumeric, DscAlphaNumeric:
return false, true
default: // AscNumericAlpha, DscNumericAlpha
return false, false
}
}

func lessAlphaNumericJ(mode SortMode) (bool, bool) {
// i == 5; j == "abc"
switch mode {
case AscAlphaNumeric, DscAlphaNumeric:
return false, false
default: // AscNumericAlpha, DscNumericAlpha:
return false, true
}
}

func lessMixedMode(iVal string, jVal string, mode SortMode) (bool, bool) {
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
if iErr == nil && jErr == nil {
if sortBy.Mode == AscNumeric {
return false, iNumVal < jNumVal
} else if sortBy.Mode == DscNumeric {
return false, jNumVal < iNumVal
}
if iErr != nil && jErr != nil { // both are alphanumeric
return lessAlphabetic(iVal, jVal, mode)
}
if iErr != nil { // iVal is alphabetic, jVal is numeric
return lessAlphaNumericI(mode)
}
if jErr != nil { // iVal is numeric, jVal is alphabetic
return lessAlphaNumericJ(mode)
}
// both values numeric
return lessNumericVal(iNumVal, jNumVal, mode)
}

func lessNumeric(iVal string, jVal string, mode SortMode) (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, mode)
}

func lessNumericVal(iVal float64, jVal float64, mode SortMode) (bool, bool) {
if iVal == jVal {
return true, false
}

switch mode {
case AscNumeric, AscAlphaNumeric, AscNumericAlpha:
return false, iVal < jVal
default: // DscNumeric, DscAlphaNumeric, DscNumericAlpha
return false, iVal > jVal
}
return true, false
}
98 changes: 66 additions & 32 deletions table/sort_test.go
Expand Up @@ -6,6 +6,72 @@ import (
"github.com/stretchr/testify/assert"
)

func TestTable_sortRows_MissingCells(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000, 9},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000, 7},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 5, Mode: Asc}})
assert.Equal(t, []int{1, 3, 0, 2}, table.getSortedRowIndices())
}

func TestTable_sortRows_InvalidMode(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 2, Mode: AscNumeric}})
assert.Equal(t, []int{0, 1, 2, 3}, table.getSortedRowIndices())
}

func TestTable_sortRows_MixedMode(t *testing.T) {
table := Table{}
table.AppendHeader(Row{"#", "First Name", "Last Name", "Salary"})
table.AppendRows([]Row{
/* 0 */ {1, "Arya", "Stark", 3000, 4},
/* 1 */ {11, "Sansa", "Stark", 3000},
/* 2 */ {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
/* 3 */ {300, "Tyrion", "Lannister", 5000, -7.54},
/* 4 */ {400, "Jamie", "Lannister", 5000, nil},
/* 5 */ {500, "Tywin", "Lannister", 5000, "-7.540"},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by nothing
assert.Equal(t, []int{0, 1, 2, 3, 4, 5}, table.getSortedRowIndices())

// sort column #5 in Ascending order alphabetically and then numerically
table.SortBy([]SortBy{{Number: 5, Mode: AscAlphaNumeric}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{1, 4, 2, 3, 5, 0}, table.getSortedRowIndices())

// sort column #5 in Ascending order numerically and then alphabetically
table.SortBy([]SortBy{{Number: 5, Mode: AscNumericAlpha}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{3, 5, 0, 1, 4, 2}, table.getSortedRowIndices())

// sort column #5 in Descending order alphabetically and then numerically
table.SortBy([]SortBy{{Number: 5, Mode: DscAlphaNumeric}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{2, 4, 1, 0, 3, 5}, table.getSortedRowIndices())

// sort column #5 in Descending order numerically and then alphabetically
table.SortBy([]SortBy{{Number: 5, Mode: DscNumericAlpha}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{0, 3, 5, 2, 4, 1}, table.getSortedRowIndices())
}

func TestTable_sortRows_WithName(t *testing.T) {
table := Table{}
table.AppendHeader(Row{"#", "First Name", "Last Name", "Salary"})
Expand Down Expand Up @@ -130,35 +196,3 @@ func TestTable_sortRows_WithoutName(t *testing.T) {
table.SortBy(nil)
assert.Equal(t, []int{0, 1, 2, 3}, table.getSortedRowIndices())
}

func TestTable_sortRows_MissingCells(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000, 9},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000, 7},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 5, Mode: Asc}})
assert.Equal(t, []int{1, 3, 0, 2}, table.getSortedRowIndices())
}

func TestTable_sortRows_InvalidMode(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 2, Mode: AscNumeric}})
assert.Equal(t, []int{0, 1, 2, 3}, table.getSortedRowIndices())
}

0 comments on commit ff55b4c

Please sign in to comment.