Rows with a key sort to the top of the range; the others, blank rows included, close up below them in their order, which is where a stable sort of every row would put them. A test checks the two agree.
- commit
- 3d2692a64d85168f42afd8d15460ea1345e4cc77
- parent
- f571a69b37078d4ac001d54a73d84769a21cfa58
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 79 | +++++++++++++++++++++++------- |
| M | internal/ | 64 | ++++++++++++++++++++++++- |
@@ -78,6 +78,10 @@ func (s *Sheet) compareRows(keys []SortKey, i, j int) int {
7878 // move with their rows and their relative references shift by the7979 // distance moved, as if copied there; references to the sorted cells8080 // from elsewhere are left alone. The whole sort is one undo step.81+//82+// Only rows holding cells are sorted: rows whose keys are all blank go83+// last in their order, as blank rows do, so the cost is the rows with84+// data, however tall r is.8185 func (s *Sheet) SortRange(r Rect, keys []SortKey) {8286 if used, ok := s.UsedRange(); ok {8387 r.To.Row, r.To.Col = min(r.To.Row, used.To.Row), min(r.To.Col, used.To.Col)@@ -85,30 +89,67 @@ func (s *Sheet) SortRange(r Rect, keys []SortKey) {
8589 if len(keys) == 0 || r.To.Row <= r.From.Row || r.To.Col < r.From.Col {8690 return8791 }88- order := make([]int, r.To.Row-r.From.Row+1)89- for i := range order {90- order[i] = r.From.Row + i91- }92- slices.SortStableFunc(order, func(i, j int) int { return s.compareRows(keys, i, j) })9392 old := map[Addr]*Cell{}94- for _, a := range s.cellsIn(r) {95- old[a] = s.cells.get(a)93+ var rows []int // rows of r with cells, in order94+ for a, c := range s.cells.inRange(r) {95+ old[a] = c96+ if len(rows) == 0 || rows[len(rows)-1] != a.Row {97+ rows = append(rows, a.Row)98+ }99+ }100+ // Rows with a key sort to the top; the others (blank keys, and blank101+ // rows) keep their order below them.102+ var keyed, rest []int103+ for _, row := range rows {104+ if s.keysBlank(keys, row) {105+ rest = append(rest, row)106+ } else {107+ keyed = append(keyed, row)108+ }109+ }110+ before := slices.Clone(keyed) // in their original order111+ slices.SortStableFunc(keyed, func(i, j int) int { return s.compareRows(keys, i, j) })112+ dst := make(map[int]int, len(rows))113+ for i, row := range keyed {114+ dst[row] = r.From.Row + i115+ }116+ // The other rows close up below: each moves down past the keyed rows117+ // that were below it and up past those that were above it.118+ n := 0119+ for _, row := range rest {120+ for n < len(before) && before[n] < row {121+ n++122+ }123+ dst[row] = row + len(keyed) - n124+ }125+ next := make(map[Addr]*Cell, len(old))126+ for a, c := range old {127+ to := Addr{Col: a.Col, Row: dst[a.Row]}128+ if to.Row != a.Row {129+ c = c.rewritten(formula.Shift(0, to.Row-a.Row))130+ }131+ next[to] = c96132 }97133 s.change("sort "+r.String(), r, func() {98- for k, src := range order {99- dst := r.From.Row + k100- if dst == src {101- continue134+ for a := range old {135+ if next[a] == nil {136+ s.place(a, nil)102137 }103- for col := r.From.Col; col <= r.To.Col; col++ {104- to := Addr{Col: col, Row: dst}105- switch c := old[Addr{Col: col, Row: src}]; {106- case c != nil:107- s.place(to, c.rewritten(formula.Shift(0, dst-src)))108- case s.cells.get(to) != nil:109- s.place(to, nil)110- }138+ }139+ for a, c := range next {140+ if old[a] != c {141+ s.place(a, c)111142 }112143 }113144 })114145 }146+147+// keysBlank reports whether every key of row is blank.148+func (s *Sheet) keysBlank(keys []SortKey, row int) bool {149+ for _, k := range keys {150+ if s.Value(Addr{Col: k.Col, Row: row}).Kind != Empty {151+ return false152+ }153+ }154+ return true155+}@@ -1,6 +1,13 @@
11 package sheet22 3-import "testing"3+import (4+ "fmt"5+ "math/rand/v2"6+ "slices"7+ "testing"8+9+ "github.com/FelineStateMachine/012/internal/formula"10+)411 512 // A filter over whole columns tests the rows with data and treats the613 // blank rows past them as one: hiding them, counting them and stepping@@ -41,3 +48,58 @@ func TestFilterWholeColumns(t *testing.T) {
4148 t.Errorf("blanks in FilterValues(A) = %+v", vals[len(vals)-1])4249 }4350 }51+52+// Sorting moves only the rows with cells, and lands every row where a53+// stable sort of all the range's rows (blank rows included) would.54+func TestSortMatchesDenseSort(t *testing.T) {55+ for seed := range 20 {56+ rng := rand.New(rand.NewPCG(uint64(seed), 3))57+ s := New()58+ fill := []string{"", "", "", "3", "1", "b", "A", "TRUE", "=1/0", "=A1+1", `=""`}59+ for row := range 30 {60+ if rng.IntN(4) == 0 {61+ continue // a blank row62+ }63+ for col := range 3 {64+ if in := fill[rng.IntN(len(fill))]; in != "" {65+ s.Set(Addr{Col: col, Row: row}, in)66+ }67+ }68+ }69+ r := Rect{From: Addr{Row: 2}, To: Addr{Col: 2, Row: MaxRows - 1}}70+ keys := []SortKey{{Col: rng.IntN(3), Desc: seed%2 == 1}, {Col: rng.IntN(3)}}71+ want := denseSort(s, r, keys)72+ s.SortRange(r, keys)73+ if got := inputs(s); fmt.Sprint(got) != fmt.Sprint(want) {74+ t.Errorf("seed %d, keys %v:\n got %v\nwant %v", seed, keys, got, want)75+ }76+ }77+}78+79+// denseSort is what sorting did before it went by rows with data: a80+// stable sort of every row of r (clipped to the used range), returning81+// the inputs it leaves.82+func denseSort(s *Sheet, r Rect, keys []SortKey) map[string]string {83+ used, _ := s.UsedRange()84+ r.To.Row, r.To.Col = min(r.To.Row, used.To.Row), min(r.To.Col, used.To.Col)85+ order := make([]int, r.To.Row-r.From.Row+1)86+ for i := range order {87+ order[i] = r.From.Row + i88+ }89+ slices.SortStableFunc(order, func(i, j int) int { return s.compareRows(keys, i, j) })90+ out := map[string]string{}91+ for a, c := range s.cells.all() {92+ if !r.Contains(a) {93+ out[a.String()] = c.Input94+ }95+ }96+ for k, src := range order {97+ for col := r.From.Col; col <= r.To.Col; col++ {98+ if c := s.cells.get(Addr{Col: col, Row: src}); c != nil {99+ to := Addr{Col: col, Row: r.From.Row + k}100+ out[to.String()] = c.rewritten(formula.Shift(0, to.Row-src)).Input101+ }102+ }103+ }104+ return out105+}