about summary refs log blame commit diff stats
path: root/dwm.h
blob: c4594fc64643ef11ea8e6542767517c34c695371 (plain) (tree)
column) :matrix :letter make :letter (list :row :column) make &quot;column :column+1 if :column=6 [make &quot;row :row+1 make &quot;column 1] end </PRE> <P>In this version, the first <CODE>foreach</CODE> instruction handles the letters of the keyword. The second <CODE>foreach</CODE> instruction handles the rest of the alphabet. The <CODE>not memberp</CODE> test handles the removal of the keyword letters from the alphabet. <P>My intent in writing this alternate version was to model my idea of how the problem would be solved without a computer, processing one letter at a time. So, for example, in the template <P><PRE>[stuff jtoi lowercase ?] </PRE> <P>it's worth noting that the operations <CODE>jtoi</CODE> and <CODE> lowercase</CODE> are being applied to single-letter inputs, even though those operations were designed to accept words of any length as a unit. I cheated, though, by applying <CODE>jtoi</CODE> to the entire keyword in the second <CODE>foreach</CODE> instruction. I was trying to make the program more readable; the honest version would be <P><PRE>foreach &quot;abcdefghiklmnopqrstuvwxyz ~ [if (ifelse equalp ? &quot;i [not (or (memberp &quot;i :keyword) (memberp &quot;j :keyword))] [not memberp ? :keyword]) [stuff ?]] </PRE> <P>Why am I subjecting you to this? My point is that what may seem to be the most natural way to think about a problem--in this case, handling one letter at a time--may not be the easiest, most elegant, or most efficient programming solution. <P>What makes the dataflow-structured version of <CODE>playfair</CODE> possible is the use of <EM>operations</EM> in Logo, and the <EM>composition</EM> of these operations by using the output from one as the input to another. This is an important technique, but one that doesn't seem to come naturally to everyone. If you're not accustomed to writing operations, I think it really pays to train yourself into that habit. <P><H2>Conversational Front End</H2> <P>It's inconvenient to type a long message into the computer in the form of an input to a procedure. Another approach would be a <EM>conversational front end.</EM> This is a procedure that reads the cleartext message using <CODE> readlist</CODE>, perhaps accepting the message over several lines. It's not hard to write: <P><PRE>to encode.big.message local [keyword cleartext] print [Welcome to the Playfair enciphering program.] print [What keyword would you like to use?] make &quot;keyword first readlist print [Now please enter your message, using as many lines as needed.] print [When you're done, enter a line containing only a period (.).] make &quot;cleartext [] read.big.message print [Here is the enciphered version:] print [] print playfair :keyword :cleartext end to read.big.message local &quot;line make &quot;line readlist if equalp :line [.] [stop] make &quot;cleartext sentence :cleartext :line read.big.message end </PRE> <P>Such a top-level procedure may be justified in a project like this, in which a very large block of text may be used as a datum. But don't get carried away. Programming languages that don't emphasize composition of functions encourage this sort of programming style, to the point where the part of the program that prompts the user and reads the data gets to be longer than the part that does the actual computation. This preoccupation with verbose conversation between the program and the user is sometimes justified by the idea of &quot;good human engineering,&quot; but I don't think that's necessarily true. To take an extreme case, consider the standard elementary school Logo procedure to draw a square: <P><PRE>to square :size repeat 4 [forward :size right 90] end </PRE> <P>Compare that to this &quot;human engineered&quot; version: <P><PRE>to square local &quot;size print [Brian's square program copyright 1985] print [What size square would you like me to draw?] make &quot;size first readlist repeat 4 [forward :size right 90] print [Thank you, please come again.] end </PRE> <P>Not only is the first version (in my opinion) much more pleasant to use, but it is also more powerful and flexible. The second version can be used <EM>only</EM> as a top-level program, carrying on a conversation with a human user. The first version can be run at top level, but it can also be used as a subprocedure of a more complicated drawing program. If it's used at top level, some person types in a number, the size, as the input to <CODE> square</CODE> on the instruction line. If it's used inside another procedure, that procedure can <EM>compute</EM> the input. <P><H2>Further Explorations</H2> <P>I haven't described the part of the program that actually transforms the message: the procedure <CODE>encode</CODE> and its subprocedures. Read the listing at the end of the chapter, then answer these questions: <P>&raquo;Why does <CODE>encode</CODE> need two base cases? <P>&raquo;What purpose is served by the four invocations of <CODE>thing</CODE> at the beginning of procedure <CODE>paircode</CODE>? <P>Of course this program can be improved in many ways. <P>&raquo;One straightforward improvement to this program would be to &quot;bulletproof&quot; it so that it doesn't die with a Logo error message if, for example, the user provides a bad keyword. (Instead, the program should give its own message, making it clear what the problem is. It's better for the user to see <P><PRE>Keywords may not have any letter repeated. </PRE> <P>than <P><PRE>t has no value in paircode </PRE> <P>after making that mistake.) Also, what if the cleartext input contains words with characters other than letters? The program should just ignore those characters and process the letters in the words correctly. <P>&raquo;Another fairly straightforward improvement would be to take the one long word output by <CODE>playfair</CODE> and turn it into a list of words with spacing and punctuation thrown in at random. The goal is to have the result look more or less like an actual paragraph of English text, except for the scrambled letters. <P>Another direction would be to work on deciphering a Playfair-coded message. There are two problems here: the easy one, in which you know what the keyword is, and the hard one, in which you know only that a Playfair cipher was used. <P>&raquo;The procedure <CODE>playfair</CODE> itself will almost work in the first case. It would work perfectly were it not for the special cases of letters in the same row and column. It's a simple modification to handle those cases correctly. An interesting extension would be to try to restore the original spacing by using a dictionary to guess where words end. <P>&raquo;The much harder problem is to try to guess the keyword. I mentioned earlier some ideas about the approaches you'd have to take, such as exploring the frequencies of use of pairs of letters. If you want more advice, you'll have to study books on cryptography. <P> <TABLE width="100%"><TR><TD><A HREF="../v1-toc2.html">(back to Table of Contents)</A> <TD align="right"><A HREF="../v1ch11/v1ch11.html"><STRONG>BACK</STRONG></A> chapter thread <A HREF="../v1ch13/v1ch13.html"><STRONG>NEXT</STRONG></A> </TABLE> <P><H2>Program Listing</H2> <PRE> to playfair :keyword :message local [matrix a b c d e f g h i j k l m n o p q r s t u v w x y z] setkeyword jtoi lowercase :keyword output encode (reduce "word :message) end ;; Prepare the code array to setkeyword :word make "matrix reorder word :word (remove :word "abcdefghiklmnopqrstuvwxyz) make "j :i end to remove :letters :string if emptyp :string [output "] if memberp first :string :letters [output remove :letters bf :string] output word first :string remove :letters bf :string end to reorder :string output reorder1 :string (mdarray [5 5]) 1 1 end to reorder1 :string :array :row :column if :row=6 [output :array] if :column=6 [output reorder1 :string :array :row+1 1] mdsetitem (list :row :column) :array first :string make first :string (list :row :column) output reorder1 (butfirst :string) :array :row :column+1 end ;; Encode the message to encode :message if emptyp :message [output "] if emptyp butfirst :message [output paircode first :message "q] if equalp (jtoi first :message) (jtoi first butfirst :message) ~ [output word (paircode first :message "q) (encode butfirst :message)] output word (paircode first :message first butfirst :message) ~ (encode butfirst butfirst :message) end to paircode :one :two local [row1 column1 row2 column2] make "row1 first thing :one make "column1 last thing :one make "row2 first thing :two make "column2 last thing :two if :row1 = :row2 ~ [output letters (list :row1 rotate (:column1+1)) ~ (list :row1 rotate (:column2+1))] if :column1 = :column2 ~ [output letters (list rotate (:row1+1) :column1) ~ (list rotate (:row2+1) :column1)] output letters (list :row1 :column2) (list :row2 :column1) end to rotate :index output ifelse :index = 6 [1] [:index] end to letters :one :two output word letter :one letter :two end to letter :rowcol output itoj mditem :rowcol :matrix end ;; I and J conversion to jtoi :word output map [ifelse equalp ? "j ["i] [?]] :word end to itoj :letter if :letter = "i [if (random 3) = 0 [output "j]] output :letter end </PRE> <P><A HREF="../v1-toc2.html">(back to Table of Contents)</A> <P><A HREF="../v1ch11/v1ch11.html"><STRONG>BACK</STRONG></A> chapter thread <A HREF="../v1ch13/v1ch13.html"><STRONG>NEXT</STRONG></A> <P> <ADDRESS> <A HREF="../index.html">Brian Harvey</A>, <CODE>bh@cs.berkeley.edu</CODE> </ADDRESS> </BODY> </HTML>