Contents: Chronological Listing Of Exercises

July 2, 2010

Although Scheme is a great language for the algorithmic exercises that we usually examine, I wrote this program in Awk, because of its good support for text munging:

export PRAXIS=/home/phil/praxis

awk ' # exercises in chronological order (page "chron")

BEGIN { itemsperpage = 30; FS = "\n"; RS = ""
              monthname, ":") }

$1 ~ /^number\t[1-9][0-9]*$/ {
    for (i=1; i<=NF; i++)
        if (split($i, f, /\t/) == 2)
            val[f[1]] = f[2]

    out[++nitems] = \
    sprintf("<tr><td>%d</td><td>%02d %s %d</td>" \
            "<td><a href=\"/%d/%02d/%02d/%s/\">%s</a>: %s</td>" \
            "<td><a href=\"/%d/%02d/%02d/%s/\">exercise</a> " \
            "<a href=\"/%d/%02d/%02d/%s/%d/\">solution</a> " \
            "<a href=\"" \
            val["number"], val["pubday"], monthname[val["pubmon"]],
            val["pubyear"], val["pubyear"], val["pubmon"],
            val["pubday"], val["file"], val["title"],
            val["blurb"], val["pubyear"], val["pubmon"],
            val["pubday"], val["file"], val["pubyear"],
            val["pubmon"], val["pubday"], val["file"],
            val["soln"], val["codepad"]) }

# END { for (i=1; i<=nitems; i++) {
#           if (i % itemsperpage == 1)
#               printheader(i, nitems)
#           print ""; print out[i]
#           if (i % itemsperpage == 0 || i == nitems)
#               printfooter(i, nitems)
#           if (i % itemsperpage == 0 && i != nitems)
#               printseparator(i, nitems) } }

END { print "<table cellpadding=\"10\">"
      for (i=1; i<=nitems; i++) {
          print ""; print out[i] }
      print "</table>" }

function ceiling(n) { if (int(n) == n) return n; else return int(n) + 1 }

function printheader(i, nitems) {
    printf("%s", "<big><big>Page:&nbsp;")
    for (page=1; page<=ceiling(nitems/itemsperpage); page++) {
        if (ceiling(i/itemsperpage) == page) printf("&nbsp;%d", page)
        else printf("&nbsp;<a href=\"/chron/%d\">%d</a>", page, page) }
    print "</big></big>"; print ""; print "<table>" }

function printfooter(i, nitems) { print ""; print "</table>" }

function printseparator(i, nitems) { print ""; print "<!--nextpage-->\n" }

' $PRAXIS/ > $PRAXIS/pages/chron

The BEGIN action does some needed initialization. The main loop takes every record (maximal sequence of non-blank lines) beginning with the regular expression ^number\t[1-9][0-9]*$, splits the lines into name/value pairs stored in the val associative array, then builds an output line and stores it in the out associative array, incrementing the nitems variable as it does so. The END action writes the table header, the output lines in order, and the table footer. The program that creates the list of exercises in reverse chronological order is the same, except that the END action counts the i variable down from nitems to 1; it is not shown here.

Originally, I intended to put page breaks every thirty exercises, as in the original Contents page, but I decided instead to just put the entire listing on a single page, which is more convenient; after seeing the size of the permuted table of contents, it no longer seemed to matter that the chronological listing was too long. But I left the code to do that in the program source text, commented out, in case I change my mind some time in the future.

The code is available at


Pages: 1 2

2 Responses to “Contents: Chronological Listing Of Exercises”

  1. […] Praxis – Chronological Listing Of Exercises By Remco Niemeijer In today’s Programming Praxis exercise our goal is to replicate a script Phil wrote to generate chronological […]

  2. Remco Niemeijer said

    My Haskell solution (see for a version with comments):

    import Data.List
    import Data.List.Split
    import Text.Printf
    import Text.Regex.Posix
    toMonth :: Int -> String
    toMonth m = chunk 3 "JanFebMarAprMayJunJulAugSepOctNovDec" !! (m - 1)
    item :: [[String]] -> String
    item xs = printf
        "<tr><td>%s</td><td>%02s %s %s</td><td>%s: %s</td>\
        \<td>%s%s<a href=\"\">\
        (g "number") (g "pubday") (toMonth . read $ g "pubmon") (g "pubyear")
        (link "" (g "title")) (g "blurb") (link "" "exercise")
        (link ("/" ++ g "soln") "solution") (g "codepad")
        where g x = maybe "" last $ find ((== x) . head) xs
              link :: String -> String -> String
              link = printf "<a href=\"/%s/%02s/%02s/%s%s/\">%s</a>"
                     (g "pubyear") (g "pubmon") (g "pubday") (g "file")
    items :: String -> [String]
    items = map (item . map (splitOn "\t") . lines) .
            filter (=~ "^number\t[1-9][0-9]*$") . splitOn "\n\n"
    listing :: ([String] -> [String]) -> String -> String
    listing f xs = "<table cellpadding=\"10\">" ++ 
                   concat (f $ items xs) ++ "</table>"
    main :: IO ()
    main = do x <- readFile ""
              putStrLn $ listing id x
              putStrLn $ listing reverse x

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: