├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── demo └── main.go ├── gsv.go ├── gsv_test.go ├── readme.md └── start.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: simonwaldherr 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: SimonWaldherr 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://flattr.com/@SimonWaldherr'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | 3 | ## General 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | ## Icon must end with two \r 9 | Icon 10 | 11 | 12 | ## Thumbnails 13 | ._* 14 | 15 | ## Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | ## Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | # Windows 32 | 33 | ## Windows thumbnail cache files 34 | Thumbs.db 35 | ehthumbs.db 36 | ehthumbs_vista.db 37 | 38 | ## Dump file 39 | *.stackdump 40 | 41 | ## Folder config file 42 | Desktop.ini 43 | 44 | ## Recycle Bin used on file shares 45 | $RECYCLE.BIN/ 46 | 47 | ## Windows Installer files 48 | *.cab 49 | *.msi 50 | *.msm 51 | *.msp 52 | 53 | ## Windows shortcuts 54 | *.lnk 55 | 56 | # Golang 57 | 58 | ## Binaries for programs and plugins 59 | *.exe 60 | *.dll 61 | *.so 62 | *.dylib 63 | 64 | ## Test binary, build with `go test -c` 65 | *.test 66 | 67 | ## Output of the go coverage tool, specifically when used with LiteIDE 68 | *.out 69 | 70 | ## Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 71 | .glide/ 72 | -trash 73 | 74 | #exclude generated gif 75 | *.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: false 4 | 5 | go: 6 | - release 7 | - tip 8 | 9 | before_install: 10 | - go get github.com/axw/gocov/gocov 11 | - go get github.com/mattn/goveralls 12 | - go get golang.org/x/tools/cmd/cover 13 | 14 | install: 15 | - go get -t simonwaldherr.de/go/GolangSortingVisualization 16 | - go get -t github.com/SimonWaldherr/GolangSortingVisualization 17 | 18 | script: 19 | - go test -v -coverprofile=gsv.coverprofile 20 | - $HOME/gopath/bin/goveralls -coverprofile=gsv.coverprofile -service=travis-ci -repotoken=MSToT9De9SIxTAH8BD3JsN68xfWAPEqDP 21 | 22 | notifications: 23 | email: 24 | recipients: 25 | - travis@simon.waldherr.eu 26 | on_success: always 27 | on_failure: always -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Simon Waldherr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | cryptoRand "crypto/rand" 5 | "flag" 6 | "fmt" 7 | gsv "simonwaldherr.de/go/GolangSortingVisualization" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func randomArray(n int, max int) []int { 13 | arr := make([]int, n) 14 | for i := 0; i < n; i++ { 15 | b := make([]byte, 1) 16 | cryptoRand.Read(b) 17 | number := float64(b[0]) 18 | arr[i] = int(number / 255 * float64(max)) 19 | } 20 | return arr 21 | } 22 | 23 | func makeVisualizer(name string) gsv.Visualizer { 24 | name = "gif" 25 | switch name { 26 | case "stdout": 27 | //return &gsv.WriteStdout{} 28 | case "gif": 29 | return &gsv.GifVisualizer{} 30 | default: 31 | return nil 32 | } 33 | return nil 34 | } 35 | 36 | func runSort(visName string, algo string, sortFunc gsv.Sorter) { 37 | visualizer := makeVisualizer(visName) 38 | if visualizer == nil { 39 | fmt.Println("Invalid visualizer name") 40 | return 41 | } 42 | visualizer.Setup(algo) 43 | arr := randomArray(gsv.Count, gsv.Max) 44 | sortFunc(arr, visualizer.AddFrame) 45 | visualizer.Complete() 46 | } 47 | 48 | func keysString(m map[string]gsv.Sorter) string { 49 | keys := make([]string, 0, len(m)) 50 | for k := range m { 51 | keys = append(keys, k) 52 | } 53 | return strings.Join(keys, "/") 54 | } 55 | 56 | func main() { 57 | var algo string 58 | var visName string 59 | 60 | sorterMap := map[string]gsv.Sorter{ 61 | "bubble": gsv.BubbleSort, 62 | "cocktail": gsv.CocktailSort, 63 | "comb": gsv.CombSort, 64 | "counting": gsv.CountingSort, 65 | "cycle": gsv.CycleSort, 66 | "gnome": gsv.GnomeSort, 67 | "insertion": gsv.InsertionSort, 68 | "oddEven": gsv.OddEvenSort, 69 | "selection": gsv.SelectionSort, 70 | "sleep": gsv.SleepSort, 71 | "stooge": gsv.StoogeSort, 72 | "pancake": gsv.PancakeSort, 73 | "quick": gsv.QuickSort, 74 | "merge": gsv.MergeSort, 75 | "shell": gsv.ShellSort, 76 | "heap": gsv.HeapSort, 77 | "radix": gsv.RadixSort, 78 | "bitonic": gsv.BitonicSort, 79 | } 80 | 81 | flag.StringVar(&algo, "algo", "bubble", "Select sorting algorithm all/"+strings.Replace(keysString(sorterMap), "bubble", "[bubble]", 1)) 82 | flag.IntVar(&gsv.Fps, "fps", 10, "frames per second") 83 | flag.IntVar(&gsv.Max, "max", 9, "highest value") 84 | flag.IntVar(&gsv.Count, "count", 30, "number of values") 85 | flag.IntVar(&gsv.Mode, "mode", 1, "visualization mode") 86 | flag.StringVar(&visName, "vis", "stdout", "Select output: [stdout]/gif") 87 | 88 | flag.Parse() 89 | 90 | fmt.Printf("sorting via %v-sort\nhighest value: %v\nnumber of values: %v\n\n", algo, gsv.Max, gsv.Count) 91 | time.Sleep(time.Second * 1) 92 | if algo == "all" { 93 | for k, v := range sorterMap { 94 | runSort(visName, k, v) 95 | } 96 | } else { 97 | sortFunc := sorterMap[algo] 98 | if sortFunc != nil { 99 | runSort(visName, algo, sortFunc) 100 | } else { 101 | fmt.Printf("Algorithm %v not found.\n", algo) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /gsv.go: -------------------------------------------------------------------------------- 1 | package gsv 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "image/gif" 9 | "math/rand" 10 | "os" 11 | "time" 12 | ) 13 | 14 | // Sorter defines a function type for sorting algorithms 15 | type Sorter func([]int, FrameGen) 16 | 17 | // FrameGen defines a function type for generating frames 18 | type FrameGen func([]int) 19 | 20 | //var test bool = false 21 | 22 | func (fg FrameGen) Setup(name string) { 23 | } 24 | 25 | func (fg FrameGen) AddFrame(arr []int) { 26 | fg(arr) 27 | } 28 | 29 | func (fg FrameGen) Complete() { 30 | } 31 | 32 | // Visualizer interface for visualizing sorting steps 33 | type Visualizer interface { 34 | Setup(string) 35 | AddFrame([]int) 36 | Complete() 37 | } 38 | 39 | // GifVisualizer is a visualizer that outputs a GIF 40 | type GifVisualizer struct { 41 | name string 42 | g *gif.GIF 43 | } 44 | 45 | var Max int 46 | var Fps int 47 | var Count int 48 | var Mode int 49 | var test bool = false 50 | 51 | // Setup initializes the GIF visualizer 52 | func (gv *GifVisualizer) Setup(name string) { 53 | gv.g = &gif.GIF{ 54 | LoopCount: 1, 55 | } 56 | gv.name = name 57 | } 58 | 59 | // AddFrame adds a frame to the GIF 60 | func (gv *GifVisualizer) AddFrame(arr []int) { 61 | frame := buildImage(arr) 62 | gv.g.Image = append(gv.g.Image, frame) 63 | gv.g.Delay = append(gv.g.Delay, 2) 64 | } 65 | 66 | // Complete writes the GIF to disk 67 | func (gv *GifVisualizer) Complete() { 68 | WriteGif(gv.name, gv.g) 69 | } 70 | 71 | // buildImage creates an image from the array state 72 | func buildImage(arr []int) *image.Paletted { 73 | var frame = image.NewPaletted( 74 | image.Rectangle{ 75 | image.Point{0, 0}, 76 | image.Point{len(arr), Max}, 77 | }, 78 | color.Palette{ 79 | color.Gray{uint8(255)}, 80 | color.Gray{uint8(0)}, 81 | }, 82 | ) 83 | for k, v := range arr { 84 | frame.SetColorIndex(k, Max-v, uint8(1)) 85 | if Mode == 2 { 86 | for y := Max - v + 1; y < Max; y++ { 87 | frame.SetColorIndex(k, y, uint8(1)) 88 | } 89 | } 90 | } 91 | return frame 92 | } 93 | 94 | // WriteGif writes the GIF file to disk 95 | func WriteGif(name string, g *gif.GIF) { 96 | w, err := os.Create(name + ".gif") 97 | if err != nil { 98 | fmt.Println("os.Create") 99 | panic(err) 100 | } 101 | defer func() { 102 | if err := w.Close(); err != nil { 103 | fmt.Println("w.Close") 104 | panic(err) 105 | } 106 | }() 107 | err = gif.EncodeAll(w, g) 108 | if err != nil { 109 | fmt.Println("gif.EncodeAll") 110 | panic(err) 111 | } 112 | } 113 | 114 | // WriteStdout writes the array to stdout as an ASCII visualization 115 | func WriteStdout(arr []int) { 116 | var buffer bytes.Buffer 117 | 118 | for y := 0; y < Max; y++ { 119 | for x := 0; x < len(arr); x++ { 120 | if arr[x] == y || (arr[x] < y && Mode == 1) || (arr[x] > y && Mode == 2) { 121 | buffer.WriteByte('#') 122 | } else { 123 | buffer.WriteByte(' ') 124 | } 125 | } 126 | buffer.WriteByte('\n') 127 | } 128 | 129 | if !test { 130 | time.Sleep(time.Second / time.Duration(Fps)) 131 | fmt.Print("\033[2J") 132 | fmt.Print(buffer.String()) 133 | } 134 | } 135 | 136 | // shuffle randomizes the order of elements in the array 137 | func shuffle(arr []int) []int { 138 | for i := len(arr) - 1; i > 0; i-- { 139 | if j := rand.Intn(i + 1); i != j { 140 | arr[i], arr[j] = arr[j], arr[i] 141 | } 142 | } 143 | return arr 144 | } 145 | 146 | // isSorted checks if the array is sorted 147 | func isSorted(arr []int) bool { 148 | for i := 0; i < len(arr)-1; i++ { 149 | if arr[i] > arr[i+1] { 150 | return false 151 | } 152 | } 153 | return true 154 | } 155 | 156 | /* SORTING ALGORITHMS BEGIN HERE */ 157 | 158 | // BogoSort is an implementation of https://en.wikipedia.org/wiki/Bogosort 159 | func BogoSort(arr []int, frameGen FrameGen) { 160 | if frameGen != nil { 161 | frameGen(arr) 162 | } 163 | for !isSorted(arr) { 164 | arr = shuffle(arr) 165 | if frameGen != nil { 166 | frameGen(arr) 167 | } 168 | } 169 | } 170 | 171 | // BubbleSort is an implementation of https://en.wikipedia.org/wiki/Bubble_sort 172 | func BubbleSort(arr []int, frameGen FrameGen) { 173 | for i := 0; i < len(arr); i++ { 174 | for j := 0; j < len(arr)-1; j++ { 175 | if arr[j] > arr[j+1] { 176 | arr[j], arr[j+1] = arr[j+1], arr[j] 177 | if frameGen != nil { 178 | frameGen(arr) 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | // CocktailSort is an implementation of https://en.wikipedia.org/wiki/Cocktail_shaker_sort 186 | func CocktailSort(arr []int, frameGen FrameGen) { 187 | for !isSorted(arr) { 188 | for i := 0; i < len(arr)-2; i++ { 189 | if arr[i] > arr[i+1] { 190 | arr[i], arr[i+1] = arr[i+1], arr[i] 191 | if frameGen != nil { 192 | frameGen(arr) 193 | } 194 | } 195 | } 196 | for i := len(arr) - 2; i > 0; i-- { 197 | if arr[i] > arr[i+1] { 198 | arr[i], arr[i+1] = arr[i+1], arr[i] 199 | if frameGen != nil { 200 | frameGen(arr) 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | // CombSort is an implementation of https://en.wikipedia.org/wiki/Comb_sort 208 | func CombSort(arr []int, frameGen FrameGen) { 209 | gap := len(arr) 210 | swapped := true 211 | 212 | for gap > 1 || swapped { 213 | swapped = false 214 | if gap > 1 { 215 | gap = int(float64(gap) / 1.3) 216 | } 217 | for i := 0; i < len(arr)-gap; i++ { 218 | if arr[i] > arr[i+gap] { 219 | arr[i], arr[i+gap] = arr[i+gap], arr[i] 220 | swapped = true 221 | if frameGen != nil { 222 | frameGen(arr) 223 | } 224 | } 225 | } 226 | } 227 | } 228 | 229 | // CountingSort is an implementation of https://en.wikipedia.org/wiki/Counting_sort 230 | func CountingSort(arr []int, frameGen FrameGen) { 231 | count := make([]int, Max+1) 232 | for _, x := range arr { 233 | count[x]++ 234 | } 235 | z := 0 236 | for i, c := range count { 237 | for c > 0 { 238 | arr[z] = i 239 | z++ 240 | c-- 241 | } 242 | if frameGen != nil { 243 | frameGen(arr) 244 | } 245 | } 246 | } 247 | 248 | // CycleSort is an implementation of https://en.wikipedia.org/wiki/Cycle_sort 249 | func CycleSort(arr []int, frameGen FrameGen) { 250 | for cycleStart := 0; cycleStart < len(arr)-1; cycleStart++ { 251 | item := arr[cycleStart] 252 | pos := cycleStart 253 | for i := cycleStart + 1; i < len(arr); i++ { 254 | if arr[i] < item { 255 | pos++ 256 | } 257 | } 258 | if pos == cycleStart { 259 | continue 260 | } 261 | for item == arr[pos] { 262 | pos++ 263 | } 264 | arr[pos], item = item, arr[pos] 265 | for pos != cycleStart { 266 | pos = cycleStart 267 | for i := cycleStart + 1; i < len(arr); i++ { 268 | if arr[i] < item { 269 | pos++ 270 | } 271 | } 272 | for item == arr[pos] { 273 | pos++ 274 | } 275 | arr[pos], item = item, arr[pos] 276 | } 277 | if frameGen != nil { 278 | frameGen(arr) 279 | } 280 | } 281 | } 282 | 283 | // GnomeSort is an implementation of https://en.wikipedia.org/wiki/Gnome_sort 284 | func GnomeSort(arr []int, frameGen FrameGen) { 285 | i := 0 286 | for i < len(arr) { 287 | if i == 0 || arr[i] >= arr[i-1] { 288 | i++ 289 | } else { 290 | arr[i], arr[i-1] = arr[i-1], arr[i] 291 | i-- 292 | } 293 | if frameGen != nil { 294 | frameGen(arr) 295 | } 296 | } 297 | } 298 | 299 | // InsertionSort is an implementation of https://en.wikipedia.org/wiki/Insertion_sort 300 | func InsertionSort(arr []int, frameGen FrameGen) { 301 | for i := 1; i < len(arr); i++ { 302 | key := arr[i] 303 | j := i - 1 304 | for j >= 0 && arr[j] > key { 305 | arr[j+1] = arr[j] 306 | j-- 307 | } 308 | arr[j+1] = key 309 | if frameGen != nil { 310 | frameGen(arr) 311 | } 312 | } 313 | } 314 | 315 | // OddEvenSort is an implementation of https://en.wikipedia.org/wiki/Odd–even_sort 316 | func OddEvenSort(arr []int, frameGen FrameGen) { 317 | sorted := false 318 | for !sorted { 319 | sorted = true 320 | for i := 1; i < len(arr)-1; i += 2 { 321 | if arr[i] > arr[i+1] { 322 | arr[i], arr[i+1] = arr[i+1], arr[i] 323 | sorted = false 324 | } 325 | if frameGen != nil { 326 | frameGen(arr) 327 | } 328 | } 329 | for i := 0; i < len(arr)-1; i += 2 { 330 | if arr[i] > arr[i+1] { 331 | arr[i], arr[i+1] = arr[i+1], arr[i] 332 | sorted = false 333 | } 334 | if frameGen != nil { 335 | frameGen(arr) 336 | } 337 | } 338 | } 339 | } 340 | 341 | // SelectionSort is an implementation of https://en.wikipedia.org/wiki/Selection_sort 342 | func SelectionSort(arr []int, frameGen FrameGen) { 343 | for i := 0; i < len(arr); i++ { 344 | minIndex := i 345 | for j := i + 1; j < len(arr); j++ { 346 | if arr[j] < arr[minIndex] { 347 | minIndex = j 348 | } 349 | } 350 | arr[i], arr[minIndex] = arr[minIndex], arr[i] 351 | if frameGen != nil { 352 | frameGen(arr) 353 | } 354 | } 355 | } 356 | 357 | // SleepSort is a non-standard sorting algorithm 358 | func SleepSort(arr []int, frameGen FrameGen) { 359 | arr2 := make([]int, len(arr)) 360 | channel := make(chan int, 1) 361 | for i := 0; i < len(arr); i++ { 362 | go func(i int) { 363 | time.Sleep(time.Duration(arr[i]) * time.Millisecond) 364 | channel <- arr[i] 365 | }(i) 366 | } 367 | 368 | for i := 0; i < len(arr); i++ { 369 | arr2[i] = <-channel 370 | if frameGen != nil { 371 | frameGen(arr2) 372 | } 373 | } 374 | copy(arr, arr2) 375 | } 376 | 377 | // StoogeSort is an implementation of https://en.wikipedia.org/wiki/Stooge_sort 378 | func StoogeSort(arr []int, frameGen FrameGen) { 379 | stoogesort(arr, 0, len(arr)-1, frameGen) 380 | } 381 | 382 | func stoogesort(arr []int, l, h int, frameGen FrameGen) { 383 | if arr[l] > arr[h] { 384 | arr[l], arr[h] = arr[h], arr[l] 385 | if frameGen != nil { 386 | frameGen(arr) 387 | } 388 | } 389 | if h-l+1 > 2 { 390 | t := (h - l + 1) / 3 391 | stoogesort(arr, l, h-t, frameGen) 392 | stoogesort(arr, l+t, h, frameGen) 393 | stoogesort(arr, l, h-t, frameGen) 394 | } 395 | } 396 | 397 | // PancakeSort is an implementation of https://en.wikipedia.org/wiki/Pancake_sorting 398 | func PancakeSort(arr []int, frameGen FrameGen) { 399 | for uns := len(arr) - 1; uns > 0; uns-- { 400 | maxIndex := 0 401 | for i := 1; i <= uns; i++ { 402 | if arr[i] > arr[maxIndex] { 403 | maxIndex = i 404 | } 405 | } 406 | pancakeFlip(arr, maxIndex, frameGen) 407 | pancakeFlip(arr, uns, frameGen) 408 | } 409 | } 410 | 411 | func pancakeFlip(arr []int, r int, frameGen FrameGen) { 412 | for l := 0; l < r; l, r = l+1, r-1 { 413 | arr[l], arr[r] = arr[r], arr[l] 414 | if frameGen != nil { 415 | frameGen(arr) 416 | } 417 | } 418 | } 419 | 420 | // QuickSort is an implementation of https://en.wikipedia.org/wiki/Quicksort 421 | func QuickSort(arr []int, frameGen FrameGen) { 422 | quickSort(arr, 0, len(arr)-1, frameGen) 423 | } 424 | 425 | func quickSort(arr []int, l, r int, frameGen FrameGen) { 426 | if l >= r { 427 | return 428 | } 429 | pivot := partition(arr, l, r, frameGen) 430 | quickSort(arr, l, pivot-1, frameGen) 431 | quickSort(arr, pivot+1, r, frameGen) 432 | } 433 | 434 | func partition(arr []int, l, r int, frameGen FrameGen) int { 435 | pivot := arr[r] 436 | i := l 437 | for j := l; j < r; j++ { 438 | if arr[j] <= pivot { 439 | arr[i], arr[j] = arr[j], arr[i] 440 | i++ 441 | } 442 | if frameGen != nil { 443 | frameGen(arr) 444 | } 445 | } 446 | arr[i], arr[r] = arr[r], arr[i] 447 | if frameGen != nil { 448 | frameGen(arr) 449 | } 450 | return i 451 | } 452 | 453 | // MergeSort is an implementation of https://en.wikipedia.org/wiki/Merge_sort 454 | func MergeSort(arr []int, frameGen FrameGen) { 455 | mergesort(arr, frameGen) 456 | } 457 | 458 | func mergesort(arr []int, frameGen FrameGen) []int { 459 | if len(arr) <= 1 { 460 | return arr 461 | } 462 | mid := len(arr) / 2 463 | left := mergesort(arr[:mid], frameGen) 464 | right := mergesort(arr[mid:], frameGen) 465 | return merge(left, right, frameGen) 466 | } 467 | 468 | func merge(left, right []int, frameGen FrameGen) []int { 469 | result := make([]int, 0, len(left)+len(right)) 470 | for len(left) > 0 && len(right) > 0 { 471 | if left[0] <= right[0] { 472 | result = append(result, left[0]) 473 | left = left[1:] 474 | } else { 475 | result = append(result, right[0]) 476 | right = right[1:] 477 | } 478 | if frameGen != nil { 479 | frameGen(result) 480 | } 481 | } 482 | result = append(result, left...) 483 | result = append(result, right...) 484 | if frameGen != nil { 485 | frameGen(result) 486 | } 487 | return result 488 | } 489 | 490 | // ShellSort is an implementation of https://en.wikipedia.org/wiki/Shellsort 491 | func ShellSort(arr []int, frameGen FrameGen) { 492 | n := len(arr) 493 | for gap := n / 2; gap > 0; gap /= 2 { 494 | for i := gap; i < n; i++ { 495 | temp := arr[i] 496 | j := i 497 | for ; j >= gap && arr[j-gap] > temp; j -= gap { 498 | arr[j] = arr[j-gap] 499 | if frameGen != nil { 500 | frameGen(arr) 501 | } 502 | } 503 | arr[j] = temp 504 | if frameGen != nil { 505 | frameGen(arr) 506 | } 507 | } 508 | } 509 | } 510 | 511 | // HeapSort is an implementation of https://en.wikipedia.org/wiki/Heapsort 512 | func HeapSort(arr []int, frameGen FrameGen) { 513 | buildMaxHeap(arr, frameGen) 514 | for i := len(arr) - 1; i > 0; i-- { 515 | arr[0], arr[i] = arr[i], arr[0] 516 | maxHeapify(arr, 0, i, frameGen) 517 | } 518 | } 519 | 520 | func buildMaxHeap(arr []int, frameGen FrameGen) { 521 | for i := len(arr)/2 - 1; i >= 0; i-- { 522 | maxHeapify(arr, i, len(arr), frameGen) 523 | } 524 | } 525 | 526 | func maxHeapify(arr []int, i, n int, frameGen FrameGen) { 527 | largest := i 528 | left := 2*i + 1 529 | right := 2*i + 2 530 | if left < n && arr[left] > arr[largest] { 531 | largest = left 532 | } 533 | if right < n && arr[right] > arr[largest] { 534 | largest = right 535 | } 536 | if largest != i { 537 | arr[i], arr[largest] = arr[largest], arr[i] 538 | if frameGen != nil { 539 | frameGen(arr) 540 | } 541 | maxHeapify(arr, largest, n, frameGen) 542 | } 543 | } 544 | 545 | // RadixSort is an implementation of https://en.wikipedia.org/wiki/Radix_sort 546 | func RadixSort(arr []int, frameGen FrameGen) { 547 | maxValue := getMax(arr) 548 | for exp := 1; maxValue/exp > 0; exp *= 10 { 549 | countingSortByDigit(arr, exp, frameGen) 550 | } 551 | } 552 | 553 | func getMax(arr []int) int { 554 | max := arr[0] 555 | for _, v := range arr { 556 | if v > max { 557 | max = v 558 | } 559 | } 560 | return max 561 | } 562 | 563 | func countingSortByDigit(arr []int, exp int, frameGen FrameGen) { 564 | output := make([]int, len(arr)) 565 | count := make([]int, 10) 566 | 567 | for i := 0; i < len(arr); i++ { 568 | index := (arr[i] / exp) % 10 569 | count[index]++ 570 | } 571 | 572 | for i := 1; i < 10; i++ { 573 | count[i] += count[i-1] 574 | } 575 | 576 | for i := len(arr) - 1; i >= 0; i-- { 577 | index := (arr[i] / exp) % 10 578 | output[count[index]-1] = arr[i] 579 | count[index]-- 580 | } 581 | 582 | copy(arr, output) 583 | if frameGen != nil { 584 | frameGen(arr) 585 | } 586 | } 587 | 588 | // BitonicSort is an implementation of https://en.wikipedia.org/wiki/Bitonic_sorter 589 | func BitonicSort(arr []int, frameGen FrameGen) { 590 | bitonicSort(arr, 0, len(arr), 1, frameGen) 591 | } 592 | 593 | func bitonicSort(arr []int, low, cnt, dir int, frameGen FrameGen) { 594 | if cnt > 1 { 595 | k := cnt / 2 596 | bitonicSort(arr, low, k, 1, frameGen) 597 | bitonicSort(arr, low+k, k, 0, frameGen) 598 | bitonicMerge(arr, low, cnt, dir, frameGen) 599 | } 600 | } 601 | 602 | func bitonicMerge(arr []int, low, cnt, dir int, frameGen FrameGen) { 603 | if cnt > 1 { 604 | k := cnt / 2 605 | for i := low; i < low+k; i++ { 606 | if (arr[i] > arr[i+k]) == (dir == 1) { 607 | arr[i], arr[i+k] = arr[i+k], arr[i] 608 | if frameGen != nil { 609 | frameGen(arr) 610 | } 611 | } 612 | } 613 | bitonicMerge(arr, low, k, dir, frameGen) 614 | bitonicMerge(arr, low+k, k, dir, frameGen) 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /gsv_test.go: -------------------------------------------------------------------------------- 1 | package gsv 2 | 3 | import ( 4 | cryptoRand "crypto/rand" 5 | "testing" 6 | ) 7 | 8 | var visName string 9 | var sorterMap map[string]Sorter 10 | 11 | func init() { 12 | test = true 13 | 14 | sorterMap = map[string]Sorter{ 15 | "bogo": BogoSort, 16 | "bubble": BubbleSort, 17 | "cocktail": CocktailSort, 18 | "comb": CombSort, 19 | "counting": CountingSort, 20 | "cycle": CycleSort, 21 | "gnome": GnomeSort, 22 | "insertion": InsertionSort, 23 | "oddEven": OddEvenSort, 24 | "selection": SelectionSort, 25 | "sleep": SleepSort, 26 | "stooge": StoogeSort, 27 | "pancake": PancakeSort, 28 | "quick": QuickSort, 29 | "merge": MergeSort, 30 | "shell": ShellSort, 31 | "heap": HeapSort, 32 | "radix": RadixSort, 33 | "bitonic": BitonicSort, 34 | } 35 | } 36 | 37 | // StdoutVisualizer implements the Visualizer interface for stdout output 38 | type StdoutVisualizer struct{} 39 | 40 | func (sv *StdoutVisualizer) Setup(name string) { 41 | // No setup required for stdout 42 | } 43 | 44 | func (sv *StdoutVisualizer) AddFrame(arr []int) { 45 | WriteStdout(arr) 46 | } 47 | 48 | func (sv *StdoutVisualizer) Complete() { 49 | // No completion step required for stdout 50 | } 51 | 52 | func randomArray(n int, max int) []int { 53 | arr := make([]int, n) 54 | for i := 0; i < n; i++ { 55 | b := make([]byte, 1) 56 | cryptoRand.Read(b) 57 | number := float64(b[0]) 58 | arr[i] = int(number / 255 * float64(max)) 59 | } 60 | return arr 61 | } 62 | 63 | func makeVisualizer(name string) Visualizer { 64 | if name == "gif" { 65 | return &GifVisualizer{} 66 | } 67 | if name == "stdout" { 68 | return &StdoutVisualizer{} 69 | } 70 | return nil 71 | } 72 | 73 | func runSort(visName string, arr []int, algo string, sortFunc Sorter) { 74 | visualizer := makeVisualizer(visName) 75 | visualizer.Setup(algo) 76 | 77 | sortFunc(arr, visualizer.AddFrame) 78 | visualizer.Complete() 79 | } 80 | 81 | func Test_GIF(t *testing.T) { 82 | Max = 9 83 | Count = 9 84 | Mode = 2 85 | 86 | runSort("gif", randomArray(Count, Max), "selection", SelectionSort) 87 | 88 | Mode = 1 89 | 90 | for k, v := range sorterMap { 91 | t.Log(k) 92 | runSort("gif", randomArray(Count, Max), k, v) 93 | } 94 | 95 | t.Log("finish") 96 | } 97 | 98 | func Test_STDOUT(t *testing.T) { 99 | Max = 9 100 | Count = 9 101 | Mode = 1 102 | 103 | for k, v := range sorterMap { 104 | t.Log(k) 105 | runSort("stdout", randomArray(Count, Max), k, v) 106 | } 107 | 108 | t.Log("finish") 109 | } 110 | 111 | // go test -bench=. 112 | func Benchmark_bogo_sort(b *testing.B) { benchmarkSort("bogo", b) } 113 | func Benchmark_bubble_sort(b *testing.B) { benchmarkSort("bubble", b) } 114 | func Benchmark_cocktail_sort(b *testing.B) { benchmarkSort("cocktail", b) } 115 | func Benchmark_comb_sort(b *testing.B) { benchmarkSort("comb", b) } 116 | func Benchmark_counting_sort(b *testing.B) { benchmarkSort("counting", b) } 117 | func Benchmark_cycle_sort(b *testing.B) { benchmarkSort("cycle", b) } 118 | func Benchmark_gnome_sort(b *testing.B) { benchmarkSort("gnome", b) } 119 | func Benchmark_insertion_sort(b *testing.B) { benchmarkSort("insertion", b) } 120 | func Benchmark_oddEven_sort(b *testing.B) { benchmarkSort("oddEven", b) } 121 | func Benchmark_selection_sort(b *testing.B) { benchmarkSort("selection", b) } 122 | func Benchmark_sleep_sort(b *testing.B) { benchmarkSort("sleep", b) } 123 | func Benchmark_stooge_sort(b *testing.B) { benchmarkSort("stooge", b) } 124 | func Benchmark_pancake_sort(b *testing.B) { benchmarkSort("pancake", b) } 125 | func Benchmark_quick_sort(b *testing.B) { benchmarkSort("quick", b) } 126 | func Benchmark_shell_sort(b *testing.B) { benchmarkSort("shell", b) } 127 | func Benchmark_heap_sort(b *testing.B) { benchmarkSort("heap", b) } 128 | func Benchmark_merge_sort(b *testing.B) { benchmarkSort("merge", b) } 129 | func Benchmark_radix_sort(b *testing.B) { benchmarkSort("radix", b) } 130 | func Benchmark_bitonic_sort(b *testing.B) { benchmarkSort("bitonic", b) } 131 | 132 | // WriteNop is a writer for FrameGen that does nothing. 133 | // Ensures we only benchmark algorithms. 134 | func WriteNop(_ []int) {} 135 | 136 | func benchmarkSort(sort string, b *testing.B) { 137 | arr := randomArray(Count, Max) 138 | frameGen := FrameGen(WriteNop) 139 | if sortFunc, found := sorterMap[sort]; found { 140 | for n := 0; n < b.N; n++ { 141 | sortFunc(arr, frameGen) 142 | } 143 | } 144 | } 145 | 146 | // cloneArray Clones an array so source and the result are not backed by the same slice. 147 | func cloneArray(source []int) []int { 148 | destination := make([]int, len(source)) 149 | copy(destination, source) 150 | return destination 151 | } 152 | 153 | // TestCloneArray checks that cloneArray creates a separate copy and not a slice backed by the same array. 154 | func TestCloneArray(t *testing.T) { 155 | s := []int{1, 2, 3, 4, 5} 156 | d := cloneArray(s) 157 | 158 | if &s == &d { 159 | t.Error("Source and Destination address should not be equal") 160 | } 161 | for i := range s { 162 | if d[i] != s[i] { 163 | t.Errorf("Expected index [%d] to be the same", i) 164 | } 165 | if &d[i] == &s[i] { 166 | t.Errorf("Expected address of index [%d] to be different", i) 167 | } 168 | } 169 | } 170 | 171 | // BenchmarkConsistentArrayNoFramegen times the sort algorithms for the same array of random data 172 | // for each algorithm without the overhead of Frame generation. 173 | func BenchmarkConsistentArrayNoFramegen(b *testing.B) { 174 | arr := randomArray(1000, 750) 175 | for method, sortFn := range sorterMap { 176 | b.Run(method, func(b *testing.B) { 177 | for i := 0; i < b.N; i++ { 178 | arrCopy := cloneArray(arr) 179 | sortFn(arrCopy, nil) 180 | } 181 | }) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GolangSortingVisualization 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/simonwaldherr.de/go/golangsortingvisualization)](https://goreportcard.com/report/simonwaldherr.de/go/golangsortingvisualization) 4 | [![codebeat badge](https://codebeat.co/badges/c175babc-9113-40ab-8802-1cdb4b14d250)](https://codebeat.co/projects/github-com-simonwaldherr-golangsortingvisualization-master) 5 | 6 | this sorting visualization is not intended to recommend any algorithm, if you need a recommendation go [somewhere else](https://en.wikipedia.org/wiki/Sorting_algorithm#Comparison_of_algorithms). 7 | 8 | if you like, feel free to add more Sorting Algorithm examples. Many thanks to all [contributors](https://github.com/SimonWaldherr/GolangSortingVisualization/graphs/contributors). 9 | 10 | ## Sorting Algorithms 11 | 12 | ### BogoSort 13 | 14 | [![Bogo Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_bogo.gif)](https://en.wikipedia.org/wiki/Bogosort) 15 | 16 | ### BubbleSort 17 | 18 | [![Bubble Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_bubble.gif)](https://en.wikipedia.org/wiki/Bubble_sort) 19 | 20 | ### CocktailSort 21 | 22 | [![Cocktail Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_cocktail.gif)](https://en.wikipedia.org/wiki/Cocktail_shaker_sort) 23 | 24 | ### CombSort 25 | 26 | [![Comb Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_comb.gif)](https://en.wikipedia.org/wiki/Comb_sort) 27 | 28 | ### CountingSort 29 | 30 | [![Counting Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_counting.gif)](https://en.wikipedia.org/wiki/Counting_sort) 31 | 32 | ### CycleSort 33 | 34 | [![Cycle Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_cycle.gif)](https://en.wikipedia.org/wiki/Cycle_sort) 35 | 36 | ### GnomeSort 37 | 38 | [![Gnome Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_gnome.gif)](https://en.wikipedia.org/wiki/Gnome_sort) 39 | 40 | ### HeapSort 41 | 42 | [![Heap Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_heap.gif)](https://en.wikipedia.org/wiki/Heapsort) 43 | 44 | ### InsertionSort 45 | 46 | [![Insertion Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_insertion.gif)](https://en.wikipedia.org/wiki/Insertion_sort) 47 | 48 | ### MergeSort 49 | 50 | [![Merge Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_merge.gif)](https://en.wikipedia.org/wiki/Merge_sort) 51 | 52 | ### OddEvenSort 53 | 54 | [![OddEven Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_oddEven.gif)](https://en.wikipedia.org/wiki/Odd–even_sort) 55 | 56 | ### PancakeSort 57 | 58 | [![Pancake Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_pancake.gif)](https://en.wikipedia.org/wiki/Pancake_sorting) 59 | 60 | ### QuickSort 61 | 62 | [![Quick Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_quick.gif)](https://en.wikipedia.org/wiki/Quicksort) 63 | 64 | ### ShellSort 65 | 66 | [![Shell Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_shell.gif)](https://en.wikipedia.org/wiki/Shellsort) 67 | 68 | ### SelectionSort 69 | 70 | [![Selection Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_selection.gif)](https://en.wikipedia.org/wiki/Selection_sort) 71 | 72 | ### StoogeSort 73 | 74 | [![Stooge Sort Animation](https://simonwaldherr.github.io/GolangSortingVisualization/sort_stooge.gif)](https://en.wikipedia.org/wiki/Stooge_sort) 75 | 76 | ## HowTo 77 | 78 | ```sh 79 | ./start.sh 80 | ``` 81 | 82 | ```sh 83 | $ go run gsv.go --help 84 | Usage of gsv: 85 | -algo="bubble": Select sorting algorithm all/bogo/[bubble]/comb/counting/gnome/insertion/oddEven/selection/sleep 86 | -count=30: number of values 87 | -fps=10: frames per second 88 | -max=9: highest value 89 | -mode=1: visualization mode 90 | -vis="stdout": Select output: [stdout]/gif 91 | ``` 92 | 93 | ## License 94 | 95 | [MIT](https://github.com/SimonWaldherr/GolangSortingVisualization/blob/master/LICENSE) 96 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "INSERT A FRAMERATE [30]" 4 | read -t 10 FPS 5 | 6 | echo "INPUT A NAME OF A SORTING ALGORITHM" 7 | echo "all/bogo/comb/counting/cycle/sleep/cocktail/gnome/oddEven/stooge/insertion/shell/heap/bubble/selection/pancake/quick/merge" 8 | read -t 30 ALGO 9 | 10 | echo "SELECT OUTPUT MODE [stdout]/gif" 11 | read -t 15 OUTPUT 12 | 13 | if [ "x$FPS" == "x" ] 14 | then 15 | FPS=30 16 | fi 17 | 18 | if [ "x$ALGO" == "x" ] 19 | then 20 | ALGO="all" 21 | fi 22 | 23 | if [ "x$OUTPUT" == "x" ] 24 | then 25 | OUTPUT="stdout" 26 | fi 27 | 28 | go run demo/main.go -count=$(tput cols) -max=$(tput lines) -fps=$FPS -algo=$ALGO -vis=$OUTPUT 29 | --------------------------------------------------------------------------------