-
Notifications
You must be signed in to change notification settings - Fork 9
/
main.coffee
410 lines (316 loc) · 12.6 KB
/
main.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
[rectWidth, rectHeight] = [32, 32]
UPDATE_INTERVAL = 1250
# Initialised by reset().
colours = null
context = null
height = null
width = null
sort_context = null
# We set this after document is ready.
canvas = null
timeouts = []
defer = (fn) ->
timeouts.push(window.setTimeout(fn, 0))
toHslString = (h) ->
# Firefox doesn't like a semicolon here. Go figure!
"hsl(#{h}, 100%, 50%)"
initColours = ->
for x in [0...width] by rectWidth
for y in [0...height] by rectHeight
val = Math.random()
hue = Math.floor(256*val)
colours.push({ val: val, hue: hue, x: x, y: y })
context.fillStyle = toHslString(hue)
context.fillRect(x, y, rectWidth, rectHeight)
reset = ->
window.clearTimeout(timeout) while (timeout = timeouts.pop())
colours = []
win = $(document)
[width, height] = [$(document).width(), $(document).height()]
canvas.attr('width', width)
canvas.attr('height', height)
context = canvas[0].getContext('2d')
initColours()
sort_context = null
defer(sort)
swapRects = (ind1, ind2) ->
val1 = colours[ind1]
val2 = colours[ind2]
swap = (field) ->
tmp = val1[field]
val1[field] = val2[field]
val2[field] = tmp
swap('val')
swap('hue')
context.fillStyle = toHslString(val1.hue)
context.fillRect(val1.x, val1.y, rectWidth, rectHeight)
context.fillStyle = toHslString(val2.hue)
context.fillRect(val2.x, val2.y, rectWidth, rectHeight)
isort = ->
sort_context ?=
i: 1
j: 1
count: 0
{ i, j, count } = sort_context
swapRects(j - 1, j) if colours[j - 1].val > colours[j].val
if j == 1
return if i == colours.length - 1
sort_context.i++
sort_context.j = sort_context.i
else
sort_context.j--
sort_context.count++
if (sort_context.count % UPDATE_INTERVAL) == 0
defer(isort)
else
isort()
# Selection sort
# @author Bernhard Häussner (https://github.com/bxt)
ssort = ->
sort_context ?=
i: 1
j: 1
min: 0
count: 0
{ i, j, min } = sort_context
if j == colours.length
swapRects(i-1, min)
return if i == colours.length - 1
sort_context.i++
sort_context.min = sort_context.i - 1
sort_context.j = sort_context.i
else
sort_context.min = j if colours[j].val < colours[min].val
sort_context.j++
sort_context.count++
if (sort_context.count % UPDATE_INTERVAL) == 0
defer(ssort)
else
ssort()
# Good old bubble sort
bsort = ->
sort_context ?=
swapped: false
i: 1
count: 0
{ swapped, i } = sort_context
if i == colours.length
sort_context.i = 1
sort_context.swapped = false
bsort() if swapped
return
if colours[i - 1].val > colours[i].val
swapRects(i - 1, i)
sort_context.swapped = true
sort_context.i++
sort_context.count++
if (sort_context.count % UPDATE_INTERVAL) == 0
defer(bsort)
else
bsort()
qsort = (tukey) ->
sort_context ?=
median_stack: []
in_partition: false
pivot_ind: -1
pivot_val: -1
started_tukey_median: false
started_partition: false
from_stack: []
to_stack: []
from: 0
to: colours.length - 1
curr: 0
count: 0
{ median_stack, in_partition, pivot_ind, pivot_val, started_tukey_median, from_stack, to_stack, from, to, curr, started_partition } = sort_context
do_next = ->
sort_context.count++
if (sort_context.count % UPDATE_INTERVAL) == 0
defer(-> qsort(tukey))
else
qsort(tukey)
if median_stack.length > 0
[ a, b, c, swap_count ] = median_stack[median_stack.length - 1]
# Rename vars for clarity, as we want the median in a, not b.
m0 = b
m1 = a
m2 = c
if swap_count == 0
if colours[m1].val < colours[m0].val
swapRects(m1, m0)
# Increment count.
sort_context.median_stack[median_stack.length - 1][3]++
else
swap_count++
if swap_count == 1
if colours[m2].val < colours[m1].val
swapRects(m2, m1)
# Increment count.
sort_context.median_stack[median_stack.length - 1][3]++
else
swap_count++
if swap_count == 2
if colours[m1].val < colours[m0].val
swapRects(m1, m0)
else
swap_count++
sort_context.median_stack.pop() if swap_count >= 2
sort_context.count-- if swap_count > 2 # If we didn't swap then doesn't count
return do_next()
if in_partition
# We would have swapped pivot to end at start.
while curr < to
if colours[curr].val <= pivot_val
swapRects(curr, pivot_ind)
sort_context.curr++
sort_context.pivot_ind++
return do_next()
sort_context.curr++
curr = sort_context.curr
# End of partition.
sort_context.in_partition = false
# Swap 'em back.
swapRects(pivot_ind, to)
return do_next()
# OK we are out of the partition we are at the top level sort.
# If we're done on this line then pop stack on next.
if from >= to
return if from_stack.length == 0
sort_context.from = sort_context.from_stack.pop()
sort_context.to = sort_context.to_stack.pop()
sort_context.curr = sort_context.from
sort_context.pivot_ind = -1
sort_context.count-- # Shouldn't count towards swaps.
return do_next()
# Do we need to figure out the pivot index?
if pivot_ind == -1
mid = Math.floor(from + (to - from)/2)
# Easy option first.
if not tukey
# Do it this way to avoid overflow.
sort_context.pivot_ind = mid
pivot_ind = mid
else if started_tukey_median
# Completed tukey median calculation.
sort_context.pivot_ind = from
pivot_ind = from
sort_context.started_tukey_median = false
else
# OK kick off tukey median calculation.
sort_context.started_tukey_median = true
stack = []
# We do this last as stack so reverse order.
stack.push([from, mid, to - 1, 0])
if to - from > 40
s = Math.floor((to - from)/8)
# reverse order as stack.
stack.push([to - 1, to - 1 - s, to - 1 - 2 * s, 0])
stack.push([mid, mid - s, mid + s, 0])
stack.push([from, from + s, from + 2 * s, 0])
sort_context.median_stack = stack
# Handle this on next invocation.
sort_context.count-- # Shouldn't count towards swaps.
return do_next()
# OK we have a pivot index.
# Did we start the partition?
if not started_partition
sort_context.started_partition = true
sort_context.in_partition = true
sort_context.curr = from
sort_context.pivot_val = colours[pivot_ind].val
# Put pivot at the end of the array.
swapRects(pivot_ind, to)
sort_context.pivot_ind = from
# Handle this on next invocation.
return do_next()
# OK partition is done.
sort_context.started_partition = false
# Do lower half first, then push next on stack.
sort_context.to = pivot_ind - 1
sort_context.pivot_ind = -1
sort_context.from_stack.push(pivot_ind + 1)
sort_context.to_stack.push(to)
# Handle this on next invocation.
sort_context.count-- # Shouldn't count towards swaps.
return do_next()
# Heapsort
# Based on this Java implementation: http://git.io/heapsort
# @author Bernhard Häussner (https://github.com/bxt)
hsort = ->
sort_context ?=
j: colours.length // 2 - 1
last_j: colours.length // 2 - 1
size: colours.length
count: 0
making_heap: true
sifting_down: false
{ j, last_j, size, count, making_heap, sifting_down } = sort_context
if making_heap or sifting_down
left = j*2 + 1
right = j*2 + 2
largest = j
largest = left if left < size and colours[left].val > colours[j].val
largest = right if right < size and colours[right].val > colours[largest].val
if j isnt largest
swapRects(j, largest)
sort_context.j = largest
else if making_heap and last_j > 0 #last_j < size//2 - 1
#sort_context.j = last_j + 1
sort_context.j = last_j - 1
sort_context.last_j = sort_context.j
else
sort_context.making_heap = false
sort_context.sifting_down = false
else
return if size == 0
# Put largest block at end.
swapRects(0, size - 1)
# Now heapify the rest.
sort_context.size--
sort_context.sifting_down = true
sort_context.j = 0
sort_context.count++
if (sort_context.count % UPDATE_INTERVAL) == 0
defer(hsort)
else
hsort()
# Default to selection sort
sort = ssort
$(document).ready(->
canvas = $('#mainCanvas')
$('#squareSize').val(rectWidth)
window.onresize = -> reset()
$('#algo').change(->
selected = $('#algo').val()
switch $(@).children(':selected').attr('id')
when 'ssort'
rectWidth = 32
sort = ssort
when 'bsort'
rectWidth = 32
sort = bsort
when 'isort'
rectWidth = 32
sort = isort
when 'qsort1'
rectWidth = 10
sort = -> qsort(false)
when 'qsort2'
rectWidth = 10
sort = -> qsort(true)
when 'hsort'
rectWidth = 10
sort = hsort
rectHeight = rectWidth
$('#squareSize').val(rectWidth)
reset()
)
$('#squareSize').change(->
n = parseInt($('#squareSize').val(), 10)
return if isNaN(n)
rectWidth = rectHeight = n
reset()
)
$('#reset').click(-> reset())
reset()
)