# "More Programming Pearls - Confessions of a Coder" by Jon Bentley
# Addison-Wesley, 1988. ISBN 0-201-11889-0, pp. 176-182.
# Appendix 2 - "A Subroutine Library"
#
# Transcribed manually by Arnold Robbins (skeeve!arnold). Therefore this
# should be redistributable...

# UTILITY ROUTINES AND SET ALGORITHMS


function swap(i, j,	t) {	# x[i] :=: x[j]
	t = x[i]; x[i] = x[j]; x[j] = t
}


function randint(l, u) {	# rand int in l..u
	return l + int((u-l+1)*rand())
}


function select(k,	l, u, i , t, m) {
		# post: x[1..k-1] <= x[k] <= x[k+1..n]
		# bugs: n**2 time if x[1]=...=x[n]
	l = 1; u = n
	while (l < u) {
		# x[1..l-1] <= x[l..u] <= x[u+1..n]
		swap(l, randint(l, u))
		t = x[l]
		m = l
		for (i = l+1; i <= u; i++) {
			# x[l+1..m] < t and x[m+1..i-1] >= t
			if (x[i] < t) swap(i, ++m)
		}
		swap(l, m)
		# x[l..m-1] <= x[m] <= x[m+1..u]
		if (m <= k) l = m+1
		if (m >= k) u = m-1
	}
}


function qsort(l, u,	i, t, m) {
		# post: sorted(l,u)
		# bugs: n**2 time if x[1]=...=x[n]
	if (l < u) {
		swap(l, randint(l, u))
		t = x[l]
		m = l
		for (i = l+1; i <= u; i++) {
			# x[l+1..m] < t and x[m+1..i-1] >= t
			if (x[i] < t) swap(i, ++m)
		}
		swap(l, m)
		# x[l..m-1] <= x[m] <= x[m+1..u]
		qsort(l, m-1)
		qsort(m+1, u)
	}
}


function isort(		i, j) {
		# post: sorted(1,n)
	for (i = 2; i <= n; i++) {
		# sorted(1, i-1)
		j = i
		while (j > 1 && x[j-1] > x[j]) {
			swap(j-1, j)
			j--
		}
	}
}

function siftup(l, u,	i, p) {
		# pre  maxheap(l,u-1)
		# post maxheap(l,u)
	i = u
	while (1) {
		# maxheap(l,u) except between
		#  i and its parent
		if (i <= l) break
		p = int(i/2)
		if (x[p] >= x[i]) break
		swap(p, i)
		i = p
	}
}


function siftdown(l, u,		i, c) {
		# pre  maxheap(l+1,u)
		# post maxheap(l,u)
	i = l
	while (1) {
		# maxheap(l,u) except between
		#  i and its parents
		c = 2*i
		if (c > u) break
		if (c+1 <= u && x[c+1] > x[c]) c++
		if (x[i] >= x[c]) break
		swap(c, i)
		i = c
	}
}


function hsort(		i) {
		# post sorted(1,n)
	for (i = int(n/2); i >= 1; i--)
		siftdown(i, n)
	for (i = n; i >= 2; i--) {
		swap(1, i); siftdown(1, i-1)
	}
}

function pqinit(i) {
	pqmax = i
	n = 0
}


function pqinsert(t) {
		# post t is added to set
	assert(n < pqmax)
	x[++n] = t
	siftup(1, n)
}

function pqextractmax(	t) {
		# pre  set isn't empty
		# post max is deleted and returned
	assert(n >= 1)
	t = x[1]; x[1] = x[n--]
	siftdown(1, n)
	return t
}


function ssearch(t,	i) {
		# post result=0     => x[1..n]  != t
		#      1<=result<=n => x[result] = t
	i = 1
	while (i <= n && x[i] != t) i++
	if (i <= n) return i; else return 0
}


function bsearch(t,	l, u, m) {
		# pre  x[1] <= x[2] <= ... <= x[n]
		# post result=0     => x[1..n]  != t
		#      1<=result<=n => x[result] = t
	l = 1; u = n
	while (l <= u) {
		# t is in x[1..n] => t is in x[l..u]
		m = int((l+u)/2)
		if	(x[m] < t) l = m+1
		else if (x[m] > t) u = m-1
		else return m
	}
	return 0
}


# TESTING ROUTINES


function genequal(	i) {	# fill x
	for (i = 1; i <= n; i++) x[i] = 1
}


function geninorder(	i) {	# fill x
	for (i = 1; i <= n; i++) x[i] = i
}


function scramble(	i) {	# shuffle x
	for (i = 1; i <= n; i++)
		swap(i, randint(i, n))
}


function assert(cond) {
	if (! cond) {
		errcnt++
		print "   >> assert failed <<"
	}
}


function checkselect(k,		i) {
	for (i = 1;    i < k; i++) {
		assert(x[i] <= x[k])
	}
	for (i = k+1; i <= n; i++) {
		assert(x[i] >= x[k])
	}
}


function checksort(	i) {
	for (i = 1; i < n; i++)
		assert(x[i] <= x[i+1])
}


function clearsubs(	i) {	# clear array x
	for (i in x) delete x[i]
}


function checksubs(	i, c) { # alters x
		# error if subscripts not in 1..n
	for (i = 1; i <= n; i++) delete x[i]
	for (i in x) c++
	assert(c == 0)
}

function sort() {	# call proper sort
	if	(sortname == "qsort") qsort(1, n)
	else if (sortname == "hsort") hsort()
	else if (sortname == "isort") isort()
	else print "invalid sort name"
}


function testsort(name,		i, nfac) {
	sortname = name
	print "  pathological tests"
	for (n = 0; n <= bign; n++) {
		print "   n=", n
		clearsubs()
		geninorder(); sort(); checksort()
		for (i = 1; i <= n/2; i++) swap(i, n+1-i)
		sort(); checksort();
		genequal(); sort(); checksort();
		checksubs()
	}
	print "  random tests"
	nfac = 1
	for (n = 1; n <= smalln; n++) {
		print "   n=", n
		nfac = nfac*n
		clearsubs()
		geninorder();
		for (i = 1; i <= nfac; i++) {
			scramble(); sort(); checksort()
		}
		checksubs()
	}
}


function search(t) {	# call proper search
	if	(searchname == "bsearch")
		return bsearch(t)
	else if	(searchname == "ssearch")
		return ssearch(t)
	else print "invalid search name"
}


function testsearch(name,	i) {
	searchname = name
	for (n = 0; n <= bign; n++) {
		print "   n=", n
		clearsubs()
		geninorder()
		for (i = 1; i <= n; i++) {
			assert(search(i)    == i)
			assert(search(i-.5) == 0)
			assert(search(i+.5) == 0)
		}
		genequal()
		assert(search(0.5) == 0)
		if (n > 0) assert(search(1) >= 1)
		assert(search(1) <= n)
		assert(search(1.5) == 0)
		checksubs()
	}
}



BEGIN {	# MAIN PROGRAM
bign = 12
smalln = 5
print "testing assert -- should fail"
	assert(1 == 0)

print "testing select"
	for (n = 0; n <= bign; n++) {
		print "   n=", n
		clearsubs()
		for (i = 1; i <= n; i++) {
			geninorder()
			select(i)
			checkselect(i)
		}
		for (i = 1; i <= n; i++) {
			scramble()
			select(i)
			checkselect(i)
		}
		genequal()
		for (i = 1; i <= n; i++) {
			select(i)
			checkselect(i)
		}
		checksubs()
	}

print "testing quick sort"
	testsort("qsort")
print "testing insertion sort"
	testsort("isort")
print "testing heap sort"
	testsort("hsort")


print "testing priority queues"
	for (m = 0; m <= bign; m++) {
		# m is max heap size
		print "   m=", m
		clearsubs()
		pqinit(m)
		for (i = 1; i <= m; i++)
			pqinsert(i)
		for (i = m; i >= 1; i--)
			assert(pqextractmax() == i)
		assert(n == 0)
		pqinit(m)
		for (i = m; i >= 1; i--)
			pqinsert(i)
		for (i = m; i >= 1; i--)
			assert(pqextractmax() == i)
		assert(n == 0)
		pqinit(m)
		for (i = 1; i <= m; i++)
			pqinsert(1)
		for (i = m; i >= 1; i--)
			assert(pqextractmax() == 1)
		assert(n == 0)
		n = m; checksubs()
	}


print "testing sequential search"
	testsearch("ssearch")
print "testing binary search"
	testsearch("bsearch")
print "total errors (1 expected):", errcnt
if (errcnt > 1) print ">>>> TEST FAILED <<<<"
}
