Skip to content

Commit

Permalink
Merge pull request #11 from tideland/5-add-sort-function
Browse files Browse the repository at this point in the history
Add sorting
  • Loading branch information
themue authored Aug 20, 2022
2 parents 4482d2e + c283371 commit 132f22a
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 9 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

**Tideland Go Slices** provides a set of functions for the processing of types slices based
on generics and higher-order functions. This processing contains tests, mappings, filterings,
concatings, deleting, filtering, folding and many more.
concatings, deleting, filtering, folding and many more. Opposite to the standard library of
Go with similiar functions like e.g. Sort() will not work on the same slice. All functions return
new slices, even if a variable operation would have no effect.

## Contributors

Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module tideland.dev/go/slices

go 1.19

require tideland.dev/go/audit v0.7.0
require (
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
tideland.dev/go/audit v0.7.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
tideland.dev/go/audit v0.7.0 h1:lr4LkNu7i5qLJuqQ6lUfnt0J09anZNfrdXdB1I9JlTs=
tideland.dev/go/audit v0.7.0/go.mod h1:Jua+IB3KgAC7fbuZ1YHT7gKhwpiTOcn3Q7AOCQsrro8=
18 changes: 11 additions & 7 deletions slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

package slices // import "tideland.dev/go/slices"

//--------------------
// IMPORTS
//--------------------

//--------------------
// SLICES
//--------------------
Expand Down Expand Up @@ -61,7 +65,7 @@ func DeleteAll[V comparable](dv V, ivs []V) []V {
if ivs == nil {
return nil
}
var ovs []V = []V{}
ovs := []V{}
for _, v := range ivs {
if v != dv {
ovs = append(ovs, v)
Expand All @@ -75,7 +79,7 @@ func DeleteAllWith[V any](pred func(V) bool, ivs []V) []V {
if ivs == nil {
return nil
}
var ovs []V = []V{}
ovs := []V{}
for _, v := range ivs {
if !pred(v) {
ovs = append(ovs, v)
Expand Down Expand Up @@ -192,7 +196,7 @@ func Split[V any](n int, ivs []V) ([]V, []V) {
// SplitWith returns the values while pred() returns true as first and the rest
// as second slice.
func SplitWith[V any](pred func(V) bool, ivs []V) ([]V, []V) {
if ivs == nil || len(ivs) == 0 {
if len(ivs) == 0 {
return nil, nil
}
n := -1
Expand Down Expand Up @@ -263,8 +267,8 @@ func Unique[V comparable](ivs []V) []V {
if ivs == nil {
return nil
}
var ovs []V = []V{}
var isContained map[V]struct{} = map[V]struct{}{}
ovs := []V{}
isContained := map[V]struct{}{}
for _, v := range ivs {
if _, ok := isContained[v]; !ok {
ovs = append(ovs, v)
Expand All @@ -281,8 +285,8 @@ func UniqueWith[V any, C comparable](pred func(V) C, ivs []V) []V {
if ivs == nil {
return nil
}
var ovs []V = []V{}
var isContained map[C]struct{} = map[C]struct{}{}
ovs := []V{}
isContained := map[C]struct{}{}
for _, v := range ivs {
cv := pred(v)
if _, ok := isContained[cv]; !ok {
Expand Down
171 changes: 171 additions & 0 deletions sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Tideland Go Slices
//
// Copyright (C) 2022 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.

package slices // import "tideland.dev/go/slices"

import (
"runtime"

"golang.org/x/exp/constraints"
)

//--------------------
// SORT
//--------------------

// Sort provides a parallel quicksort for a slice of values with the
// constraint ordered.
func Sort[V constraints.Ordered](ivs []V) []V {
less := func(vs []V, i, j int) bool {
return vs[i] < vs[j]
}

return SortWith(ivs, less)
}

// SortWith sorts a slice based on a less function comparing the two values
// at the indexes i and j and returning true if the value at i has to be sorted
// before the one at j.
func SortWith[V any](ivs []V, less func(vs []V, i, j int) bool) []V {
ovs := Copy(ivs)

sort(ovs, less)

return ovs
}

// IsSorted returns true if a slice is sorted in ascending order.
func IsSorted[V constraints.Ordered](vs []V) bool {
for i := len(vs) - 1; i > 0; i-- {
if vs[i] < vs[i-1] {
return false
}
}
return true
}

// IsSortedWith returns true if a slice is sorted in ascending order
// using less as comparison function.
func IsSortedWith[V any](vs []V, less func(a, b V) bool) bool {
for i := len(vs) - 1; i > 0; i-- {
if less(vs[i], vs[i-1]) {
return false
}
}
return true
}

//--------------------
// PRIVATE
//--------------------

// sequentialThreshold for switching from sequential quick sort to insertion sort.
var sequentialThreshold = runtime.NumCPU()*4 - 1

// parallelThreshold for switching from parallel to sequential quick sort.
var parallelThreshold = runtime.NumCPU()*2048 - 1

// swap exchanges two values in a slice.
func swap[V any](vs []V, lo, hi int) {
tmp := vs[lo]
vs[lo] = vs[hi]
vs[hi] = tmp
}

// insertionSort for smaller data collections.
func insertionSort[V any](vs []V, less func(vs []V, i, j int) bool, lo, hi int) {
for i := lo + 1; i < hi+1; i++ {
for j := i; j > lo && less(vs, j, j-1); j-- {
swap(vs, j, j-1)
}
}
}

// median to caclculate the median based on Tukey's ninther.
func median[V any](vs []V, less func(vs []V, i, j int) bool, lo, hi int) int {
m := (lo + hi) / 2
d := (hi - lo) / 8
// Move median into the middle.
mot := func(ml, mm, mh int) {
if less(vs, mm, ml) {
swap(vs, mm, ml)
}
if less(vs, mh, mm) {
swap(vs, mh, mm)
}
if less(vs, mm, ml) {
swap(vs, mm, ml)
}
}
// Get low, middle, and high median.
if hi-lo > 40 {
mot(lo+d, lo, lo+2*d)
mot(m-d, m, m+d)
mot(hi-d, hi, hi-2*d)
}
// Get combined median.
mot(lo, m, hi)
return m
}

// partition the data based on the median.
func partition[V any](vs []V, less func(vs []V, i, j int) bool, lo, hi int) (int, int) {
med := median(vs, less, lo, hi)
idx := lo
swap(vs, med, hi)
for i := lo; i < hi; i++ {
if less(vs, i, hi) {
swap(vs, i, idx)
idx++
}
}
swap(vs, idx, hi)
return idx - 1, idx + 1
}

// sequentialQuickSort using itself recursively.
func sequentialQuickSort[V any](vs []V, less func(vs []V, i, j int) bool, lo, hi int) {
if hi-lo > sequentialThreshold {
// Use sequential quicksort.
plo, phi := partition(vs, less, lo, hi)
sequentialQuickSort(vs, less, lo, plo)
sequentialQuickSort(vs, less, phi, hi)
} else {
// Use insertion sort.
insertionSort(vs, less, lo, hi)
}
}

// parallelQuickSort using itself recursively and concurrent.
func parallelQuickSort[V any](vs []V, less func(vs []V, i, j int) bool, lo, hi int, done chan struct{}) {
if hi-lo > parallelThreshold {
// Parallel QuickSort.
plo, phi := partition(vs, less, lo, hi)
partDone := make(chan struct{})
go parallelQuickSort(vs, less, lo, plo, partDone)
go parallelQuickSort(vs, less, phi, hi, partDone)
// Wait for the end of both sorts.
<-partDone
<-partDone
} else {
// Sequential QuickSort.
sequentialQuickSort(vs, less, lo, hi)
}
// Signal that it's done.
done <- struct{}{}
}

// sort starts the parallel quick sort for the whole slice.
func sort[V any](vs []V, less func(vs []V, i, j int) bool) {
done := make(chan struct{})

go parallelQuickSort(vs, less, 0, len(vs)-1, done)

<-done
}

// EOF
Loading

0 comments on commit 132f22a

Please sign in to comment.