Show HN: Write a sort, watch it go
visualsort.appspot.com
visualsort.appspot.com
pos = [0 ... VA.length]
rpos = [0 ... VA.length]
swap = (i, j) ->
VA.swap i, j
tmp = rpos[j]; rpos[j] = rpos[i]; rpos[i] = tmp
pos[rpos[i]] = i; pos[rpos[j]] = j
j = 0
for i in [0 ... VA.length]
v = VA.get(i)
do (v, i) ->
setTimeout ->
# Move the element that was originally in position i to position j
swap pos[i], j++
VA.play()
, 100 * v sort = (begin, end, bit) ->
i = begin
j = end
mask = 1 << bit
while i < j
while i < j and !(VA.get(i) & mask)
++i
while i < j and (VA.get(j - 1) & mask)
--j
if i < j
VA.swap(i++, --j)
if bit and i > begin
sort(begin, i, bit - 1)
if bit and i < end
sort(i, end, bit - 1)
sort(0, VA.length, 30)I thought it was a bit clearer to understand what it was doing when I stuck
VA.locals.bit = bit
at the very top of your sort function. for x in [0 ... VA.length]
ind = VA.get(x)-1
VA.swap(x, ind + VA.length)
for x in [0 ... VA.length]
VA.swap(x, x + VA.length) for x in [0 ... VA.length]
ind = VA.get(x)-1
while ind != x
VA.swap(x, ind)
ind = VA.get(x)-1Did someone solve the halting problem while I wasn't looking?
s = (lo, hi) ->
if lo==hi
return
if lo+1==hi
if VA.gt(lo,hi)
VA.swap(lo,hi)
return
mid=Math.floor(lo+(hi-lo)/2)
s(lo,mid)
s(mid+1,hi)
mid++
while lo<=mid && mid<=hi
if VA.gt(lo,mid)
VA.insert(mid, lo)
mid++
else
lo++
s(0,VA.length) tmp = VA.length
merge = (start1, start2, end2) ->
out = start1
end1 = start2 - 1
for x in [tmp+out .. tmp+end2]
if start1 <= end1 && start2 <= end2
if VA.lt(start1, start2)
VA.swap(start1, x)
++start1
else
VA.swap(start2, x)
++start2
else if start1 <= end1
VA.swap(start1, x)
++start1
else
VA.swap(start2, x)
++start2
for x in [out..end2]
VA.swap(x, tmp+x)
mergesort = (left, right) ->
if (right - left) == 1
if VA.gt(left, right)
VA.swap(left, right)
else if (right - left) == 0
/* do nothing */
else
split = Math.floor((right-left)/2) + left;
mergesort(left, split - 1)
mergesort(split, right)
merge(left, split, right)
mergesort(0, VA.length - 1)I'm not sure if this possible or relevant to record based on how Javascript engines compile and work, but it would seem interesting to know.
Instead of actually doing the operations, your sorting code outputs a list of actions to perform, which is then displayed on the webpage.
1. Allow for the array to contain values outside of the range of [1..length]. Ideally, allow for duplicate values as well. Since you control the swap and insert operations, you can maintain two separate sets, one of the actual numbers, and one of the "normalized" values that you display as your graph.
2. Give us an operation to highlight a set of lines and have the highlights persist. I wanted to take mayoff's radix-exchange sort and tweak it to highlight the "working set" that it's currently sorting, but there's no way to do that. I'm thinking here that we just need one function VA.persistHighlight() which takes start and end indices and highlights them, and persists those highlights until a new call to VA.persistHighlight(). The "transient" highlights would be layered on top of the persistent one.
2. Fantastic idea. This is exactly what I needed when I was struggling to write a quicksort from memory. Its in there now.
Heapsort:
fix_heap = (y, size) ->
loop
y1 = 2*y+1
y2 = 2*y+2
if y1 >= size then break
if !(y2 < size) || VA.gt(y1, y2)
if VA.lt(y, y1)
VA.swap(y, y1)
y = y1
else
break
else
if VA.lt(y, y2)
VA.swap(y, y2)
y = y2
else
break
pull_up = (y) ->
loop
y0 = (y-1) >> 1
if y == 0 || VA.lt(y, y0) then break
VA.swap(y0, y)
y = y0
for x in [1 ... VA.length]
pull_up(x)
for x in [VA.length-1 ... 0] by -1
VA.swap(x, 0)
fix_heap(0, x) fix_heap = (y, size) ->
loop
y1 = 2*y+1
if y1 >= size then break
if y1 + 1 < size && VA.gt(y1 + 1, y1)
y1++
if VA.lt(y, y1)
VA.swap(y, y1)
y = y1
else
break
for x in [VA.length >> 1 ... -1] by -1
fix_heap(x, VA.length)
for x in [VA.length-1 ... 0] by -1
VA.swap(x, 0)
fix_heap(0, x)When a swap costs the same as a compare, bubble sort is as fast as the other N^2 sorts.
A minor tweak is (changed lines marked with #):
bubbleSort = ->
VA.locals.swapped = true
y = VA.length # grab initial length
while VA.locals.swapped
y-- # shorten sorted portion on each pass
VA.locals.swapped = false
for x in [0...y] # only iterate to y
VA.locals.x = x
if VA.gt(x, x + 1)
VA.swap(x, x + 1)
VA.locals.swapped = true
bubbleSort() VA = [4, 2, 5, 7];
for(x=0; x<VA.length; x++) {
var y = VA[x];
setTimeout('console.log(' + y + ')', y*1000);
}
it tells me I can't use "var". And when I remove that I get some syntax error.
Any nice alternative way to implement a sleep sort in coffescript? VA = [4, 2, 5, 7]
for y in VA
setTimeout('console.log(' + y + ')', y*1000) VA = [4, 2, 5, 7]
for y in VA
setTimeout "console.log(#{y})", y*1000And to segue into my own area of interest with sorting, it would be awesome to see cache and branch behaviour too (though of course this running in the browser, it's not exactly the real behaviour, but still).
By branch behaviour, I mean branch prediction. I did some research (http://paulbiggar.com/research/#sorting-tr) before that showed that branch prediction is really important for sorts. For example, insertion sort is way better than selection sort, and radix sort has really really good branch prediction properties, making it beat quicksort (well, this is LSB radixsort, and I think you implemented MSB radixsort, but no doubt it applies somewhat).
So for the branch simulation, implement a 2 bit dynamic saturating counter, and note the number of mispredictions. There are simpler and more complex predictors, but that's probably the simplest that's a reasonable approximation of real life.
for x in foreach([1...VA.length], "for1")
y = 0
while branch(VA.gt(x, y), "while1")
y++
if branch(y == x, "if1")
break
VA.insert(x, y)
Where the `branch`, `foreach` functions are identities over their first argument, and they use the second argument to to track branching for a given branch point (i.e. you must uniquely name each branch, foreach line). A foreach would be a branch run arg0.length + 1 times, with arg0.length true branches followed by a single false branch. Sound at all reasonable? pos = 1
while pos < VA.length
if (VA.gte(pos, pos-1))
pos++
else
VA.swap(pos, pos-1)
if (pos > 1)
pos--
else
pos++ shuffle = ->
for x in [0..VA.length]
VA.swap x, Math.floor(Math.random()*VA.length)
sort = ->
VA.play()
if !checkSorted()
shuffle()
setTimeout(sort, 10)
checkSorted = ->
for x in [0..VA.length-1]
if VA.gte(x, x+1)
return false
return true
sort()This allows "algorithms" like this:
for x in [0...VA.length]
while (VA.get(x) > x)
VA.swap(VA.get(x), x)Also, that's not going to work quite right - the values are [1..VA.length] (see the bottom of the page), while the indices are [0...VA.length].
http://corte.si/posts/code/visualisingsorting/index.html
I also love Robert Sedgewick's technique of using angle to encode array value. Animated and static examples here:
http://bl.ocks.org/1243323 http://mbostock.github.com/protovis/ex/sort.html
q = (lo, hi) =>
# highlight range
VA.persistHighlight([lo..hi])
p = VA.get(lo)
l = lo
r = hi
# test pivot position
t = false
while (l < r)
if VA.gt(l,r)
VA.swap(l,r)
t = !t
if t
r--
else
l++
if (l > lo)
q(lo, l - 1)
if (hi > l + 1)
q(l + 1, hi)
q(0,VA.length - 1) twoWayBubbleSort = ->
VA.locals.swapped = true
while VA.locals.swapped
VA.locals.swapped = false
for x in [0...VA.length - 1]
VA.locals.x = x
if VA.gt(x, x + 1)
VA.swap(x, x + 1)
VA.locals.swapped = true
for x in[(VA.length-1)..1]
VA.locals.x = x
if VA.gt(x - 1, x)
VA.swap(x - 1, x)
VA.locals.swapped = true
twoWayBubbleSort() stoogesort = (lo, hi) ->
if VA.lt(hi, lo)
VA.swap(lo, hi)
if hi - lo > 1
third = Math.floor((hi - lo + 1)/3)
stoogesort(lo, hi-third)
stoogesort(lo+third, hi)
stoogesort(lo, hi-third)
stoogesort(0, VA.length-1)
It's probably best to run this on an array that's smaller than 100 numbers...Some others have posted methods (random pivot, median of 3). The gnu libc qsort implementation is a good learning tool (it uses median of 3). I think CLRS has the guaranteed n log n version.
Pick median of three random elements as pivot; it's hard to pick wrong pivot every time unless your RNG always returns 7.
On the other hand, if you are sorting unsanitised input, then random is good, but it should be secure pseudo-random numbers if it's important to your security/performance whilst under attack.
VA.swap(left, Math.floor(Math.random() * (right - left + 1)) + left)
centre = (left + right) / 2;
if VA.lt(left, centre)
if VA.lt(centre, right)
VA.swap(left, centre)
else if VA.lt(left, right)
VA.swap(left, right)
else
if VA.lt(centre, right)
VA.swap(left, centre)
else if VA.lt(left, right)
VA.swap(left, right)