Friday, July 28, 2017

A First Example

A First Example

Each program language paradigm has some problems which are immediately suited to it. The problem of  finding all the permutations of a list of values is a good example of a problem especially suitable to rules. The operation frequently shows up in situations where we must search through all possibilities to find a solution.

Let's state the problem in simple terms. Given a list of values, say letters, find all the possible orders of the letters. We will use this problem to demonstrate some of the fundamental characteristics of production rule systems.

We begin by showing a list as a CLIPS fact. We have a word followed by a sequence of values. If the list is a1 2 3 4 5, then the clips fact may be as:  (list 1 2 3 4) The other list will be things like (list 2 3 4 1).  We must be given an initial lists and we will use the first mentioned.  The CLIPS construct deffacts collects facts that should be initialized every time we reset to begin operation anew. It's shown below:
 (deffacts list
       (list 1 2 3 4))

So each time we start, there will be a fact that a list has 1, ,2, 3 and 4. We know want to make all the other lists we can from this. The trick is just what you might do in your head. Find two items in the list and swap them.  CLIPS rules match named variables to parts of a fact. In our case we want some initial part $?A two items ?x and ?y and a final part $?B. In CLIPS speak, A will match 0 or more values at the beginning, x one, y one and $? B any zero or more remaining values in the list. The things to note are two. The first is that ?x and ?y capture two items somewhere in the list. Any and all remaining are either before these two ($?A) or after ($?B).  

(defrule permuteList
    (list $?A ?x ?y $?B)
=>
    (assert (list $?A ?y ?x $?B)))

The assert just creates a new fact using pieces of the old.  The rule system does redo the same rule with the same datum. The inference has already been done. However we have created a new datum.

But notice that there are several ways of matching (list $?A ?x ?y $?B) in (list 1 2 3 4). The possible values for ?x and ?y are 1 and 2 or 2 and 3 or 3 and 4. That means $?A and $?B are () and (3 4) or (1) and (4) or (1 2) and (4). The original fact may be matched in any of three ways. Each is an instantiation of the rule with the fact.. More correctly, a rule system never reuses an instantiation.

If you now imagine the various instantiations being fired. You should realize that different instantiations may repeat facts! Imagine the list with (list 1 2 3 4)  and $?A = (). It generates (list 2 1 3 4). But the rule can be instantiated with the new fact and $?A=() which would generate (list 1 2 3 4)!

Every instantiation will be fired, but no duplicates facts are recorded in normal operation. This is a policy called fact refraction. Why record the same fact multiple times?

If we rest and run the clip the code  description we have so far, the following facts are made:
f-0     (initial-fact)
f-1     (list 1 2 3 4)
f-2     (list 2 1 3 4)
f-3     (list 2 3 1 4)
f-4     (list 3 2 1 4)
f-5     (list 3 1 2 4)
f-6     (list 1 3 2 4)
f-7     (list 1 3 4 2)
f-8     (list 3 1 4 2)
f-9     (list 3 4 1 2)
f-10    (list 4 3 1 2)
f-11    (list 4 1 3 2)
f-12    (list 1 4 3 2)
f-13    (list 1 4 2 3)
f-14    (list 4 1 2 3)
f-15    (list 4 2 1 3)
f-16    (list 2 4 1 3)
f-17    (list 2 1 4 3)
f-18    (list 1 2 4 3)
f-19    (list 2 4 3 1)
f-20    (list 4 2 3 1)
f-21    (list 4 3 2 1)
f-22    (list 3 4 2 1)
f-23    (list 3 2 4 1)

f-24    (list 2 3 4 1)
The facts are numbered in the order the were recorded so their index is historical.

You should understand that each list fact, first to last, had three instantiations with the permute rule. All those were fired - i.e. executed. that is 24*3=72 rule instantiations fired, each trying to create a fact.  All but the 24 recorded were rejected as duplicates to the some earlier fact.Think of the last fact, everything it creates must be a duplicate.

If you did this manually you might use some discipline to make sure you get all possibilities. In rule based system there are three essential  disciplines for choosing instantiations to fire. They are depth, breadth and random strategies. Without specifying full details, depth tries to use the newest facts to get farther in a hurry. Breadth  tries to get everything out of the old facts before going on.  Random is - well random. Below you can see how the results differ from the depth strategy we used at first when we try depth or random strategies.

BREADTH
f-1     (list 1 2 3 4)
f-2     (list 1 2 4 3)
f-3     (list 1 3 2 4)
f-4     (list 2 1 3 4)
f-5     (list 1 4 2 3)
f-6     (list 2 1 4 3)
f-7     (list 1 3 4 2)
f-8     (list 3 1 2 4)
f-9     (list 2 3 1 4)
f-10    (list 1 4 3 2)
f-11    (list 4 1 2 3)
f-12    (list 2 4 1 3)
f-13    (list 3 1 4 2)
f-14    (list 3 2 1 4)
f-15    (list 2 3 4 1)
f-16    (list 4 1 3 2)
f-17    (list 4 2 1 3)
f-18    (list 2 4 3 1)
f-19    (list 3 4 1 2)
f-20    (list 3 2 4 1)
f-21    (list 4 3 1 2)
f-22    (list 4 2 3 1)
f-23    (list 3 4 2 1)

f-24    (list 4 3 2 1)

RANDOM
f-1     (list 1 2 3 4)
f-2     (list 2 1 3 4)
f-3     (list 1 2 4 3)
f-4     (list 2 1 4 3)
f-5     (list 1 4 2 3)
f-6     (list 2 4 1 3)
f-7     (list 4 1 2 3)
f-8     (list 2 4 3 1)
f-9     (list 4 2 3 1)
f-10    (list 4 2 1 3)
f-11    (list 2 3 4 1)
f-12    (list 3 2 4 1)
f-13    (list 3 2 1 4)
f-14    (list 3 4 2 1)
f-15    (list 4 3 2 1)
f-16    (list 4 3 1 2)
f-17    (list 3 4 1 2)
f-18    (list 2 3 1 4)
f-19    (list 3 1 2 4)
f-20    (list 3 1 4 2)
f-21    (list 1 3 4 2)
f-22    (list 1 4 3 2)
f-23    (list 1 3 2 4)
f-24    (list 4 1 3 2)

The moral of all this is: rules can be very concise but they imply a lot of action of the part of the system It's a little little like the difference between mathematics and programming. Knowing something can be done doesn't always mean there is a practicable way of doing it. The failure may be the in ability to describe it to a computer, or it may be the inability to do enough work in reasonable time.

For the sake of the curious., I have used CLIPS to write two more code examples. Each makes permutations. One is in the normal procedural paradigm, i.e. it give a definite series of steps, in terms of sequence, selection and iteration,  to be carried out. The second is a nearly pure functional code example. A pure functional language applies functions to arguments, possibly using the same function over (recursion) to get to an answer. Of course no practical language is purely any of these.  However for the curious I give the procedural and functional example below without comment.

PROCEDURAL

This was taken  from https://en.wikipedia.org/wiki/Heap%27s_algorithm  on July 27 2017

I transliterated the original code as nearly simply as I could. I allowed no function except the aset and aget which emulate array operations.  I used the flexibility of CLIPS identifiers to make suggestine names for array values. The comments in the CLIPS code show the original procedural code.

Even the original pseudocode is an excellent example of how data structures and sequence can clutter up. Can you find the idea coded there?

; fix indexing base and supply direct 'array' access operation
(deffunction aset (?Array ?inx ?value)
    (replace$ ?Array (+ ?inx 1)(+ ?inx 1) ?value))

(deffunction aget (?Array ?inx)
    (nth$ (+ ?inx 1) ?Array))



(deffunction proceduralPermute(?n $?A)     ;procedure generate(n : integer, A : array of any):
                                                 ;    c : array of int
    (bind $?C (create$))                                           ;    for i := 0; i < n; i += 1 do
    (loop-for-count ?n                                              ;       c[i] := 0
(bind $?C (create$ $?C 0))
    )                                                                            ;    end for
    
    (printout t $?A crlf)                                             ;    output(A)

    (bind ?i 0)                                   ;    i := 0;
    (while (< ?i ?n)                                                     ;    while i < n do
    (if (< (aget $?C ?i) ?i)                                           ;        if  c[i] < i then
     then
       (if (= (mod ?i 2) 0)                                              ;            if i is even then
        then
          (bind ?A[0] (aget $?A 0))                               ;                swap(A[0], A[i])
          (bind ?A[i] (aget $?A ?i))
          (bind $?A (aset $?A  0 ?A[i]))
          (bind $?A (aset $?A ?i ?A[0]))
        else                                                                    ;   else
             (bind ?c[i] (aget $?C ?i))                            ;    swap(A[c[i]], A[i])
            (bind ?A[c[i]] (aget $?A ?c[i]))  
            (bind ?A[i] (aget $?A ?i))
            (bind $?A (aset $?A ?c[i] ?A[i]))
            (bind $?A (aset $?A  ?i ?A[c[i]]))
        )                                                                         ;   end if
       (printout t $?A crlf)                                           ; output(A)
       (bind $?C (aset $?C ?i (+ (aget $?C ?i) 1)))    ;    c[i] += 1
       (bind ?i 0)                                                          ;     i := 0
    else                                                                        ;else
    (bind $?C (aset $?C ?i 0))                                   ;     c[i] := 0
    (bind ?i (+ ?i 1))                                                    ;   i += 1
    )                                                                             ;  end if
  )                                                                               ;end while
)


FUNCTIONAL

For simplicity I do not try to return the list or check that an item is in the list. . Progn$ is doing the work of a map function. The rest is obvious? The permutations will come out in counting order!

; assuming ?item is in the list(s) 
(deffunction remove$ (?item $?List)
   (delete$ ?List (member$ ?item $?List) 
                  (member$ ?item $?List))
)

(deffunction all-permutations (?sofar $?list)
  (if (= (length$ $?list) 0) 
   then (printout t ?sofar crlf)
   else (progn$ (?x $?list)
           (all-permutations (create$ ?sofar ?x)
                             (remove$ ?x $?list)))))

(deffunction permute ($?List)
    (all-permutations (create$) $?List))
               

Wednesday, July 26, 2017

Valentines Logic Problem

Valentines

Now we begin a second problem from the same site. The problem is to figure out the first name, thw color of valentine each of three young ladies received and from whom they thought it came. The full problem is here. Below we list the relevant constraints from the web site:

1. Unluckily for Freddie, when Miss Jetson received her card, she thought it was from Adam.

2. When Molly received her blue coloured card, she told Miss Hanson and together they worked out who the card was from. It didn't occur to either of them that it was from Freddie!

3. The girl who received the red card was convinced it came from Ethan.

4. Neither Millie nor Miss Motson received a pink card.


Note that poor Fred nevers figures at at in our deduction! The girls, their first names. the card colors and the senders can be represented by a few facts.

(domain last Hanson Jetson Motson)
(domain first Lilly Millie Molly)
(domain color blue pink red)
(domain sender Adam Dylan Ethan)

Since there are but three ladies, three  first names, three colors and three senders, the number of possibilities are few. If we let the order of  last names of the ladies be fixed, then any order of the other domains can represent a choice for the ladies first name, card color or her beau. The possible orders are just the permutation of the domain’s values. The code below permutes domain values as long as the name of the domain is not last.  (note that the phrase “&~last“ tells CLIPS not to match the foregoing variable with the word last.):

(defrule permuteDom
   (domain ?dom&~last $?X ?x ?y $?Y)
=>
   (assert (domain ?dom $?X ?y ?x $?Y)))

The result of running our code so far gives all possible orders or domain values relative to the last names:

CLIPS> (facts)
f-0     (initial-fact)
f-1     (domain last Hanson Jetson Motson)
f-2     (domain first Lilly Millie Molly)
f-3     (domain color blue pink red)
f-4     (domain sender Adam Dylan Ethan)
f-5     (domain sender Dylan Adam Ethan)
f-6     (domain sender Dylan Ethan Adam)
f-7     (domain sender Ethan Dylan Adam)
f-8     (domain sender Ethan Adam Dylan)
f-9     (domain sender Adam Ethan Dylan)
f-10    (domain color pink blue red)
f-11    (domain color pink red blue)
f-12    (domain color red pink blue)
f-13    (domain color red blue pink)
f-14    (domain color blue red pink)
f-15    (domain first Millie Lilly Molly)
f-16    (domain first Millie Molly Lilly)
f-17    (domain first Molly Millie Lilly)
f-18    (domain first Molly Lilly Millie)
f-19    (domain first Lilly Molly Millie)

A possible world now is represented by one choice of  order for each of first, color and guy.  The rule below below creates a world by assembling one order of first name, one of color and one of sender into a single world fact.

(defrule makeWorld
  (domain last $?L)
  (domain first $?F)
  (domain color $?C)
  (domain sender $?S)
=>
  (assert (world L: $?L F: $?F C: $?C S: $?S)))

But the basic operation of a rule based system is to apply the rule every way possible so all combinations will get tried.

In fact there are 6x6x6 or 216 distinct possible worlds. A lot of facts for a person, but few for a computer.Each world represents a choice of values in a standard logic problem like a solving grid. That’s too many to show them all, but we give two examples below for reference:


CLIPS> (facts 100 101)
f-100   (world L: Hanson Jetson Motson F: Molly Millie Lilly C: red blue pink S: Ethan Dylan Adam)
f-101   (world L: Hanson Jetson Motson F: Molly Millie Lilly C: red blue pink S: Dylan Ethan Adam)

Fact 101 is interpreted as      
  1. Hanson ….    Molly …   red … Dylan
  2.         Jetson   …  Milly …  blue  ... Ethan
  3.               Motson ... Lilly …  pink …   Adam
                                      
Now we need to eliminate the unreal possible worlds given the constraints. We will use rules this time rather than facts. Rules let one use the full matching, logical and arithmetic powers of the language. We consider rules to enforce the implicit constraints given to reject worlds one by one until (hopefully) one is left as the only possible world.

1. Unluckily for Freddie, when Miss Jetson received her card, she thought it was from Adam.

We know Miss Jetson’s place so we match the corresponding place in the sender information. If Adam is not there, we retract the world. Notice how we ask CLIPS to remember the fact’s index so we may retract it later:

(defrule JetsonFromAdam
?f<-(world L: $? F: $? C: $? S: ? ~Adam ?)      
=>
  (retract ?f))

We could have used an even simpler pattern (world $? ~Adam ?)  but the different pieces of the world fact will be useful later.

Now consider the second constraint of the problem: “When Molly received her blue coloured card, she told Miss ;Hanson and together they worked out who the card was from….”Ffrom this we know:
  1. Molly’s card is blue
  2. Hanson is not Molly
  3. Hanson’s card is not blue

We know just where to find Hansons data values. They are first in each tupple. We can use the nth$ CLIPS function to pick out the first value in any list. So if we just get the correcct value out of a list, we test to see whether is is Molly or blue. For constraint two we test the value retrieved from the list to see if it is Molly - a reject. For constraint 3 we do the same but with colors.

(defrule HansonNotMolly
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test  (eq (nth$ 1 $?F) Molly))
=>
  (retract ?f))

(defrule HansonNotBlue
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test (eq (nth$ 1 $?C) blue))
=>
  (retract ?f))

Constraint 1 is a little more complex. Worlds have Molly in different places but they cannot have blue in the same place. So we have to find where in the first names list has Molly. Then we can look at the corresponding value in the colors list. The (member$ ?what $?list) function looks in the  list a tells a what location (1,2 …) it first found the ?what value. Since Molly must be there somewhere, we don’t have to worry about member$ answering FALSE. We will reject worlds where the color value in Molly’s place is not blue. This more complex rule looks like this:

(defrule MollyBlue
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test (neq    (nth$    (member$ Molly $?F)    $?C)        blue))
=>
  (retract ?f))

Now we need to fill out the constraint. However the means we have used so far will work. If a constraint uses a last name, where know where its values are. Check the value in the corresponding place. If the constraint requires something other than last name, find the first value and use its location to get and inspect the second domain value.

My complete list of rules is in this section notes. However it might be more fun to write them yourself first. Here are the rules I wrote for constraints 3 & 4. Be careful about when to use eq and when to use neq!:

;3. The girl who received the red card was convinced it came ;from Ethan.
(defrule RedMustBeEthan ...

;4. Neither Millie nor Miss Motson received a pink card.
(defrule MotsonNotMillie ...

(defrule MillieNotPink ...

(defrule MotsonNotPink ...
Running all my rules, the only world fact that remained was the correct answer. The last fact remaining was: f-213   (world L: Hanson Jetson Motson F: Millie Lilly Molly C: red pink blue S: Ethan Adam Dylan) Of course, you can now read the answers tight off the fact!




Notes

Marathon

Here is the complete set of facts gleaned directly from the Brain Basher’s marathon information:

  1. (ahead LL TT)(ahead BB TT)(ahead TT MM)
  2. (ahead PP DD) (ahead PP LL)
  3. (ahead PP SS)(ahead SS JJ)(ahead SS HH)
  4. (ahead PP KK)(ahead MM KK)(ahead TT KK)
  5. (ahead BB LL)(ahead DD LL)(ahead LL JJ)(ahead LL MM)
  6. (ahead SS MM)(ahead BB MM)
  7. (ahead BB JJ) (ahead BB MM) (ahead BB PP)
  8. (ahead DD KK)(ahead DD TT)(ahead SS DD)
  9. (ahead JJ KK)(ahead JJ TT)(ahead JJ MM)(ahead PP JJ)(ahead DD JJ)
  10. (ahead HH MM)(ahead LL HH)(ahead JJ HH)(ahead TT HH)


Valentine Constraint Rules Notes


;3. The girl who received the red card was convinced it came ;from Ethan.
(defrule RedMustBeEthan
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test (neq (nth$ (member$ red $?C) $?S) Ethan))
=>
  (retract ?f))

;4. Neither Millie nor Miss Motson received a pink card.
(defrule MotsonNotMillie
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test (eq (nth$ 3 $?F) Millie))
=>
  (retract ?f))

(defrule MillieNotPink
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test (eq (nth$ (member$ Millie $?F) $?C) pink))
=>
  (retract ?f))

(defrule MotsonNotPink
?f<-(world L: $?L F: $?F C: $?C S: $?S)
   (test (eq (nth$ 3 $?C) pink))
=>
  (retract ?f))


Making the Snail Puzzle

The snail puzzle asked us to find river names in a seemingly random sequence of letters. The solution was a little messy and required a lot of search. It was surprisingly hard. The original puzzle was further complicated by the letters being written on the spirals of a snail shell. For reference we show the original sequence of letters and a matching set of river names below.

A O E M U A R Z O P N T I N S D H H I N A E A L R E R M O L I C E N I A O N T S E G E S

Thames Seine Orinoco Nile Euphrates Darling Amazon


Creating such a puzzle is quite easy. The crux is randomizing the location of letters from the names. This is easy if we forget about the letters in a word and just represent each word by repeating an id number for each letter. We may then permute the numbers without worrying about spelling order. When we have a sufficiently random order we can just replace each number front to back by the next letter from the word with that id. Here's how it goes:

First we define the words giving each an id number. At the same time we define and empty text.

(deffacts rivers
  (river  1 T h a m e s)
  (river  2 S e i n e)
  (river  3 O r i n o c o)
  (river  4 N i l e)
  (river  5 E u p h r a t e s)
  (river  6 D a r l i n g)
  (river  7 A m a z o n)
  (text #)
)

Now to face the randomization problem. We can overlook the word's letter order if we let the word be represented by a copy of its id for each original letter:

(defrule addRiver
    (BUILD)
?f<-(text # $?T)
    (river ?i $?R)
    (test (not (member$ ?i $?T)))
=>
    (retract ?f)
    (loop-for-count (length$ $?R)
       (bind $?T (create$ $?T ?i)))
    (assert (text # $?T)))

When this rule is repeated with each individual river, we get a 'text' factthat looks like this:

f-16    (text # 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1)

That is, it shows where the letters of word n are to be placed. Now we randomly permute those to new locations:

(defrule randomize
    (RANDOMIZE)
?f<-(text # $?A ?x $?B ?y $?C)
=>
    (retract ?f)
    (assert (text # $?A ?y $?B ?x $?C)))

This rule swaps to digits from the text fact. If we use depth or breadth strategies nothing will change! We can test this by asserting RANDOMIZE and firing the rule 10 times by giving the command "(run 10)" The result under DEPTH is below. (Try breadth too if you like):

CLIPS> (assert (RANDOMIZE))
<Fact-17>
CLIPS> (run 10)
CLIPS> (facts)
f-0     (initial-fact)
f-1     (river 1 T h a m e s)
f-2     (river 2 S e i n e)
f-3     (river 3 O r i n o c o)
f-4     (river 4 N i l e)
f-5     (river 5 E u p h r a t e s)
f-6     (river 6 D a r l i n g)
f-7     (river 7 A m a z o n)
f-9     (BUILD)
f-17    (RANDOMIZE)
f-27    (text # 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1)

This happens because depth and breadth strategies operate in disciplined ways. They always begin with the leftmost (or right most) CHECK WHICH IS WICH pattern match. Unfortunately, that means the first 7's or the last 1'1, so the new version of the 'text' is the same as before. And then we repeat.

The solution is to insist on a random strategy. Instead of continually selecting the same instantiation, we select a random instantiation.  This works immediately.

CLIPS> (set-strategy random)
depth
CLIPS> (run 10)
CLIPS> (facts)
f-0     (initial-fact)
f-1     (river 1 T h a m e s)
f-2     (river 2 S e i n e)
f-3     (river 3 O r i n o c o)
f-4     (river 4 N i l e)
f-5     (river 5 E u p h r a t e s)
f-6     (river 6 D a r l i n g)
f-7     (river 7 A m a z o n)
f-9     (BUILD)
f-17    (RANDOMIZE)
f-37    (text # 7 7 7 2 7 2 5 4 6 6 5 6 6 6 6 2 5 5 2 5 5 5 4 7 4 4 3 3 3 3 3 3 5 6 5 2 7 3 1 1 1 1 1 1)
For a total of 11 facts.
CLIPS>

In the final procedure we will fire the randomize rule 100 times. That will give us a quite jumbled version of the digits in text. Now we just need to restore the letters of the river names.  This is where the previously unmentioned "#" symbol comes into play. We we use it to separate the prefix of the text which has be 'decoded' from the tail which has not. Perhaps the rule will make this clear. (No I didn't think of it at first either!)

(defrule addRiverLetter
    (DECODE)
?f<-(text $?PRFX # ?n $?SFFX)
?g<-(river ?n ?c $?Rest)
=>
    (retract ?f ?g)
    (assert (text $?PRFX ?c # $?SFFX)
            (river ?n $?Rest)))

This rule uses the river for the next id to find the next  decoded letter. The text is changed to a new longer prefix and the currently first of the remaining river letters is dropped. The next number to be decoded is always just after the #. Such a rule will run as long as there is a digit for ?n and a matching river with a letter for ?c. But we made one digit for each letter and just messed then about.

One last point has been overlooked. Each of the rules has an extra fact that is not important to the actions of the rule. They are there to stage the use of the rules. That is, we want to make full digitized 'text,  then scramble the digits, then decode the result. These staging fact allows to control when a rule may operate by asserting or retracting the corresponding facts.

Recall that we also need to be sure we use random strategy during randomization. The entire package is complex enough to deserve a procedure (in CLIPS a function) of its own:

(deffunction makePuzzle ()
   (reset)
   (bind ?f (assert (BUILD)))
   (run)
   (retract ?f)
   (bind ?f (assert (RANDOMIZE)))
   (bind ?s (set-strategy random))
   (run 100)
   (retract ?f)
   (set-strategy ?s)
   (assert (DECODE))
   (run)
)

Here the process. Start by restoring the original river and text data. Now assert the build fact remembering as ?f which fact it is.  Run until the text is created.  The system will stop at that point. Be neat. Retract the build fact before asserting the randomize fact. Set the strategy to random while remembering the old strategy as ?s. Randomize by letting the randomize rule run 100 times.  Remove the randomize fact to prevent any further changes. Be courteous, Restore the original strategy.  Now assert decode and finish. We get get a random result like the one below:

CLIPS> (makePuzzle)
CLIPS> (facts)
f-0     (initial-fact)
f-118   (DECODE)
f-174   (river 5)
f-192   (river 6)
f-194   (river 2)
f-196   (river 1)
f-202   (river 3)
f-204   (river 7)
f-205   (text O A E r u T m N i h p D i h l a a r a S t r a m l i e s n z n e i o e n g e s o c o n e #)
f-206   (river 4)
For a total of 10 facts.
CLIPS> 

Of course, we could make our function more useful. We could add parameters so that any choice of words could be used. We would have to add code to expand the words to letters and to assert the 'river' (word?) facts. But like most programming, that would be tedious detail.



Variation: find a minimal sequence of letters that preserves the words. TBA