Skip to content

Commit a3ad84c

Browse files
authored
Merge pull request #1 from yourbasic/tip
Tip
2 parents 4ddd6b2 + e7004df commit a3ad84c

File tree

4 files changed

+228
-6
lines changed

4 files changed

+228
-6
lines changed

example_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package radix_test
2+
3+
import (
4+
"fmt"
5+
"github.com/yourbasic/radix"
6+
)
7+
8+
func ExampleSortSlice() {
9+
people := []struct {
10+
Name string
11+
Age int
12+
}{
13+
{"Gopher", 7},
14+
{"Alice", 55},
15+
{"Vera", 24},
16+
{"Bob", 75},
17+
}
18+
radix.SortSlice(people, func(i int) string { return people[i].Name })
19+
fmt.Println(people)
20+
// Output: [{Alice 55} {Bob 75} {Gopher 7} {Vera 24}]
21+
}

slice_test.go

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package radix
2+
3+
import (
4+
"bufio"
5+
"log"
6+
"os"
7+
"reflect"
8+
"sort"
9+
"strconv"
10+
"testing"
11+
)
12+
13+
func TestSortSlice(t *testing.T) {
14+
data := [...]string{"", "Hello", "foo", "fo", "xb", "xa", "bar", "foo", "f00", "%*&^*&^&", "***"}
15+
sorted := data[0:]
16+
sort.Strings(sorted)
17+
18+
a := data[0:]
19+
SortSlice(a, func(i int) string { return a[i] })
20+
if !reflect.DeepEqual(a, sorted) {
21+
t.Errorf(" got %v", a)
22+
t.Errorf("want %v", sorted)
23+
}
24+
25+
SortSlice(nil, func(i int) string { return a[i] })
26+
a = []string{}
27+
SortSlice(a, func(i int) string { return a[i] })
28+
if !reflect.DeepEqual(a, []string{}) {
29+
t.Errorf(" got %v", a)
30+
t.Errorf("want %v", []string{})
31+
}
32+
a = []string{""}
33+
SortSlice(a, func(i int) string { return a[i] })
34+
if !reflect.DeepEqual(a, []string{""}) {
35+
t.Errorf(" got %v", a)
36+
t.Errorf("want %v", []string{""})
37+
}
38+
}
39+
40+
func TestSortSlice1k(t *testing.T) {
41+
data := make([]string, 1<<10)
42+
for i := range data {
43+
data[i] = strconv.Itoa(i ^ 0x2cc)
44+
}
45+
46+
sorted := make([]string, len(data))
47+
copy(sorted, data)
48+
sort.Strings(sorted)
49+
50+
SortSlice(data, func(i int) string { return data[i] })
51+
if !reflect.DeepEqual(data, sorted) {
52+
t.Errorf(" got %v", data)
53+
t.Errorf("want %v", sorted)
54+
}
55+
}
56+
57+
func TestSortSliceBible(t *testing.T) {
58+
var data []string
59+
f, err := os.Open("res/bible.txt")
60+
if err != nil {
61+
log.Fatal(err)
62+
}
63+
for sc := bufio.NewScanner(f); sc.Scan(); {
64+
data = append(data, sc.Text())
65+
}
66+
67+
sorted := make([]string, len(data))
68+
copy(sorted, data)
69+
sort.Strings(sorted)
70+
71+
SortSlice(data, func(i int) string { return data[i] })
72+
if !reflect.DeepEqual(data, sorted) {
73+
for i, s := range data {
74+
if s != sorted[i] {
75+
t.Errorf("%v got: %v", i, s)
76+
t.Errorf("%v want: %v", i, sorted[i])
77+
}
78+
}
79+
}
80+
}
81+
82+
func BenchmarkRadixSortSliceBible(b *testing.B) {
83+
b.StopTimer()
84+
var data []string
85+
f, err := os.Open("res/bible.txt")
86+
if err != nil {
87+
log.Fatal(err)
88+
}
89+
for sc := bufio.NewScanner(f); sc.Scan(); {
90+
data = append(data, sc.Text())
91+
}
92+
93+
a := make([]string, len(data))
94+
for i := 0; i < b.N; i++ {
95+
copy(a, data)
96+
b.StartTimer()
97+
SortSlice(a, func(i int) string { return a[i] })
98+
b.StopTimer()
99+
}
100+
if err := f.Close(); err != nil {
101+
log.Fatal(err)
102+
}
103+
}
104+
105+
func BenchmarkSortSliceBible(b *testing.B) {
106+
b.StopTimer()
107+
var data []string
108+
f, err := os.Open("res/bible.txt")
109+
if err != nil {
110+
log.Fatal(err)
111+
}
112+
for sc := bufio.NewScanner(f); sc.Scan(); {
113+
data = append(data, sc.Text())
114+
}
115+
116+
a := make([]string, len(data))
117+
for i := 0; i < b.N; i++ {
118+
copy(a, data)
119+
b.StartTimer()
120+
sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
121+
b.StopTimer()
122+
}
123+
if err := f.Close(); err != nil {
124+
log.Fatal(err)
125+
}
126+
}
127+
128+
func BenchmarkRadixSortSlice1k(b *testing.B) {
129+
b.StopTimer()
130+
data := make([]string, 1<<10)
131+
for i := range data {
132+
data[i] = strconv.Itoa(i ^ 0x2cc)
133+
}
134+
135+
a := make([]string, len(data))
136+
for i := 0; i < b.N; i++ {
137+
copy(a, data)
138+
b.StartTimer()
139+
SortSlice(a, func(i int) string { return a[i] })
140+
b.StopTimer()
141+
}
142+
}
143+
144+
func BenchmarkSortSlice1k(b *testing.B) {
145+
b.StopTimer()
146+
data := make([]string, 1<<10)
147+
for i := range data {
148+
data[i] = strconv.Itoa(i ^ 0x2cc)
149+
}
150+
151+
a := make([]string, len(data))
152+
for i := 0; i < b.N; i++ {
153+
copy(a, data)
154+
b.StartTimer()
155+
sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
156+
b.StopTimer()
157+
}
158+
}

sort.go

+45-2
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33
// This is an optimized sorting algorithm equivalent to sort.Strings.
44
// For string sorting, a carefully implemented radix sort can be considerably
55
// faster than Quicksort, sometimes more than twice as fast.
6-
//
76
package radix
87

8+
import (
9+
"reflect"
10+
"unsafe"
11+
)
12+
913
// Sort sorts a slice of strings in increasing byte-wise lexicographic order.
14+
//
1015
// The function is equivalent to sort.Strings in the standard library.
1116
func Sort(a []string) {
1217
n := len(a)
1318
if n < 2 {
1419
return
1520
}
16-
mem := make([]list, n) // Put elements into a linked list.
21+
// Put elements into a linked list.
22+
mem := make([]list, n)
1723
for i, s := range a {
1824
mem[i].str = s
1925
if i < n-1 {
@@ -27,6 +33,43 @@ func Sort(a []string) {
2733
}
2834
}
2935

36+
// SortSlice sorts a slice according to the strings returned by str.
37+
//
38+
// The function panics if the provided interface is not a slice.
39+
func SortSlice(slice interface{}, str func(i int) string) {
40+
if slice == nil {
41+
return
42+
}
43+
n := reflect.ValueOf(slice).Len()
44+
if n < 2 {
45+
return
46+
}
47+
// Put elements into a linked list.
48+
mem := make([]list, n)
49+
for i := 0; i < n; i++ {
50+
mem[i].str = str(i)
51+
if i < n-1 {
52+
mem[i].next = &mem[i+1]
53+
}
54+
}
55+
res := msdRadixSort(&mem[0], n)
56+
// Create a permutation that will sort the slice.
57+
perm := make([]int, n)
58+
const size = unsafe.Sizeof(list{})
59+
base := uintptr(unsafe.Pointer(&mem[0]))
60+
for i := 0; i < n; i++ {
61+
perm[(uintptr(unsafe.Pointer(res))-base)/size] = i
62+
res = res.next
63+
}
64+
// Apply permutation by swapping.
65+
swap := reflect.Swapper(slice)
66+
for i := 0; i < n; i++ {
67+
for j := perm[i]; j != i; perm[j], j = j, perm[j] {
68+
swap(i, j)
69+
}
70+
}
71+
}
72+
3073
const insertBreak = 16
3174

3275
type list struct {

sort_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ func TestSortBible(t *testing.T) {
7373
for i, s := range data {
7474
if s != sorted[i] {
7575
t.Errorf("%v got: %v", i, s)
76-
t.Errorf("%v want: %v\n\n", i, sorted[i])
76+
t.Errorf("%v want: %v", i, sorted[i])
7777
}
7878
}
7979
}
8080
}
8181

82-
func BenchmarkSortMsdBible(b *testing.B) {
82+
func BenchmarkRadixSortBible(b *testing.B) {
8383
b.StopTimer()
8484
var data []string
8585
f, err := os.Open("res/bible.txt")
@@ -125,7 +125,7 @@ func BenchmarkSortStringsBible(b *testing.B) {
125125
}
126126
}
127127

128-
func BenchmarkSortMsd1K(b *testing.B) {
128+
func BenchmarkRadixSort1k(b *testing.B) {
129129
b.StopTimer()
130130
data := make([]string, 1<<10)
131131
for i := range data {
@@ -141,7 +141,7 @@ func BenchmarkSortMsd1K(b *testing.B) {
141141
}
142142
}
143143

144-
func BenchmarkSortStrings1K(b *testing.B) {
144+
func BenchmarkSortStrings1k(b *testing.B) {
145145
b.StopTimer()
146146
data := make([]string, 1<<10)
147147
for i := range data {

0 commit comments

Comments
 (0)