Programming Corner from Icon Newsletter 12


July 14, 1983; Icon Version 5

Solutions to the Problems in Newsletter 11

N Queens: Numerous solutions to the 8-queens problem have been presented to show different backtracking techniques. Icon lends itself nicely to such problems as illustrated by the following porgram.
procedure main()
    rite(q(1),q(2),q(3),q(4),q(5),q(6),q(7),q(8))
end
procedure q(c)
   suspend place(1 to 8,c)   # look for a row end
procedure place(r,c)
   static up, down, row

   initial {
      up := list(15,0)
      down := list(15,0)
      row := list(8,0)
      }
   if row[r] = down[r + c - 1] = up[8 + r - c] = 0
   then suspend row[r] <- down[r + c - 1] <-
      up[8 + r - c] <- r     # place if free end
The heart of this solution lies in the mutual goal-directed evaluation of q(1), q(2), ..., q(8). Each set of values for which these procedure calls mutually succeed corresponds to a solution. Note that all the queens are "equal".

Clearly, however, this type of solution does not lend itself to parameterization, since each of the q(i) must appear explicitly in the program.

One approach to the general problem is to use a list to contain the solutions and a hierarchical, recursive scheme in which each queen controls the invocation of the next one. Thus, the first queen dominates the second, and so on. A program of this kind, due to Steve Wampler, is
global n, solution
procedure main(x)\
   n := x[1]           # number of queens
   solution := list(n) # list of column solutions
   every q(1)          # start with queen in 1st col.
end

procedure q(c)
   local r
   static up, down, rows

   initial {
      up := list(2 * n - 1,0)
      down := list(2 * n - 1,0)
      rows := list(n,0)
      }

   every 0 = rows[r := 1 to n] = up[n + r - c] =
      down[ r + c - 1] & rows[r] <- up[n + r - c] <-
      down[r + c - 1] <- 1 do {
         solution[c] := r     # record placement
         if c = n then show() # all queens placed
         else q(c + 1)        # try to place next queen
      }
end
procedure show()
   every writes(!solution)
   write()
end
Here the value of n is supplied on the command line when the program is run. The version of the program given here is stripped down to conserve space. The complete program with error checking and a display of the queens on a chessboard is included in the Icon program library.

This approach can also be cast in terms of co-expressions, as illustrated by the following program by Steve Wampler:
global n, nthq, solution

procedure main(x)
   local i

   n := x[1]
   nthq := list(n + 2) # list of queens
   solution := list(n) # list of solutions
   nthq[1] := &main    # 1st queen is main routine
   every     i := 1 to n do    # 2 to n+1 are real queens
   placements nthq[i + 1] :=
      create q(i)      # one co-expression per column
   nthq[n + 2] := 
      create show( )   # n+2nd queen is display routine      @nthq[2]            # start with queen in col. 1
end

procedure q(c)
   local r
   static up, down, rows

   initial {
      up := list(2 * n - 1,0)
      down := list(2 * n - 1,0)
      rows := list(n,0)
      }

   repeat {
      every 0 = rows[r := 1 to n] = up[n + r - c] =
        down[r + c - 1] & rows[r] <- up[n + r - c] <-
        down[r + c - 1] <- 1 do { solution[c] :=
        r
      @nthq[c + 2]        # try to place next queen
      }
   @nthq[c]               # tell last queen try again
   }
end

procedure show()
   repeat {
      every writes(!solution)
      write()
      @nthq[n + 1] # tell last queen to try again
      }
end
A somewhat different form of solution -- and one that requires a better understanding of Icon -- is illustrated by the following solution to the n-rooks problem by Tom Slone.
global n

procedure main(x)
   n := x[1]             # number of rooks
   every show(rook(n))
end

procedure show(a)
   every writes(!a)
   write()
end

procedure rook(i)
   static a

   initial a := list(n)

   if i = 0 then return
   if i < n then suspend (a[i] := r()) & rook(i - 1)
   else suspend (a[i] := r()) & rook(i - 1) & a
end

procedure r()
   suspend place(1 to n)
end

procedure place(r)
   static col

   initial col := list(n,0)

   if col[r] = 0 then suspend col[r] <- r
end
A Puzzle: In the last Newsletter, the behavior of the following program was posed:
procedure main()
   write("abcde" ? {
      p() := "x"
      write(&subject) &subject
      }
   )
end

procedure p()
   suspend &subject[2:3]
end
In the first place, the argument of write in main is a scanning expression. The scanning expression consists of three sub-expressions. The first is a call to p that returns a substring of &subject. Since this is a substring of a global variable (&subject), it is not dereferenced when p returns, and the subsequent assignment changes the value of &subject to "axcde", which is written. The last sub-expression of the scanning expression is simply &subject; this is the result of the scanning expression and hence the argument of the outer write. Since &subject is a global variable, it is not dereferenced when it is produced by the scanning expression. Hence write gets the variable &subject. However, when the scanning expression returns, it restores the former value of &subject (in this case, the empty string, the initial default value of &subject). So, by the time write dereferences &subject, its value is the empty string. Thus, this program writes axcde followed by an blank line!

This example illustrates two things: (1) dereferencing is a serious language design problem, and (2) programmers may encounter some mysterious results if they write programs that rely on the side effects of assignment to global variables like &subject.

Superficially, this program looks like it ought to write axcde twice (actually, the write in the scanning expression got there as a result of trying to find out why the outer write produced a blank line). The problem can be solved by explicitly dereferencing &subject in the scanning expression, as in
procedure main()
   write("abcde" ? {
      p() := "x"
      write(&subject)
      .&subject
      }
      )
end
Now the value returned by the scanning expression is "axcde" -- the value of &subject before its former value is restored at the end of scanning.

New Problems

Scanning an Entire File: Since Icon has a string data type and many operations on strings, it is natural to process strings as whole objects, rather than character by character as in most lower-level languages. String scanning raises the operations on strings to an even higher level, providing a subject on which scanning functions operate, matching functions that move the position of attention in the subject, and backtracking to the previous position in case a matching function fails.

One annoying problem in trying to process strings as whole objects occurs in situations like lexical analysis, in which the object to be processed is really a file with interspersed line terminators. The result of read in Icon is a string consisting of a line up to a line terminator. Therefore it is natural to process files in terms of their lines. However, a construction to be processed may span several lines (comments are typical). Thus, the natural input to string scanning may not contain the whole string to be processed. Although it is possible to make the entire input into a single string, this is generally inefficient and often impractical.

There is a device that often can be used to overcome these problems. If an expected match fails (for example, searching for the closing token of a comment), the subject of scanning can be reset during scanning to be the next line of input. This is illustrated by the following program by Tom Slone for stripping "white space" out of Pascal programs:
procedure main()
   local line

   while line := read() do
      line ? {
         remwhite()
         write("" ~== tab(0))
         }
end

procedure remwhite()
   while tab(many(' \t')) | (="(*" & comment()) |
      (pos(0) & newline())
end

procedure comment()
   while not ="*)" do
      if pos(0) then
         newline()
      else move(1) & tab(many(~'*'))
  return
end

procedure newline()
   (&subject := read()) | stop("unexpected end-of-file.") return end
This technique is applicable only if the part of the subject already scanned can be discarded -- once a new value has been assigned to the subject, the old value of the subject is lost and the automatic backtracking in string scanning does not apply to it.

The program above is not complete -- it has at least two defects. Correcting these is left as an exercise.

Random Numbers: A linear congruence method is used for generating pseudo-random numbers, and elements of strings and structures. Thus, the operation ?x is valid for values of x of different types. The pseudo-random sequence is computed from the value of &random, which is initially zero and which changes with each use of ?x. &random also can be set, so that a sequence can be repeated or started at an arbitrary place. A problem arises, however, if two or more independent random sequences are needed -- the use of one inevitably affects the other - or does it?

For starters, write a procedure random() whose result sequence consists of the successive values of &random as they are produced by successive uses of ?x (note that is it not necessary to know how Icon actually performs the pseudo-random computation). Assume that there is no other use of random generation in the program in which random() is used.

Now remove the assumption, so that the result sequence produced by random() is not affected by uses of ?x elsewhere in the program.

Icon home page