<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7012287</id><updated>2011-07-30T19:21:08.143-07:00</updated><category term='books reviews code'/><category term='anagram'/><category term='bicycle'/><category term='fantasy'/><category term='kenken'/><category term='books'/><category term='spam'/><category term='haskell'/><category term='anagrams'/><category term='kenken haskell'/><category term='puzzle.'/><category term='fractran haskell'/><category term='tv'/><category term='science fiction'/><category term='books publishers'/><category term='kenken haskell debug'/><category term='oddity'/><category term='books history fantasy bioinformatics'/><title type='text'>that jefu guy</title><subtitle type='html'>Jeff Putnam - currently teaching at Eastern Washington University.   Interested in hiking, reading (mostly science fiction, fantasy and history), and programming - especially programming languages and environments.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>24</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7012287.post-8464139468610823864</id><published>2009-11-06T13:33:00.000-08:00</published><updated>2009-11-06T13:39:07.962-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='books publishers'/><title type='text'>On Publishers</title><content type='html'>&lt;p&gt;Over the years I've been reading and reviewing books, I've come to appreciate just how much a good publisher can do for a book.&amp;nbsp; Having the right author and the right material is essential, but a good publisher can make things really work.&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;/p&gt;&lt;p&gt;There are lots of good publishers in the field of computing.&amp;nbsp; Notable among these are Morgan Kaufmann, ddison Wesley and O'Reilly.&amp;nbsp; These publishers seem to me to produce consistently good books.&amp;nbsp; Not that others do not put out good books, but one of the things you notice is that some publishers are just a little bit less reliably good.&amp;nbsp; And a few are even reliably poor.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;How do these differences show up?&amp;nbsp; The good publishers ensure that the material is fundamentally good: interesting, timely and on point.&amp;nbsp; But even good material can be poorly presented and one of the jobs of a good publisher is to make sure that the material is presented well - this involves everything from checking the quality of the writing to good proofreading to making the typography and layout look good and to a good index.&amp;nbsp; Even the good publishers sometimes goof a bit here, and mistakes are ok as long as the whole book works.&amp;nbsp; A good publisher makes a good author look very good indeed, and can make even a middling author look good.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Other publishers (and I'm trying carefully to name no names) don't seem to care.&amp;nbsp; At the worst there are a couple that seem interested in pushing almost anything with pages and a cover out their door. Sometimes the books are just silly, vanity press offerings but dressed up as something more (perhaps to get the author(s) publication credits). Sometimes the underlying material is good, but the publisher let the author down - not providing adequate proofreading, not working to ensure that the book is consistently laid out, not getting adequate technical review.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;And then there are the money-grubbers.&amp;nbsp; There are a couple of bottom feeder publishing houses who go that one step further down.&amp;nbsp; I suspect they have academic and other large libraries who just gobble up whatever they print (I can't imagine why).&amp;nbsp; These serve everyone poorly - the libraries use up their budget in buying junk, the authors look like idiots and the readers waste their time.&amp;nbsp; Sure, one book in every dozen might be halfway okay, but the others are worse than a waste of money.&amp;nbsp; I've read and/or reviewed more than a few of these. In one of the worst of these the authors started with material that was (at best) thin, wrote poorly (and clearly the publisher invested not one cent in proofreading), and wrapped it up in a $100 plus package that, and I can say this only of a very small number of the books I've reviewed, left me wishing I'd never even seen the book.I'll sometimes give books away on &lt;a href="http://bookmooch.com"&gt;bookmooch&lt;/a&gt;- but I'd feel sorry for the person who got it - even free.&amp;nbsp; A responsible publisher would have either just left this book to rot, or would have found a way to make the material more informative and interesting.&amp;nbsp; I fault the authors for allowing the book to  go to press, but I fault the publishers all the more.&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-8464139468610823864?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/8464139468610823864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=8464139468610823864' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/8464139468610823864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/8464139468610823864'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/11/on-publishers.html' title='On Publishers'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-4446128083551134082</id><published>2009-11-02T12:53:00.000-08:00</published><updated>2009-11-02T12:53:28.383-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='books reviews code'/><title type='text'>Reviewing Books</title><content type='html'>I have been writing reviews for &lt;a href="http://www.reviews.com/"&gt;Computing Reviews &lt;/a&gt;for a while now and it is an interesting thing to do.&amp;nbsp;&amp;nbsp; I tend to focus more on books than articles, in large part because I like books, but because I have found that as I pursue my journey to curmudgeon, its not 90% of articles that are crap, but more like 99.9%, due in large part to the fact that making tenure in a university requires publication of quantities of the aforementioned substance.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Not that even 10% of the books are good, but a bit of experience and care in selecting the books I review has meant that I've been fairly lucky in picking interesting stuff.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Almost all of the books I review are technical - either computer oriented math or programming-related.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;In this post (and perhaps a followup)&amp;nbsp; I'm going to say a few things about programming books - things that bug me.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Reading code is hard.&amp;nbsp;&amp;nbsp; It is hard when you're using an IDE that does syntax highlighting, it is harder when it all gets printed up.&amp;nbsp;&amp;nbsp; And yet, far too often, programming books consist of piles and piles of code, separated by text.&amp;nbsp;&amp;nbsp;&amp;nbsp; The text should say the important things - and it should tell us about them in words. &amp;nbsp; Authors need to think carefully about what they're saying and how, and keep the code to a minimum.&amp;nbsp;&amp;nbsp;&amp;nbsp; If it is absolutely necessary to add more code (often boilerplate of one sort or another),&amp;nbsp; that should be done in the web copy of the code (there is a web accessible copy of the code, naturally).&amp;nbsp;&amp;nbsp; A couple of lines is usually all that that is needed to show how things are working (more in some of the more verbose languages).&amp;nbsp;&amp;nbsp; A good guideline: if the code needs to be split across multiple pages, it is too long.&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;In some of the poorer books I've seen, there are great hunks of code, a rather shorter section of explanations and then more great hunks of code.&amp;nbsp;&amp;nbsp; In the worst of these, the great hunks of code may be many pages long.&amp;nbsp;&amp;nbsp; In one case (this was a while back), the book was organized around a single program which started at perhaps 20 pages of code that did something simple.&amp;nbsp;&amp;nbsp; Then over the course of several iterations, slight changes were made to the code and the whole program was repeated (with typography indicating the changes).&amp;nbsp;&amp;nbsp; It was a while ago, but I remember that the final iteration was huge.&amp;nbsp; Now this was before the internet made it as easy as it is now to put your code online (the author/publisher did put the code online?&amp;nbsp; I thought so), but I doubt that anyone read much more than I did of the programs (typically only a quarter of a page or so).&amp;nbsp;&amp;nbsp;&amp;nbsp; The idea of picking a single program and adding to it was a good one (so the reader doesn't have to switch problem domains repeatedly), but the way it was done was simply awful. &lt;br /&gt;&lt;br /&gt;In another book, the same kind of thing was done in the sense that most of the text worked around a single problem, offering different approaches along the way. &amp;nbsp; This book didn't just do massive code dumps onto pages, but did repeat code text over and over with a line added or changed here or there. &amp;nbsp;&amp;nbsp; Some of this is probably necessary, but repeating whole functions with no changes multiple times is unnecessary.&amp;nbsp; &amp;nbsp; In this case the author did have all the code available online, but some of the listings were incomplete and didn't compile on my system. &amp;nbsp;&lt;br /&gt;&lt;br /&gt;Perhaps the code is really, really necessary to have on paper. &amp;nbsp; Fair enough. &amp;nbsp; But comments? &amp;nbsp; There's a place for printing out very short comments in the code. &amp;nbsp; This might be a quick "notice this on this line" thing, or perhaps an identifier for the file on the website.&amp;nbsp;&amp;nbsp; For the most part though,&amp;nbsp; the text should be doing the explanations - not the comments.&amp;nbsp;&amp;nbsp; I've seen programs in books where the size of the comments is roughly the size of the code.&amp;nbsp;&amp;nbsp;&amp;nbsp; Why oh why didn't the author explain all this in the text?&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; In more than one case, there have been programs that didn't fit on a single page, and the reason was that the comments made it too long.&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;Program typography is another problem, but I'm not sure there's a good solution.&amp;nbsp;&amp;nbsp; Monospaced typewriter style fonts seem to be the favorite for program text, but even a little help can make the code easier to read.&amp;nbsp;&amp;nbsp; Boldfacing reserved words and doing comments in italics can go a long way to improve readability.&amp;nbsp;&amp;nbsp; Line numbers where needed (if they're needed, the code may already be too long) can be useful, but it is quite possible to do them in a smaller font so they're not so intrusive.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Finally the swoopy "this a a newline" thingy.&amp;nbsp; If your lines are long enough to require a second line, format them in your editor to fix this.&amp;nbsp;&amp;nbsp;&amp;nbsp; Most programming languages can cope with this quite nicely - while there &lt;b&gt;might&lt;/b&gt; be a reason to do this in a whitespace sensitive language such as Python,&amp;nbsp; there is no reasonable justification for doing this in a language like Java or C.&amp;nbsp;&amp;nbsp;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-4446128083551134082?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/4446128083551134082/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=4446128083551134082' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/4446128083551134082'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/4446128083551134082'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/11/reviewing-books.html' title='Reviewing Books'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-4239509934823445762</id><published>2009-09-29T14:55:00.000-07:00</published><updated>2009-09-29T15:03:26.597-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='spam'/><title type='text'>Dun and Bradstreet spam</title><content type='html'>I recently got mail from Dun and Bradstreed, a Wall Street firm that (as far as I can tell) sells financial information on people and businesses.&amp;nbsp;&amp;nbsp;&amp;nbsp; I've never done any business with them, so their mail was (as far as I was concerned) spam.&amp;nbsp;&amp;nbsp; And, typically of spam, it was not sent by them, but by another firm that they've hired to do this for them.&amp;nbsp;&amp;nbsp;&amp;nbsp; There were a number of links in the mail that purported to go to Dun and Bradstreet, but the URLs went to exct.net (and not to Dun and Bradstreet) and looked like &lt;a href="http://cl.exct.net/?qs=e01bbfea4af04cb36262d4c642e759cc3a28a3043a6adf633c00cf9f636dd733" target="_blank"&gt;http://cl.exct.net/?qs=long-hex-string-here&lt;/a&gt;.&amp;nbsp; They even had "unsubscribe" links.&amp;nbsp;&amp;nbsp; I'm always reluctant to click on such links as who knows what is on the other end, and, of course, because clicking on unsubscribe links usually just confirms the email as valid to the spammers.&amp;nbsp; Eventually, I did click on one from a sandboxed browser and it took me to Dun and Bradstreet.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;I was still a bit suspicious though and sent Dun and Bradstreet an email pointing out that their mail was spam (at least unsolicited commercial email), that it looked like a phishing attack at the mail level, and that this was not considered good behavior. &amp;nbsp; I usually try to let companies know when they're being phished or are (perhaps inadvertently) being used in spam. &amp;nbsp;&amp;nbsp; I got a response back from someone who had clearly not understood my points and fired off another email to them trying to explain just why their mail was spam, why it was suspicious and why I was bothered.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;A while later I got another response, and some of that is worth quoting. &amp;nbsp;&amp;nbsp; I'll leave out the part where they say I'm misguided (which may be the case, honestly enough). &amp;nbsp; &amp;nbsp; &amp;nbsp; I'd also note that I now seem to be on their email list (I've received two emails from them asking me to do a survey in the last day) and will be marking their mail as spam in the future. &amp;nbsp;&amp;nbsp; This response confirms that they are harvesting emails with a view to spamming ("sent to millions of recipients") and the part about "html to most and text to others" is also nonsense as I got the HTML version and the text version in the same email and it had the same links (that is, not to Dun and Bradstreet).&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;The rest of this post is excerpted from their email : &lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; text-align: justify;"&gt;As an FYI, the campaign was sent to millions&lt;br /&gt;of recipients whose e-mail addresses we've collected through our Jigsaw&lt;br /&gt;partnership. &amp;nbsp;Due to the large number of recipients involved, we're&lt;br /&gt;bound to get a certain number of complaints from people who don't&lt;br /&gt;understand the purpose of the campaign (though we tried to explain in&lt;br /&gt;the message) and others who simply like complaining.&lt;br /&gt;&lt;br /&gt;The only other comment I'd make is that the message was transmitted in&lt;br /&gt;html to most and in text to others. &amp;nbsp;Our e-mail vendor informed us that&lt;br /&gt;this is standard, as it depends on the formatting of the various ISP's&lt;br /&gt;through which the messages are transmitted. &amp;nbsp;It looks like the recipient&lt;br /&gt;below received the message in text format, which is why the links look&lt;br /&gt;weird and unofficial. &amp;nbsp;I believe that only the html version shows the&lt;br /&gt;graphics, D&amp;amp;B brand, etc. &amp;nbsp;Unfortunately, neither we nor our vendor have&lt;br /&gt;any control over the format through which the given ISP's transmit the&lt;br /&gt;message. &lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; text-align: justify;"&gt;&lt;wbr&gt;&lt;/wbr&gt;&lt;br /&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-4239509934823445762?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/4239509934823445762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=4239509934823445762' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/4239509934823445762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/4239509934823445762'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/09/dun-and-bradstreet-spam.html' title='Dun and Bradstreet spam'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-5336826998511717943</id><published>2009-09-24T12:16:00.000-07:00</published><updated>2009-09-24T12:21:38.884-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='puzzle.'/><category scheme='http://www.blogger.com/atom/ns#' term='oddity'/><title type='text'>Some think new name.</title><content type='html'>A while back in the New York Times, I ran across a printed black rectangle with the words "Some think new name."  printed (in white) in the top left hand corner.   I had no idea what it meant, but posted it on my door because, well, it was odd and intriguing.    Today I was looking at it and decided to do a Google search on the phrase.    Not a hit to be found, nor was there one on Bing, nor on the NY Times web site. &lt;br /&gt;&lt;br /&gt;Anyone have a clue as to what this is/was?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-5336826998511717943?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/5336826998511717943/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=5336826998511717943' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5336826998511717943'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5336826998511717943'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/09/some-think-new-name.html' title='Some think new name.'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-6922334484048327844</id><published>2009-09-22T13:40:00.000-07:00</published><updated>2009-09-22T13:48:58.900-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tv'/><title type='text'>Cracker</title><content type='html'>BBC America today showed reruns of "Cracker" featuring Robbie Coltrane (probably better known as Hagrid in the Harry Potter movies).     This is one of the best TV dramas I've seen, funny, tragic, and oddly haunting.   Even if you don't like the usual TV crime drama stuff, this one is worth a watch and a second watch.    Coltrane handles his part beautifully and almost effortlessly and the writing (especially in the episodes shown today) is almost perfect.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-6922334484048327844?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/6922334484048327844/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=6922334484048327844' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/6922334484048327844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/6922334484048327844'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/09/bbc-america-today-showed-reruns-of.html' title='Cracker'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-7876832010677600922</id><published>2009-09-21T11:13:00.000-07:00</published><updated>2009-09-21T12:19:24.529-07:00</updated><title type='text'>Pictures</title><content type='html'>I was in the Peace Corps from 1973-1977 - Zaire - now Congo (again) Shaba province (now Katanga again, I believe) in the towns of Luabo, Chibambo (on the Luapula river) and Kalemie (on Lake Tanganika).   I carried a camera around and took pictures pretty often while I was there.   This resulted in a box of fading photographs and another of negatives that I didn't really look at often.   I've tried to scan the negatives a couple of times.  &lt;br /&gt;&lt;br /&gt;Once was in the local lab at EWU.   They had a negative scanner attached to a nice big mac, but they'd disabled Terminal on the Mac so the only way to get images off was email or to burn a CD - instead of being able to scp them to my local desktop machine (sigh).  &lt;br /&gt;&lt;br /&gt;I also tried a negative scanner which scanned one image at a time, slowly, and required manual positioning on the negative.    It was essentially unusable for my boxload of negatives.   I returned it.&lt;br /&gt;&lt;br /&gt;Then recently I was reminded that I wanted to do this and found a recommended scanner on Amazon - an Epson V300.    And it worked very nicely, nicely enough that just two weeks later I now have 1500+ scanned negatives.    I've put them all, pretty much without looking at them up on my &lt;a href="http://foo.ewu.edu/jefu/pcpix/by-geography"&gt;work server&lt;/a&gt;  (this will be going away at some point in the next year or so,  I'll try to find another place to put them).     I'll be going through these and removing the ones out of focus and that don't show anything, duplicates and the like and I'll also generate some thumbnails and labels.   If you happen to be in one of these pictures and want it to be un-posted, let me know and I'll do that.&lt;br /&gt;&lt;br /&gt;I may put some of them on Panoramio or something so I can add google map links to them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-7876832010677600922?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/7876832010677600922/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=7876832010677600922' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/7876832010677600922'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/7876832010677600922'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/09/pictures.html' title='Pictures'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-4306189076385338248</id><published>2009-09-03T15:31:00.000-07:00</published><updated>2009-09-05T07:13:49.418-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fractran haskell'/><title type='text'>Fractran in Haskell</title><content type='html'>One of the blogs I tend to follow is  &lt;a href="http://scienceblogs.com/goodmath/"&gt;Good Math, Bad Math&lt;/a&gt; which had a post on one of those oddball things that can be lots of fun to play with, in this case &lt;a href="http://scienceblogs.com/goodmath/2009/09/pathological_programming_with.php"&gt;Fractran&lt;/a&gt;, a Turing Complete language by John Horton Conway.   I implemented this myself in Haskell back in (hmmm, not at all sure) 1997(?) in&lt;br /&gt;Haskell.   The quality of the Haskell novice I was then (and remain) shows though my Haskell has improved since then.    Read that post for better information on what Fractran is and how it works.&lt;br /&gt;&lt;br /&gt;This implementation factors the numbers used and just keeps track of the factors in the numerator and denominator of the fractions involved.   I probably reinvented the wheel to get the primes and factors and all.  I tested it in the primes program (named primegame) and&lt;br /&gt;the addition program (named addergame), but not on much else as actually writing Fractran programs was not something I tried very hard to master. &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import Ratio&lt;br /&gt;--&lt;br /&gt;-- run takes an integer i&lt;br /&gt;-- and a list of fractions (the program)&lt;br /&gt;-- it returns a result list of the integers generated&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;run :: Integer -&gt; [Rational] -&gt; [Integer]&lt;br /&gt;run i p =      takeWhile (&gt;0) res&lt;br /&gt;        where&lt;br /&gt;      res = i:(map runstep res)&lt;br /&gt;      runstep j        = runl j p&lt;br /&gt;&lt;br /&gt;runl :: Integer -&gt; [Rational] -&gt; Integer&lt;br /&gt;runl    j []     = 0&lt;br /&gt;runl    j (f:fs) = let&lt;br /&gt;                     ifv = (fromInteger j)*f&lt;br /&gt;                  in  if   isInteger1 ifv&lt;br /&gt;                      then (numerator ifv)  -- essentially a toInteger&lt;br /&gt;                      else runl j fs&lt;br /&gt;&lt;br /&gt;isInteger i = (i == (truncate i))&lt;br /&gt;isInteger1 i = ((denominator i) == 1) -- works in this context&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- the numbers themselves dont show much, so write them as products&lt;br /&gt;-- of powers of primes&lt;br /&gt;--&lt;br /&gt;primes      :: Integral a =&gt; [a]&lt;br /&gt;primes       = map head (iterate sieve [2..])&lt;br /&gt;sieve (p:xs) = [ x | x&lt;-xs, x `rem` p /= 0 ] &lt;br /&gt;powersOfTwo = 2:(map (2*) powersOfTwo)  &lt;br /&gt;--&lt;br /&gt;-- returns 0 if not a power of two&lt;br /&gt;-- else returns the power &lt;br /&gt;-- &lt;br /&gt;whichPowerOfTwo x =     l                     &lt;br /&gt; where                  &lt;br /&gt;       (a,b) = span (&amp;lt; x) powersOfTwo&lt;br /&gt;       l = x == (head b) then 1 + (length a) else 0&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- brute force factorization&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;factor  x   = factor1 x primes &lt;br /&gt;factor1 1 _      = []&lt;br /&gt;factor1 x (p:ps) = let (c,q) = fp x p&lt;br /&gt;                  in  if c &gt; 0&lt;br /&gt;                      then (c,p):(factor1 q ps)&lt;br /&gt;                      else factor1 q ps&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- multiply two lists of prime, power pairs&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;mult [] l = l&lt;br /&gt;mult l [] = l&lt;br /&gt;mult l@((p,pow):ps) l'@((p',pow'):ps')&lt;br /&gt; |  p == p'              = (p,pow+pow'):(mult ps ps')&lt;br /&gt; |  p &amp;lt; p'             = (p,pow):(mult ps l')&lt;br /&gt; |  p &gt;  p'              = (p',pow):(mult l ps')&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- divide one list of prime,power pairs by another&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;divvy []  l = []&lt;br /&gt;divvy l  [] = l&lt;br /&gt;divvy l@((p,pow):ps) l'@((p',pow'):ps')&lt;br /&gt;| p == p' &amp;amp;&amp;amp; pow == pow'       = (divvy ps ps')&lt;br /&gt;| p == p'                      = (p, pow - pow'):(divvy ps ps')&lt;br /&gt;| p &amp;lt;  p'                      = (p,pow):(divvy ps l')&lt;br /&gt;| p &gt;  p'                      = (p, -pow'):(divvy ps ps')&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- isIntegerL returns true if the list represents an integer&lt;br /&gt;-- when all the powers are &gt;= 0&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;isIntegerL l = and (map ((&gt;0).snd) l)&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- fp takes two integers and returns the largest&lt;br /&gt;-- power of the second that evenly divides the first&lt;br /&gt;-- and the quotient thus determined&lt;br /&gt;--&lt;br /&gt;&lt;br /&gt;fp :: Integer -&gt; Integer -&gt; (Integer, Integer)&lt;br /&gt;fp x p     = if   x `mod` p == 0&lt;br /&gt;               then let (c,q) = fp (x `div` p) p&lt;br /&gt;                    in  (c+1,q)&lt;br /&gt;               else (0, x)&lt;br /&gt;&lt;br /&gt;primegame = [17%91, 78%85, 19%51, 23%38, 29%33, 77%29, 95%23, 77%19, 1%17, 11%13, 13%11, 15%2, 1%7,5&lt;br /&gt;5%1 ]&lt;br /&gt;&lt;br /&gt;--&lt;br /&gt;-- start with 2^a * 3^b&lt;br /&gt;-- ends with 2^a+b&lt;br /&gt;-- so started with (8=2^3) * (243 = 3^5) = 1944 should result in 256 = 2^8&lt;br /&gt;-- that is, run 1944 addergame =&gt; 256&lt;br /&gt;--&lt;br /&gt;addergame = [2%3]&lt;br /&gt;&lt;br /&gt;p1 = filter (/= 0) (map whichPowerOfTwo (run 2 primegame))&lt;br /&gt;&lt;br /&gt;-- main = putStr (show (take 20 p1))&lt;br /&gt;main = putStr (unlines (map show (map factor (take 10000 (run 2 primegame)))))&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-4306189076385338248?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/4306189076385338248/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=4306189076385338248' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/4306189076385338248'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/4306189076385338248'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/09/one-of-blogs-i-tend-to-follow-is-good.html' title='Fractran in Haskell'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-420605928411859214</id><published>2009-07-14T09:12:00.000-07:00</published><updated>2009-07-14T09:39:10.121-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken haskell debug'/><title type='text'>Haskell kenken debugging</title><content type='html'>Programming in Haskell requires a different kind of debugging process.   Some debugging can be eased by using something like &lt;a href="http://www.cs.chalmers.se/~rjmh/QuickCheck/"&gt;quickcheck &lt;/a&gt; and &lt;a href="http://www.haskell.org/hat/"&gt;Hat&lt;/a&gt; provides tracing facilities.   I've used both of these and quite like quickcheck as it provides a nice way to check low level code.   I usually find though that I'd like to do some (often selective) printing of specific information in a format that I can easily scan and interpret.   I often spend a fair amount of time building output that is easy to look at as doing that both helps me debug and helps me to ensure that I understand what it is I'm trying to do.  &lt;br /&gt;&lt;br /&gt;In a language like C you can just dump in printfs here and there (usually controlled by some debug value either defined at the preprocessor level or in the language).   But in Haskell it is much harder to just dump IO statements in at random points in the code unless that code is in the IO monad.   &lt;br /&gt;&lt;br /&gt;In this code, I've used the state transformer monad on top of the IO monad for the highest level of code, so the IO monad is available.   To get the same effect as in C where I can turn on and off such statements by changing the value of a variable, I've built some low level helper functions.   The essential part of the code is : &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;debug = False &lt;br /&gt;&lt;br /&gt;dbPutLn :: String -&gt; PuzzleM () &lt;br /&gt;dbPutLn s = if debug then  liftIO $ putStrLn s else return () &lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;Because of the liftIO this needs to be run in the appropriate monadic environment, but thats not a problem here.   &lt;br /&gt;&lt;br /&gt;Along with that I've also constructed a way to print multiple lines with a label and so that the label lies up against the left hand side of the output and the lines all are indented a bit.   This makes the output easier (for me at least) to scan for interesting occurrences.  Typical output might look like :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;in function foo&lt;br /&gt;    value1=1&lt;br /&gt;    value2=2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And the code for this looks like : &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;dbPutLabeledLines :: String -&gt; [String] -&gt; PuzzleM ()&lt;br /&gt;dbPutLabeledLines label ls = if debug &lt;br /&gt;                                then do &lt;br /&gt;                                  liftIO $ putStrLn ""&lt;br /&gt;                                  liftIO $ putStrLn label &lt;br /&gt;                                  mapM_ (liftIO . putIndentedLine) ls &lt;br /&gt;                                else return () &lt;br /&gt;   where &lt;br /&gt;         putIndentedLine l = putStrLn $ "   " ++ l&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;On a side note, languages that provide support for pre-conditions, post-conditions and class invariants (such as Eiffel and the sadly defunct Sather) are, for me anyway, about the best thing around for building correct code.   Not only can good pre/post-conditions make debugging easier (especially with a good test suite and a good way to generate test cases), but they also help me decide just what it is that the code needs to do and they provide invaluable information when I read the code to see what it does.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-420605928411859214?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/420605928411859214/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=420605928411859214' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/420605928411859214'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/420605928411859214'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/07/haskell-kenken-debugging.html' title='Haskell kenken debugging'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-5046722083796679554</id><published>2009-07-13T09:10:00.000-07:00</published><updated>2009-07-13T09:22:10.946-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken haskell'/><title type='text'>more kenken</title><content type='html'>My second kenken program is a bit longer and more complex.   At the base is the old kenken program (see &lt;a href="http://jefu.blogspot.com/2009/05/whole-kenken-program.html"&gt;this post&lt;/a&gt;).   &lt;br /&gt;&lt;br /&gt;This program though tries to eliminate possibilities on each cell as it goes.   There is a structure that ties cells to the set of possibilities that are currently available for that cell :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data CellPos = CellPos { &lt;br /&gt;       cpcell :: Cell, &lt;br /&gt;       cposs  :: [Int] &lt;br /&gt;     } deriving (Eq, Show)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;These are all initialized at start up so that cposs contains the list of integers [1..size of puzzle], then as possible assignments for cells are explored, each such assignment is used to try to reduce the possibilities in each of these structures. &lt;br /&gt;&lt;br /&gt;The solve function then :&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;   &lt;li&gt; finds all of the cells with only one possibility and makes all those assignments&lt;br /&gt;   &lt;li&gt; sorts the cells with multiple possibilities so those with the fewest number of possibilities come first &lt;br /&gt;   &lt;li&gt; tries making all the assignments currently possible with the first cell (which will be one of those with the fewest possibilities still available) &lt;br /&gt;   &lt;li&gt; returns all solutions found. &lt;br /&gt;&lt;/ul&gt; &lt;br /&gt;&lt;br /&gt;Another difference (as noted above) is that this solver will try to find all possible solutions for a puzzle instead of just the first.  &lt;br /&gt;&lt;br /&gt;Here is the solve function (stripped of debug prints) :  &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;solve cpl assList resultList = do &lt;br /&gt;  puz &lt;- getPuzzle &lt;br /&gt;  s &lt;- getSize &lt;br /&gt;&lt;br /&gt;  let sortedPossibilities = sortBy comparer cpl&lt;br /&gt;      comparer x y        = compare (pcount x) (pcount y)&lt;br /&gt;      uhOh                =   null sortedPossibilities  -- no assignments possible ?? &lt;br /&gt;                           || 0 == pcount (head sortedPossibilities) &lt;br /&gt;&lt;br /&gt;  -- if no assignments possible from here, just return the result list &lt;br /&gt;  -- otherwise if there are ways to do a single assignment (so the possiblity&lt;br /&gt;  -- list for a cell contains only one value), do all of those &lt;br /&gt;  -- then double check to see if it is all possible, if so call solve recursively&lt;br /&gt;  if uhOh &lt;br /&gt;    then return resultList  &lt;br /&gt;    else do &lt;br /&gt;      let &lt;br /&gt;          oneChoices          = filter (\x -&gt; 1 == pcount x) sortedPossibilities &lt;br /&gt;          newAssignments      = makeAssignments oneChoices &lt;br /&gt;          multiChoices        = filter (\x -&gt; 1 &lt; pcount x)  sortedPossibilities &lt;br /&gt;          currentAssignments  = assList ++ newAssignments &lt;br /&gt;&lt;br /&gt;      ok &lt;- verify currentAssignments&lt;br /&gt;&lt;br /&gt;      if not ok &lt;br /&gt;        then do return resultList -- verify failed &lt;br /&gt;        else if null multiChoices &lt;br /&gt;                then return $ currentAssignments:resultList &lt;br /&gt;                else if length newAssignments &gt; 0  &lt;br /&gt;                        then do  -- new assignments to do, try them &lt;br /&gt;&lt;br /&gt;                          constraints &lt;- getConstraints &lt;br /&gt;                          let constrainedPossibilities = foldr (updateRCPossibilities currentAssignments constraints) multiChoices newAssignments&lt;br /&gt;&lt;br /&gt;                          solve constrainedPossibilities currentAssignments resultList &lt;br /&gt;                        else do &lt;br /&gt;                               let &lt;br /&gt;                                  nextCell            = head multiChoices &lt;br /&gt;                                  needAssignments     = tail multiChoices &lt;br /&gt;                                  nextAssignments     = map (\x -&gt; Assignment{ acell = cpcell nextCell, avalue=x}) $ cposs nextCell&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;                               -- now we have a cell with several possibilities, try each of those in sequence &lt;br /&gt;                               maybeRes &lt;- mapM (\x -&gt; solveWithAssignment x needAssignments currentAssignments []) nextAssignments &lt;br /&gt;                               return $ resultList ++ (concat $ filter (not.null) maybeRes) &lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-5046722083796679554?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/5046722083796679554/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=5046722083796679554' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5046722083796679554'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5046722083796679554'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/07/more-kenken.html' title='more kenken'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-3129945587461397733</id><published>2009-07-06T09:52:00.000-07:00</published><updated>2009-07-06T09:59:08.741-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='anagrams'/><category scheme='http://www.blogger.com/atom/ns#' term='bicycle'/><title type='text'>anagrams</title><content type='html'>&lt;p&gt;&lt;br /&gt;I let my anagrams program run for a while trying to find anagrams for "eastern washington university".   I killed it after it had produced 60 million anagrams and 5.9Gb of output.   At that point all of the lines still started with :&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[["a"],["a"] ... &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;On another note, the Tour De France has started again.   I used to do a fair amount of cycling (but no racing) so have been watching this now for a while and in the last couple of years have found it increasingly fun.    It helps that Versus shows several hours of it in the morning pretty much as it happens - so not only do you get to watch the race, but you also get to look at the French (and Italian and Spanish and ...) countryside.   The later broadcasts focus more on the race and especially on the American riders, which gets substantially less interesting for me - I enjoy watching them all battle it out and watching the soap opera of who gets to win (and who gets to win the various jerseys) unfold over the course of three weeks or so.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-3129945587461397733?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/3129945587461397733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=3129945587461397733' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/3129945587461397733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/3129945587461397733'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/07/anagrams.html' title='anagrams'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-2175868358858633953</id><published>2009-06-30T12:32:00.001-07:00</published><updated>2009-06-30T12:41:18.834-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><category scheme='http://www.blogger.com/atom/ns#' term='anagram'/><title type='text'>Anagram Code</title><content type='html'>Finally, here is the anagram code.    It worked on my tests, but failed to build the anagram trie with the enable word list (which I no longer find a direct link to online).   I'm getting a stack overflow error which implies there is a space leak and a bit of investigation turns up that it is somewhere in building the trie.    I've tried different ways to force strictness, but to no (so far) avail.    But if I run it with a smaller dictionary, or with +RTS -K16M, it works.    &lt;br /&gt;&lt;br /&gt;Notice that to keep from getting duplicates, the function "anagramsFor" only will produce a new word if it follows (or is equal to) "starter", which is set to empty to start, then to the last word generated.    This should eliminate duplicates where anagramsFor generates a list like ["aaa", "bbb", "ccc"] and also one like ["aaa", "ccc", "bbb"].  &lt;br /&gt;&lt;br /&gt;It runs my (simple) test cases (using small dictionaries). &lt;br /&gt;&lt;br /&gt;Here's the code :&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;{-# LANGUAGE BangPatterns #-} &lt;br /&gt;&lt;br /&gt;{- run with +RTS -K16M for enable wordlist -} &lt;br /&gt;&lt;br /&gt;import Char&lt;br /&gt;import Data.List &lt;br /&gt;import System &lt;br /&gt;import qualified Data.Map as M&lt;br /&gt;import Control.Monad.State&lt;br /&gt;import Control.Monad.List&lt;br /&gt;&lt;br /&gt;data Trie a b =   Empty &lt;br /&gt;                | Node { entries :: (M.Map a (Trie a b)),  ender :: Maybe b } &lt;br /&gt;                  deriving (Eq, Show) &lt;br /&gt;&lt;br /&gt;type AnagramTrie = Trie Char String &lt;br /&gt;data AnagramHelper = Ah { trie :: AnagramTrie, strings ::  M.Map String [String]} &lt;br /&gt;                     deriving Show &lt;br /&gt;&lt;br /&gt;type AnagramTree = Trie String [String] &lt;br /&gt;&lt;br /&gt;emptyHelper = Ah { trie = Empty, strings = M.empty } &lt;br /&gt;&lt;br /&gt;trieInsert :: (Ord a, Show a) =&gt; [a] -&gt; b -&gt; Trie a b -&gt; Trie a b &lt;br /&gt;&lt;br /&gt;trieInsert [] w Empty = Node {entries=M.empty, ender=Just w} &lt;br /&gt;trieInsert [] w n = n  -- should never happen&lt;br /&gt;&lt;br /&gt;trieInsert !l1@(x:xs) !l Empty = Node {entries=M.singleton x (trieInsert xs l Empty ), ender=Nothing} &lt;br /&gt;      &lt;br /&gt;trieInsert !l1@(x:xs) !al !Node{entries=es, ender=en}      &lt;br /&gt;  | M.member x es              = let !st = trieInsert xs al       in Node{entries=M.adjust st x es, ender=en} &lt;br /&gt;  | otherwise                  = let !st = trieInsert xs al Empty in Node{entries=M.insert x st es, ender=en} &lt;br /&gt;&lt;br /&gt;findInTrie Empty _ = Nothing&lt;br /&gt;findInTrie Node{entries = es, ender=en} [] = en &lt;br /&gt;findInTrie Node{entries=es, ender=en} (c:cs)&lt;br /&gt;  | M.member c es = findInTrie (fromJust $ M.lookup c es) cs&lt;br /&gt;  | otherwise     = Nothing &lt;br /&gt;&lt;br /&gt;toString :: (Show a, Show b) =&gt; Trie a b   -&gt; String -&gt; [String]&lt;br /&gt;toString Empty indent = [] &lt;br /&gt;toString Node{entries=es, ender=en} indent = enderLine:subtrees&lt;br /&gt;   where &lt;br /&gt;       enderLine = case en of &lt;br /&gt;                      Nothing -&gt; []  &lt;br /&gt;                      Just l -&gt; (indent ++ "-" ++ show l)&lt;br /&gt;       doEntry k t =  (indent ++ " " ++ show [k]):(toString t ("  "++indent)) &lt;br /&gt;       subtrees =  concat $ M.elems $ M.mapWithKey doEntry es &lt;br /&gt;&lt;br /&gt;-- extract the paths in a trie (ignoring the entries, if any) &lt;br /&gt;pathsThrough :: Trie a b -&gt; [[a]] &lt;br /&gt;pathsThrough Empty = [[]] &lt;br /&gt;pathsThrough Node{entries=es,ender=en} = l &lt;br /&gt;   where &lt;br /&gt;     l = concatMap (\(x,y) -&gt; (map (x:)) (pathsThrough y)) $ M.assocs es &lt;br /&gt;&lt;br /&gt;allWhitespace s = and $ map isSpace s&lt;br /&gt;&lt;br /&gt;makeAnagramHelperFromList l = foldl' ahInsert emptyHelper l&lt;br /&gt;&lt;br /&gt;ahInsert !a@Ah{trie=t,strings= s} !word &lt;br /&gt;   | M.member sw s = Ah{trie=t, strings = M.insertWith' insertUnique sw [word] s}  &lt;br /&gt;   | otherwise     = Ah{trie=trieInsert sw sw t, strings=M.insert sw [word] s} &lt;br /&gt;  where&lt;br /&gt;    sw = sort word &lt;br /&gt;    insertUnique [w] l = if w `elem` l then l else (w:l)&lt;br /&gt;&lt;br /&gt;clean x = filter (not.null) (nub x)   -- profiling shows this is a bit faster &lt;br /&gt;&lt;br /&gt;-- These are some common lists of words - one per line used here&lt;br /&gt;-- these (obviously) live in my filesystem - you might use /usr/dict/share/words if&lt;br /&gt;-- you're on a unix system &lt;br /&gt;&lt;br /&gt;-- wordfile = "words/american-english-large-no-quotes"&lt;br /&gt;-- wordfile = "words/american-english-smaller"&lt;br /&gt;-- wordfile = "words/shorter-wordlist"&lt;br /&gt;-- wordfile = "words/random-words"&lt;br /&gt;-- wordfile = "words/seethe"&lt;br /&gt;&lt;br /&gt;strip s = reverse $ dropWhile isSpace $ reverse $ dropWhile isSpace s &lt;br /&gt;&lt;br /&gt;allchars w         = (not.or) $ map (\x -&gt; x &lt; 'a' || x &gt; 'z') w&lt;br /&gt;&lt;br /&gt;-- most dictionaries include all the letters on their own, so &lt;br /&gt;-- filter out all one letter words, but toss "a" and "i" back in&lt;br /&gt;-- also strip the strings as there are some cases where they have trailing spaces &lt;br /&gt;-- sometimes there seem to be duplicates (probably because there are caps in some versions)&lt;br /&gt;-- in this version duplicates are handled by the builder &lt;br /&gt;&lt;br /&gt;getWordsFromList l = ("a" :) $ ("i" :) $ filter (\x-&gt;(length x) &gt; 1) $ filter allchars $ map strip $ (lines $ map toLower l)&lt;br /&gt;&lt;br /&gt;makeAnagramHelperFromFile fn = do &lt;br /&gt;    rawWords &lt;- readFile fn &lt;br /&gt;    let wordList = getWordsFromList rawWords  &lt;br /&gt;    return $! makeAnagramHelperFromList wordList &lt;br /&gt;&lt;br /&gt;getStringsFromTopWithString :: AnagramTrie -&gt; String -&gt; [String] &lt;br /&gt;&lt;br /&gt;getStringsFromTopWithString Empty _ = [] &lt;br /&gt;getStringsFromTopWithString _  [] = [] &lt;br /&gt;getStringsFromTopWithString topTrie@Node{entries=es, ender=en} s@(c:cs) = &lt;br /&gt;  let maybeSubTree =  M.lookup c es &lt;br /&gt;  in case maybeSubTree of &lt;br /&gt;       Nothing -&gt; [] &lt;br /&gt;       Just thisSubTree -&gt; maybePrepend en $ findAllSubsetStringsInSubTree thisSubTree cs &lt;br /&gt;        &lt;br /&gt;findAllSubsetStringsInSubTree Empty _ = [] &lt;br /&gt;findAllSubsetStringsInSubTree Node{ender=en} [] = maybePrepend en [] &lt;br /&gt;findAllSubsetStringsInSubTree t@Node{entries=es, ender=en} (c:cs) = &lt;br /&gt;   let next = M.lookup c es  -- does this first character in this node have a subtree ? &lt;br /&gt;       subs = findAllSubsetStringsInSubTree t $ dropWhile (==c) cs -- no, skip all of those and try the rest &lt;br /&gt;   in case next of &lt;br /&gt;        Nothing -&gt; subs &lt;br /&gt;        Just t' -&gt; maybePrepend en $ findAllSubsetStringsInSubTree t' cs ++ subs  &lt;br /&gt;&lt;br /&gt;maybePrepend Nothing l = l &lt;br /&gt;maybePrepend (Just x) l = x:l&lt;br /&gt;&lt;br /&gt;fromJust (Just l) = l &lt;br /&gt;&lt;br /&gt;testWords = ["at", "tear", "rat", "ate", "sea", "teas", "tea", "erase", "tar", "art", "eat", "tears", "stare", "reset", "stares", "star" , "tartar", "seatea", "erasetar", "tee", "see"] &lt;br /&gt;&lt;br /&gt;getTestTrie = trie getTestAH   &lt;br /&gt;&lt;br /&gt;getTestAH = makeAnagramHelperFromList testWords &lt;br /&gt;&lt;br /&gt;anagramsFor starter ah@Ah{trie=t, strings=m} [] = Just Empty&lt;br /&gt;anagramsFor starter ah@Ah{trie=t, strings=m} s  = anatree  &lt;br /&gt;    where &lt;br /&gt;       s' = sort s &lt;br /&gt;       slist = getStringsFromTopWithString t s' &lt;br /&gt;       subtreeskeys = filter (\(x,y) -&gt; y /=Nothing) $ map (\x -&gt; (x,anagramsFor x ah (s' \\ x))) slist &lt;br /&gt;       subtreeskeys1 = filter (\(x,y) -&gt; x&gt;=starter) subtreeskeys &lt;br /&gt;       mkAnaTrieEntries m (x, Nothing) = m &lt;br /&gt;       mkAnaTrieEntries m (x, Just y)  = M.insert x y m &lt;br /&gt;                                 &lt;br /&gt;       anatree =  if subtreeskeys1 == [] &lt;br /&gt;                     then Nothing &lt;br /&gt;                     else Just Node{entries=foldl mkAnaTrieEntries M.empty subtreeskeys1, ender=Just s} &lt;br /&gt;&lt;br /&gt;simpleTest s = do &lt;br /&gt;  let t = anagramsFor [] getTestAH s&lt;br /&gt;      t' = fromJust t &lt;br /&gt;  mapM_ putStrLn $ toString t' "" &lt;br /&gt;  mapM_ putStrLn $ map show $ pathsThrough t' &lt;br /&gt;&lt;br /&gt;main = do&lt;br /&gt;          (wl, strings) &lt;- getCommandLineArgs   &lt;br /&gt;          putStrLn $ "Using dictionary : " ++ wl&lt;br /&gt;          ah &lt;- makeAnagramHelperFromFile wl&lt;br /&gt;          mapM_ (getAnagrams ah) strings &lt;br /&gt;&lt;br /&gt;getCommandLineArgs = do &lt;br /&gt;   al &lt;- getArgs&lt;br /&gt;   if length al &lt; 1 &lt;br /&gt;      then error "Args : [-dictionary] strings " &lt;br /&gt;      else let (s:xs) = al !! 0  &lt;br /&gt;           in if s == '-' &lt;br /&gt;              then return (xs,drop 1 al) &lt;br /&gt;              else return ("words/enable.list", al) &lt;br /&gt;&lt;br /&gt;listVariants :: AnagramHelper -&gt; [String] -&gt; [[String]] &lt;br /&gt;listVariants Ah{strings=m} sl = map getWords sl &lt;br /&gt;  where&lt;br /&gt;    getWords s = fromJust $ M.lookup s m &lt;br /&gt;&lt;br /&gt;getAnagrams ah s = do &lt;br /&gt;   putStrLn $ "getting anagrams for " ++ s &lt;br /&gt;   let at = anagramsFor [] ah s &lt;br /&gt;   case at of &lt;br /&gt;      Nothing -&gt; putStrLn $ "No anagrams for: " ++ s &lt;br /&gt;      Just t  -&gt; do &lt;br /&gt;                    mapM_ putStrLn $ toString t "" &lt;br /&gt;                    let p = pathsThrough t &lt;br /&gt;                    mapM_ putStrLn $ map show p &lt;br /&gt;                    let v = map (listVariants ah) p &lt;br /&gt;                    mapM_ putStrLn $ map show v &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-2175868358858633953?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/2175868358858633953/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=2175868358858633953' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/2175868358858633953'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/2175868358858633953'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/06/anagram-code.html' title='Anagram Code'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-9106864710594584955</id><published>2009-06-29T10:22:00.001-07:00</published><updated>2009-06-29T10:29:59.559-07:00</updated><title type='text'>ICFP programming contest</title><content type='html'>Last Friday at 11:16 (local time) the &lt;a href="http://icfpcontest.org/"&gt;2009 ICFP programming contest&lt;/a&gt; started.   One of our students and I gave it a try.    Currently we're about 190/317 (with about 30 minutes to go) which isn't great.    But it was fun and I spent much of my time between the start of the contest and yesterday about 5 pm working on it.  &lt;br /&gt;&lt;br /&gt;Lots of things I did wrong - our virtual machine ran nicely (once a problem with the specification was fixed and announced) but our interface to it started all wrong.    Once that was fixed there were a bunch of bugs (as usual) many of them dealing with signs of quantities.     Anyway we solved all the first level problems and most of the second level problems and I had figured out the basics of the third level problems (I think).    &lt;br /&gt;&lt;br /&gt;Biggest thing that would have helped us was better communications.    We were working remotely (from each other) and being in one place to share a whiteboard and be able to more quickly talk about what was wrong and draw pictures would have helped immensely.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-9106864710594584955?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/9106864710594584955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=9106864710594584955' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/9106864710594584955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/9106864710594584955'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/06/icfp-programming-contest.html' title='ICFP programming contest'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-5835993428331036749</id><published>2009-06-21T11:11:00.000-07:00</published><updated>2009-06-21T12:01:44.841-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='fantasy'/><category scheme='http://www.blogger.com/atom/ns#' term='science fiction'/><title type='text'>Reading with the Flu</title><content type='html'>Of course, I was wrong in my previous post about not having to worry about multiplicity (and indeed my code doesn't do that).   But the code needs to be a bit cleaned up before posting.&lt;br /&gt;&lt;br /&gt;Over the last four days I've had some kind of mini-flu.   Temperature in the 100.5 range and a complete inability to feel either warm enough or cool enough for more than about an hour.    Not important as such, and it seems to be gone now, but it did lead to my spending quite a bit of time reading.   This has included samples from the most recent "New Yorker", CACM, and IEEE Computer, but relatively little online stuff (sitting up was just too tiring).&lt;br /&gt;&lt;br /&gt;Most of the reading was science fiction/fantasy and not overly demanding (the kind of density that works well for airline travel and mild flu).   I would note that I don't usually read anything like this much paper in most four day periods.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The City and the City&lt;/span&gt; by China Miéville.   Miéville is a fascinating writer.   I've read his three books set in &lt;span style="font-weight: bold;"&gt;Bas-Lag&lt;/span&gt;, a very strange world but one that hangs together nicely.   These are : &lt;span style="font-weight: bold;"&gt;Perdido Street Station&lt;/span&gt;, &lt;span style="font-weight: bold;"&gt;The Scar&lt;/span&gt;, and &lt;span style="font-weight: bold;"&gt;The Iron Council&lt;/span&gt; and are often characterized as the "new weird" but while they are certainly weird enough in setting, the stories and themes are very human.   His newest &lt;span style="font-weight: bold;"&gt;The City and the City&lt;/span&gt; is a murder mystery and in many ways fits the conventions of that genre pretty nicely.   Except that instead of being set in somewhere that we might know, it is set in two twin but separate cities Bezel (there is an accent over the "z" that I don't know how to generate) and Ul Qoma.   What makes this interesting is that the cities overlap in some undefined way, and residents learn to "unsee" things in the other city that may be occurring across the street (or closer).     This prompted me (I hesitate to say "the reader") to contemplate just this kind of overlap that occurs for other reasons - perhaps Jerusalem or some city in Europe where the Jews were forced to live apart (not necessarily in ghettos - just "apart") or even a city in the US where classes may intermix without intermixing.    Think of the  oft mentioned example of a black man failing to get a cab in New York where a white man just down the block succeeds.&lt;br /&gt;&lt;br /&gt;I have not yet read &lt;span style="font-weight: bold;"&gt;Un Lun Dun&lt;/span&gt; by Miéville but look forward to it.      I also added more than a few books to my (far too large and unlikely ever to even begin to empty significantly) Amazon wishlist after reading a list of book suggestions by him.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Candle&lt;/span&gt; by John Barnes.   Just as I was ready to put this down thinking I'd figured it all out, he twists things just enough, and then it happens again.   I'm not sure it is worth a re-read, but it was a good read-once.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Against a Dark Backround&lt;/span&gt; by Iain M. Banks.   I've read a couple of his "Culture" novels and liked them well enough.   This was a good read, but in a kind of unrelenting sameness sort of way.   Lots of imaginative touches, but tossed in almost at random.    I must also admit that books and movies where everyone gets killed off (one by one by one) to be less than rewarding in general.   There are exceptions, but there needs to be pretty substantial other rewards.  &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Killing Time&lt;/span&gt; by Caleb Carr.    I've enjoyed Carr's 19th century mysteries,  but not enough to seek out any more.   This one fell into my hands (as did several of the others I read in the last couple days) at a yard sale where someone who had a serious interest in science fiction was selling things off.   I wanted to like it, but it just didn't work.&lt;br /&gt;&lt;br /&gt;I finished &lt;span style="font-weight: bold;"&gt;Before and After&lt;/span&gt; by Rosellen Brown.   This was made into a movie a while back.   The book is pretty good and well worth reading.    The narration from the viewpoint of several characters reads a bit false though as only one of the characters writes in the first person - is this narrator also the writer of the other viewpoints?   Certainly a possibility, but not one that seemed to me to add much to the story.&lt;br /&gt;&lt;br /&gt;I also read  &lt;span style="font-weight: bold;"&gt;Slant&lt;/span&gt; by Greg Bear and &lt;span style="font-weight: bold;"&gt;Beowulf's Children&lt;/span&gt; by Niven, Pournelle and Barnes - on which I will not comment,  and started &lt;span style="font-weight: bold;"&gt;A Distant Mirror&lt;/span&gt; by Barbara Tuchman and &lt;span style="font-weight: bold;"&gt;Snowqueen&lt;/span&gt; by Joan Vinge.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h1 id="firstHeading" class="firstHeading"&gt;&lt;br /&gt;&lt;/h1&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-5835993428331036749?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/5835993428331036749/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=5835993428331036749' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5835993428331036749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5835993428331036749'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/06/of-course-i-was-wrong-in-my-previous.html' title='Reading with the Flu'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-595703391507197687</id><published>2009-06-17T07:56:00.000-07:00</published><updated>2009-06-17T08:08:03.751-07:00</updated><title type='text'></title><content type='html'>One of the advantages of doing things this way is that now we can build the tree containing the pieces of the anagram in such a way as to guarantee that there are no duplicates.    Consider "staringstaring" again.   This is sorted before searching for the anagrams giving  "aaggiinnrrsstt".   Now look up in the trie the first character of that string.    Any anagram of the string must contain that first character, so, of course, if we find no lookup for "a" then there are no anagrams.   If we find all the words in the subtrie of the trie containing "a" and any other characters of the string, then we can look up all the anagrams for them in the top level of the trie using the same procedure and building subtrees along the way.   These then get stitched together into the complete tree.   Even better, we don't need to even bother with the second "a" when we start at the top again because we already have all the anagrams that use an "a" and the multiplicity doesn't matter.&lt;br /&gt;&lt;br /&gt;Even better, if you look at the structure of the tree of anagrams we're building, it is (drum roll please) just a trie again but with the indices being strings, so if we paramaterize the Trie we get the second tree we're using essentially for free.   In this trie though, we don't need to remember values at the end points (though having something there doesn't hurt), we use the path down through the trie as the information we need.   Indeed, if we construct it correctly, every node in the trie corresponds to a partial anagram, so the path to every leaf node describes the anagram (of sorted words, remember). &lt;br /&gt;&lt;br /&gt;Of course, we're not using this trie the same way - the first trie is used as a search device, the second as a storage device, but the shape of the trie is the same in both cases.    Also, the first trie is built from the top down - starting with an empty top node and adding things to it as we go, and the second from the bottom up.   Also in the latter case,  we need to prune empty subtries (which correspond to "failure to anagram").&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-595703391507197687?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/595703391507197687/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=595703391507197687' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/595703391507197687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/595703391507197687'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/06/one-of-advantages-of-doing-things-this.html' title=''/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-230642814078302444</id><published>2009-06-12T11:03:00.000-07:00</published><updated>2009-06-12T11:39:43.093-07:00</updated><title type='text'>Anagrams</title><content type='html'>I do (sporadically) cryptic crosswords.   I used to do regular crosswords, but after a while they became less challenging - when I could do the NY Times Sunday Crossword in an hour or so (with perhaps five or six squares open), I started paying more attention to cryptics. &lt;br /&gt;&lt;br /&gt;I'd been exposed to cryptics when I was in the Peace Corps and we could, from time to time, get the Zambian newspaper which had a cryptic although those were mostly beyond my skills.   When I returned to the US, I found Stephen Sondheim's book of cryptics and was hooked - though they were difficult indeed, I could at least make headway with them (and now I wish I'd xeroxed the pages so I could do them again).&lt;br /&gt;&lt;br /&gt;In any case, cryptics often use anagrams, so one day a while ago I wrote an anagram generator in Haskell.   You gave it a dictionary and a string and it would generate all the anagrams from that string consisting of words in the dictionary.    It was messy and didn't cope with repeated anagrams well. &lt;br /&gt;&lt;br /&gt;Just a short time ago I decided to redo this and try to make it cleaner.     I decided to reuse one of the main parts of the previous version, that of using a trie to store the words from the dictionary, but to change it around a bit.   A &lt;a href="http://en.wikipedia.org/wiki/Trie"&gt;trie &lt;/a&gt;is a kind of tree that stores subtrees indexed by prefix elements (in this case characters) of the objects stored in it.    (Wikipedia has a much better picture than I can manage, I suspect).    In the previous version, the nodes at the end of each string contained the words that ended up there.   Words?   Yes, I sorted the characters in the word before entering it in the trie, so multiple words ("tar", "rat", "art") could map to a single node.     In this version, I'm still going to use a trie in essentially the same way, but instead of storing all the words that sort to the same string in the nodes, I'm going to store only the sorted string and provide for a map (Data.Map) to associate the sorted string with the list of words that are anagrams.&lt;br /&gt;&lt;br /&gt;The main problem with the previous version was that a word could have multiple anagrams and these might (given the way I searched the trie) pop up in different orders.    But the duplicates were always generated (which was both space and time costly) and then duplicates were eliminated (time costly).   For example,  "staring" would have ["rat", "sing"], ["tar", "gins"], ["art", "sing"], ["sing", "art"] appear in the list of anagrams, but these are really all more or less the same and could have been better represented as ["art", "igns"] where each anagram is in sorted order, and the anagrams themselves are sorted.    Then, a final step could generate all the possibilities from this string by looking at the mapped values of "art" and "igns" and making all the combinations.&lt;br /&gt;&lt;br /&gt;But there's another step.   Consider "staringstaring".   This sorts to "aaggiinnrrsstt".  Suppose that we determine that "agg" is (I think it is) the first word alphabetically in the string (generated by the english word "gag").   Then we can drop those characters from the string and find all the anagrams for "aiinnrrsstt" (assuming there are any).   If we build a tree that has a node with a string in it and subnodes a list of similar trees so that any path from the root to a leaf consists of a list of strings whose sorted concatenation is the string we're looking for anagrams for, and if we do it correctly, then it becomes easy to produce all the anagrams for the string.&lt;br /&gt;&lt;br /&gt;More in a bit.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-230642814078302444?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/230642814078302444/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=230642814078302444' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/230642814078302444'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/230642814078302444'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/06/anagrams.html' title='Anagrams'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-5079285184606577635</id><published>2009-05-07T08:02:00.000-07:00</published><updated>2009-05-07T08:25:49.037-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='books history fantasy bioinformatics'/><title type='text'>What I've been reading</title><content type='html'>I read a bit.   Typically have a book or two I'm working on scattered around my house in different places where I might end up reading for a while.      From time to time I'll post on what I'm reading.&lt;br /&gt;&lt;br /&gt;My current (or recent) selections includes:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Dreadnought&lt;/span&gt; by &lt;span class="ptBrand"&gt;Robert K. Massie  - a massive (&gt; 1000 pages) history of England and Germany in the years leading up to the first world war.   For some reason I've been reading a bit about the first world war a lot and find it very interesting and this book is no exception.   The book focuses on the various personalities and especially on the people influencing the navies of the two countries.   It is a bit scattershot for someone like me who doesn't know a bit more about the history of the time, bouncing from one personality to another, but is generally a good read and gives a nice overview of the people involved.  &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Bioinformatics&lt;/span&gt; by Volker Sperschneider - I do voluntary book reviews for&lt;a href="http://www.reviews.com"&gt; Computing Reviews&lt;/a&gt; and this was a recent choice.   I try to pick books on topics I'd like to know more about as well as on topics that I do know something about and this was mostly new material for me.   I found it tough going most of the time and not as illuminating as I might like.   For instance he starts out without really framing the problem (analysis and construction of DNA) sequences and alternates between very formal discussions  and sketchy views of things.   It is published by Springer and I'm finding the books published by Springer to be generally poor in quality, but with some exceptions that are excellent.    &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Mage Guard of Hamor  - &lt;/span&gt;L.E. Modesitt Jr.   A couple of years back I picked up six of the "Magic of Recluse" series by Modesitt and found them eminently readable.    Since then as I've found new books in the series I've picked them up and read them.   They often get tagged as "young adult" but are good reads for most anyone who likes fantasy.   They do tend to be a bit repetive (boy discovers magic powers, boy has trouble with magic powers and the current power structure, boy rises above it all), but the magic involved makes a certain amount of sense in contest and is not unlimited - that is, there are few places where suddenly the magicians suddenly discover powers that come from nowhere.    And Modesitt is a good storyteller and that makes up for a lot of the deficiencies.    Even better, while the story takes place in a single world, each book is more or less self contained (with a few of the stories spanning two books) and each gives a different view of the world.    &lt;br /&gt;&lt;br /&gt;One nice thing is that the stories jump back and forth in history, so you get another view of what happens.   In the first few books the heros tend to focus on "black magicians" and the "white magicians" are portrayed as being more or less evil, but as the series progresses we also get views of white magicians that manage to portray them as being good as well.    I've not started any of his other fantasy novels, but if more get published in this series I'll probably read them as well.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-5079285184606577635?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/5079285184606577635/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=5079285184606577635' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5079285184606577635'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/5079285184606577635'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/05/what-ive-been-reading.html' title='What I&apos;ve been reading'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-640126421026944189</id><published>2009-05-06T10:01:00.000-07:00</published><updated>2009-05-06T10:47:46.475-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken haskell'/><title type='text'>Kenken Comments</title><content type='html'>My (previously posted) simple kenken solver solved every kenken puzzle I tried it on but two (where I think I transcribed the problem wrong).   For the most part it seemed fast enough - taking about a half a second per puzzle.   Profiling shows that most of the work goes into checking the various constraints, so they might benefit from tuning.   &lt;br /&gt;&lt;br /&gt;I thought it might help to sort the cells before solving the puzzle to see if there was a benefit to (for instance) doing the cells in division and subtraction constraints first.   It turns out that that doesn't help much.   Worse yet, if the cells are sorted so that addition and multiplication are first  the run time goes from less than a second to hours.   I had expected the run time to increase, but the size of the increase was startling.   After a bit of consideration though, the reason became apparent. &lt;br /&gt;&lt;br /&gt;Currently the cells are processed along the top row, then the second row and so on, which means that once the top left cell has been assigned a tentative value, the cells in the first row (and first column) are already constrained by the row/column constraints as well as by the (local) constraints imposed by the blocks.   Thus fewer possibilities need to be considered.   If we have a puzzle in which there are two division constraints at two diagonally opposite corners, and these are considered first, then the row and column constraints will have little (or no) effect and the solver will be forced to consider many more possible values for the cells.   &lt;br /&gt;&lt;br /&gt;Thus, solving a row at a time from left to right is probably about as good an ordering as you can get for this (not very smart) solver.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-640126421026944189?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/640126421026944189/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=640126421026944189' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/640126421026944189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/640126421026944189'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/05/kenken-comments.html' title='Kenken Comments'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-8183388477378156356</id><published>2009-05-05T07:40:00.000-07:00</published><updated>2009-05-05T07:43:32.505-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Whole Kenken program</title><content type='html'>Here is the complete program including all the bits previously posted as well as some helper functions and the main driver.   It is set up in such a way that you can load it into ghci and then run "doPuzzle filename" to run a puzzle. &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import System &lt;br /&gt;import Char &lt;br /&gt;import Maybe &lt;br /&gt;import Control.Monad.State&lt;br /&gt;&lt;br /&gt;-- a puzzle has a size (so we know the limit of values to use)&lt;br /&gt;-- its original input as a list of strings (just in case we want to print it)&lt;br /&gt;-- a list of constraints &lt;br /&gt;-- and a list of cells with position/label&lt;br /&gt;-- the cells could be a list of lists, but doing the lookup in a data set&lt;br /&gt;-- this size isn't likely to be the limiting factor and we'll abstract&lt;br /&gt;-- over getting a cell by x,y coordinates anyway &lt;br /&gt;&lt;br /&gt;data Puzzle = Puzzle { &lt;br /&gt;      psize :: Int, &lt;br /&gt;      origInput ::  [String],  &lt;br /&gt;      constraints :: [Constraint], &lt;br /&gt;      pcells :: [Cell] &lt;br /&gt;    } &lt;br /&gt;   &lt;br /&gt;-- constraints have labels (from the input description)&lt;br /&gt;-- operations (the arithmetic operators as strings)&lt;br /&gt;-- target values &lt;br /&gt;-- and a list of the cells that make up the constraint &lt;br /&gt;data Constraint =  Constraint &lt;br /&gt;                   { conlabel :: String,&lt;br /&gt;                     conop :: String,&lt;br /&gt;                     contarget :: Int,&lt;br /&gt;                     concells :: [Cell] &lt;br /&gt;                   }&lt;br /&gt;                  deriving Show &lt;br /&gt;&lt;br /&gt;-- each cell in the puzzle has a position (cx, cy) and a label&lt;br /&gt;-- corresponding to the constraint it is in &lt;br /&gt;data Cell = Cell {&lt;br /&gt;              clabel ::  String,&lt;br /&gt;              cx :: Int,&lt;br /&gt;              cy :: Int&lt;br /&gt;          }&lt;br /&gt;          deriving (Eq, Show)&lt;br /&gt;&lt;br /&gt;-- an assignment is, well, an assignment of a value to a cell&lt;br /&gt;&lt;br /&gt;data Assignment = Assignment { acell :: Cell, avalue :: Int } &lt;br /&gt;                deriving (Show, Eq)&lt;br /&gt;&lt;br /&gt;-- a possibility represents a "possible" solution to the puzzle&lt;br /&gt;&lt;br /&gt;type Possibility = [ Assignment ] &lt;br /&gt;&lt;br /&gt;-- The PuzzleM type contains the base puzzle&lt;br /&gt;&lt;br /&gt;type PuzzleM = StateT Puzzle IO&lt;br /&gt;&lt;br /&gt;getPuzzle :: PuzzleM Puzzle &lt;br /&gt;getPuzzle =  get &lt;br /&gt;&lt;br /&gt;getConstraints = do &lt;br /&gt;  p &lt;- getPuzzle &lt;br /&gt;  return $ constraints p &lt;br /&gt;&lt;br /&gt;-- not a fancy show, but shows the pieces - quick and easy &lt;br /&gt;instance Show Puzzle where&lt;br /&gt;   show (Puzzle{psize=s, origInput=inp,constraints=cos,pcells=ces}) = &lt;br /&gt;     unlines $ ["Puzzle::", "size="++(show s)] &lt;br /&gt;                 ++ inp&lt;br /&gt;                 ++  (map show cos) &lt;br /&gt;                 ++ (map show ces)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;strip l = sl &lt;br /&gt;  where &lt;br /&gt;      sl = reverse $ dropWhile isSpace $ reverse $ dropWhile isSpace l &lt;br /&gt;&lt;br /&gt;parse :: String -&gt; Puzzle&lt;br /&gt;parse s = Puzzle {psize=size, origInput=plines, constraints=constraintList, pcells=cellList}&lt;br /&gt;          where &lt;br /&gt;            plines = map strip $ lines s &lt;br /&gt;            (cellLines, constraintLines) = break ([]==) plines&lt;br /&gt;            size = length cellLines &lt;br /&gt;            cellList = doCellLines 0 cellLines &lt;br /&gt;            constraintList = parseConstraintLines cellList $ tail constraintLines &lt;br /&gt;&lt;br /&gt;doCellLines :: Int -&gt; [String] -&gt; [Cell] &lt;br /&gt;doCellLines i []     = [] &lt;br /&gt;doCellLines i (l:ls) = let l1 = zip [0..] l &lt;br /&gt;                           mkcell (xpos, y) = Cell { clabel=[y], cx=xpos, cy=i} &lt;br /&gt;                           l2 = map mkcell l1 &lt;br /&gt;                           in l2 ++ (doCellLines (i+1) ls) &lt;br /&gt;&lt;br /&gt;parseConstraintLines cells lines = map (parseConstraint cells) (filter ("" /=) lines)&lt;br /&gt;&lt;br /&gt;parseConstraint cells l =  Constraint {conlabel= label, &lt;br /&gt;                                   conop = op, &lt;br /&gt;                               contarget = target,&lt;br /&gt;                                   concells = clist } &lt;br /&gt;                    where &lt;br /&gt;                      (label,rest) = break ('='==) l &lt;br /&gt;                      (starget, op) = break (not.isDigit) $ tail rest &lt;br /&gt;                      target = read starget         &lt;br /&gt;                      clist = filter (\c -&gt; clabel c == label) cells &lt;br /&gt;&lt;br /&gt;showPuzzle = do &lt;br /&gt;                p &lt;- getPuzzle&lt;br /&gt;                liftIO $ putStrLn $ show p &lt;br /&gt;&lt;br /&gt;solve :: [Cell] -&gt; Possibility -&gt; PuzzleM Possibility&lt;br /&gt;solve [] assList  = return assList &lt;br /&gt;solve cl@(c:cs) assList  = do &lt;br /&gt;                           s &lt;- psize `liftM` getPuzzle &lt;br /&gt;                           let pass = map (\v -&gt; Assignment{ acell=c, avalue=v}) [1..s]&lt;br /&gt;                               passes = map (:assList) pass &lt;br /&gt;                           solve1 cs passes &lt;br /&gt;&lt;br /&gt;solve1 cells [] = return [] &lt;br /&gt;solve1 cells pl@(p:ps) = do &lt;br /&gt;                        good &lt;- okSoFar p &lt;br /&gt;                        if good&lt;br /&gt;                           then do solved &lt;- solve cells p&lt;br /&gt;                                   if solved /= [] &lt;br /&gt;                                      then return solved&lt;br /&gt;                                      else solve1 cells ps &lt;br /&gt;                           else solve1 cells ps &lt;br /&gt;&lt;br /&gt;allRowsOK p = do &lt;br /&gt;                s &lt;- psize `liftM` getPuzzle&lt;br /&gt;                return $ and $ map (rowOK s p) [0..s-1] &lt;br /&gt;&lt;br /&gt;allColsOK p = do &lt;br /&gt;                s &lt;- psize `liftM` getPuzzle&lt;br /&gt;                return $ and $ map (colOK s p) [0..s-1] &lt;br /&gt;&lt;br /&gt;rowOK s plist row = allDifferent (map avalue inrow) &lt;br /&gt;    where &lt;br /&gt;      inrow = filter (\x -&gt; (row == (cy $ acell x))) plist &lt;br /&gt;&lt;br /&gt;colOK s plist col = allDifferent (map avalue incol) &lt;br /&gt;    where &lt;br /&gt;      incol = filter (\x -&gt; (col == (cx $ acell x))) plist &lt;br /&gt;                               &lt;br /&gt;allDifferent [] = True &lt;br /&gt;allDifferent (x:xs) = (not $ elem x xs) &amp;&amp; allDifferent xs &lt;br /&gt;&lt;br /&gt;allConsOK p = do &lt;br /&gt;                 conlist &lt;- constraints `liftM` getPuzzle  &lt;br /&gt;                 return $ and $ map (conOK p) conlist &lt;br /&gt;&lt;br /&gt;conOK p constraint = checkCon convals contype target cl&lt;br /&gt;    where &lt;br /&gt;      concl = concells constraint &lt;br /&gt;      convals = map avalue $ filter (\x -&gt; ( acell x) `elem` concl) p&lt;br /&gt;      contype = conop constraint &lt;br /&gt;      target = contarget constraint &lt;br /&gt;      cl = length concl &lt;br /&gt;&lt;br /&gt;checkCon [] _   tgt _ = True       &lt;br /&gt;checkCon cl "=" tgt _ = tgt == cl !! 0&lt;br /&gt;checkCon cl "*" tgt l = if length cl == l &lt;br /&gt;                           then tgt == product cl &lt;br /&gt;                           else 0== tgt `mod` (product cl) &lt;br /&gt;checkCon cl "+" tgt l = if length cl == l&lt;br /&gt;                           then tgt == sum cl &lt;br /&gt;                           else tgt &gt;= sum cl &lt;br /&gt;checkCon cl "-" tgt _&lt;br /&gt;  | length cl &gt; 2   = False &lt;br /&gt;  | length cl == 2  = abs(cl !! 0 - cl !! 1) == tgt &lt;br /&gt;  | length cl == 1  = True&lt;br /&gt;&lt;br /&gt;checkCon cl "/" tgt  _&lt;br /&gt;  | length cl &gt; 2   = False &lt;br /&gt;  | length cl == 1  = True &lt;br /&gt;  | length cl == 2  = let a = cl !! 0 &lt;br /&gt;                          b = cl !! 1&lt;br /&gt;                      in  (a `div` b) == tgt  || (b `div` a) == tgt&lt;br /&gt;&lt;br /&gt;okSoFar p = do &lt;br /&gt;               rowsOK &lt;- allRowsOK p &lt;br /&gt;               colsOK &lt;- allColsOK p &lt;br /&gt;               consOK &lt;- allConsOK p &lt;br /&gt;               return $ rowsOK &amp;&amp; colsOK &amp;&amp; consOK &lt;br /&gt;&lt;br /&gt;showKnownCells al s =   unlines $ map getRow [0..s-1] &lt;br /&gt;                    where &lt;br /&gt;                      getCellByRowCol al r c = filter (\x -&gt; (r == (cy $ acell x)) &amp;&amp; (c == (cx $ acell x))) al &lt;br /&gt;                      getRow r = unwords $ map doCell $ map (getCellByRowCol al r) [0..s-1] &lt;br /&gt;                      doCell [] = " "&lt;br /&gt;                      doCell (x:xs) = show $ avalue x &lt;br /&gt;                      &lt;br /&gt;&lt;br /&gt;runPuzzle  = do&lt;br /&gt;                {- showPuzzle  -} &lt;br /&gt;                cl &lt;- pcells `liftM` getPuzzle &lt;br /&gt;                solve cl [] &lt;br /&gt;&lt;br /&gt;showPossibles p =   unlines $ map show p&lt;br /&gt;&lt;br /&gt;main = do &lt;br /&gt;          args &lt;- getArgs &lt;br /&gt;          doPuzzle (args !! 0)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;doPuzzle fn = do &lt;br /&gt;  inp &lt;- readFile fn &lt;br /&gt;  let puzzle = parse inp &lt;br /&gt;  &lt;br /&gt;  putStrLn inp &lt;br /&gt;  putStrLn "about to evalState puzzle..."&lt;br /&gt;  (soln,p) &lt;- runStateT runPuzzle puzzle&lt;br /&gt;  putStrLn $ showKnownCells soln 6&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-8183388477378156356?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/8183388477378156356/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=8183388477378156356' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/8183388477378156356'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/8183388477378156356'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/05/whole-kenken-program.html' title='Whole Kenken program'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-8852210743520977663</id><published>2009-05-04T11:21:00.000-07:00</published><updated>2009-05-04T11:27:22.271-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Kenken Solver</title><content type='html'>The solver is the remaining major piece of the kenken program.   It is simple enough here - the function solve takes a list of cells that are not yet assigned values, a list of cells with values (a "Possibility") and returns a "Possibility" that should, if not null, result in a solution.   To do this it takes the next unassigned cell from the list of cells, makes a list of all the possible values it might take (that is the values from 1 up to the size of the puzzle - no culling is attempted) and tries to solve the puzzle with each of those values being assigned to the cell.&lt;br /&gt;&lt;br /&gt;This looks like : &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;solve :: [Cell] -&gt; Possibility -&gt; PuzzleM Possibility&lt;br /&gt;solve [] assList  = return assList &lt;br /&gt;solve cl@(c:cs) assList  = do &lt;br /&gt;                           s &lt;- psize `liftM` getPuzzle &lt;br /&gt;                           let pass = map (\v -&gt; Assignment{ acell=c, avalue=v}) [1..s]&lt;br /&gt;                               passes = map (:assList) pass &lt;br /&gt;                           solve1 cs passes &lt;br /&gt;&lt;br /&gt;solve1 cells [] = return [] &lt;br /&gt;solve1 cells pl@(p:ps) = do &lt;br /&gt;                        good &lt;- okSoFar p &lt;br /&gt;                        if good&lt;br /&gt;                           then do solved &lt;- solve cells p&lt;br /&gt;                                   if solved /= [] &lt;br /&gt;                                      then return solved&lt;br /&gt;                                      else solve1 cells ps &lt;br /&gt;                           else solve1 cells ps &lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;I think that if I used List as the base monad in the stack (instead of IO) I could have used the nondeterminism aspect to simplify this, but I did not, so here's what I have.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-8852210743520977663?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/8852210743520977663/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=8852210743520977663' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/8852210743520977663'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/8852210743520977663'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/05/kenken-solver.html' title='Kenken Solver'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-611931573854832809</id><published>2009-04-29T08:27:00.000-07:00</published><updated>2009-04-29T08:38:39.958-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken haskell'/><title type='text'>Kenken constraints</title><content type='html'>There are several constraints in a kenken puzzle.   First, each row and each column must have the numbers from 1 to the size of the puzzle with no repeats.   Then each arithmetic constraint needs to be satisfied.   &lt;br /&gt;&lt;br /&gt;The row and column constraints are easy enough - just require that all the numbers in a row or column are different - and this works for partial rows/columns as well as full ones.   Only the row code is included here (a following post will contain all of the code).  This code checks all of the rows using a map that checks each row by index and uses a helper function "allDifferent" that checks to be sure that all the numbers in a row are different.   I suspect there may be a better way using "nub" but this is simple enough.  &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;allRowsOK p = do &lt;br /&gt;                s &lt;- psize `liftM` getPuzzle&lt;br /&gt;                return $ and $ map (rowOK s p) [0..s-1] &lt;br /&gt;&lt;br /&gt;rowOK s plist row = allDifferent (map avalue inrow) &lt;br /&gt;    where &lt;br /&gt;      inrow = filter (\x -&gt; (row == (cy $ acell x))) plist &lt;br /&gt;&lt;br /&gt;allDifferent [] = True &lt;br /&gt;allDifferent (x:xs) = (not $ elem x xs) &amp;&amp; allDifferent xs &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The constraints are more difficult.   These are checked with "checkCon" which takes a list of cell values, a target and an operator.    A "+" constraint requires that the numbers so far add up to less than the target.   A "*" constraint requires that the numbers so far add up to a divisor of the target.   For divide and difference, a zero length list of cells is ok as is a list of cells with one entry and a list of cells with two entries is checked both ways and if either works the constraint is ok.  This is tightened up a bit in a later version of the code, but this works for this simple version.  &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;checkCon [] _   tgt _ = True       &lt;br /&gt;checkCon cl "=" tgt _ = tgt == cl !! 0&lt;br /&gt;checkCon cl "*" tgt l = if length cl == l &lt;br /&gt;                           then tgt == product cl &lt;br /&gt;                           else 0== tgt `mod` (product cl) &lt;br /&gt;checkCon cl "+" tgt l = if length cl == l&lt;br /&gt;                           then tgt == sum cl &lt;br /&gt;                           else tgt &gt;= sum cl &lt;br /&gt;checkCon cl "-" tgt _&lt;br /&gt;  | length cl &gt; 2   = False &lt;br /&gt;  | length cl == 2  = abs(cl !! 0 - cl !! 1) == tgt &lt;br /&gt;  | length cl == 1  = True&lt;br /&gt;&lt;br /&gt;checkCon cl "/" tgt  _&lt;br /&gt;  | length cl &gt; 2   = False &lt;br /&gt;  | length cl == 1  = True &lt;br /&gt;  | length cl == 2  = let a = cl !! 0 &lt;br /&gt;                          b = cl !! 1&lt;br /&gt;                      in  (a `div` b) == tgt  || (b `div` a) == tgt&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-611931573854832809?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/611931573854832809/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=611931573854832809' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/611931573854832809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/611931573854832809'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/04/kenken-constraints.html' title='Kenken constraints'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-9179468821661009951</id><published>2009-04-28T08:23:00.001-07:00</published><updated>2009-04-29T08:39:12.364-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken haskell'/><title type='text'>kenken parser</title><content type='html'>The parser for my kenken input format is simple and doesn't cope well with errors in the format.   That's ok for me, as this was more or less an exercise in programming and not an attempt to build anything that anyone but me might use (and just became a blog post because, well, it was there). &lt;br /&gt;&lt;br /&gt;In any case, I read in the file in one gulp using readFile, then pass that to the parse routine, so the parser is pure code.   That gets passed to "parse" which  breaks the input into lines (using "lines", natch), strips each input line of spaces and breaks the input into two parts at the first blank line.   The size of the first list (the block of letters) is used to determine the size of the puzzle and then two helper functions are called, the first builds the list of cells and the second builds the list of constraints with the list of cells and the list of constraint definitions as its input.  &lt;br /&gt;&lt;br /&gt;Each of the lines in the block of cells is used to generate a Cell with the position derived using a counter passed to a recursive routine (I'd do it differently now, but rewriting would probably end up with my building a more robust parser and I'm not sure I want to do that).   &lt;br /&gt;&lt;br /&gt;Each constraint line is broken on the equals sign - the label for the constraint is the part before the equals, the target is the integer value of the list of digits and the operator (which must be present) is the last bit.  &lt;br /&gt;&lt;br /&gt;Without further ado, here is the parsing section of the solver (not all functions have types, but in later versions of this, the types are included pretty much everywhere).  &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;parse :: String -&gt; Puzzle&lt;br /&gt;parse s = Puzzle {psize=size, origInput=plines, constraints=constraintList, pcells=cellList}&lt;br /&gt;          where &lt;br /&gt;            plines = map strip $ lines s &lt;br /&gt;            (cellLines, constraintLines) = break ([]==) plines&lt;br /&gt;            size = length cellLines &lt;br /&gt;            cellList = doCellLines 0 cellLines &lt;br /&gt;            constraintList = parseConstraintLines cellList $ tail constraintLines &lt;br /&gt;&lt;br /&gt;doCellLines :: Int -&gt; [String] -&gt; [Cell] &lt;br /&gt;doCellLines i []     = [] &lt;br /&gt;doCellLines i (l:ls) = let l1 = zip [0..] l &lt;br /&gt;                           mkcell (xpos, y) = Cell { clabel=[y], cx=xpos, cy=i} &lt;br /&gt;                           l2 = map mkcell l1 &lt;br /&gt;                           in l2 ++ (doCellLines (i+1) ls) &lt;br /&gt;&lt;br /&gt;parseConstraintLines cells lines = map (parseConstraint cells) (filter ("" /=) lines)&lt;br /&gt;&lt;br /&gt;parseConstraint cells l =  Constraint {conlabel= label, &lt;br /&gt;                                   conop = op, &lt;br /&gt;                               contarget = target,&lt;br /&gt;                                   concells = clist } &lt;br /&gt;                    where &lt;br /&gt;                      (label,rest) = break ('='==) l &lt;br /&gt;                      (starget, op) = break (not.isDigit) $ tail rest &lt;br /&gt;                      target = read starget         &lt;br /&gt;                      clist = filter (\c -&gt; clabel c == label) cells &lt;br /&gt;strip l = sl &lt;br /&gt;  where &lt;br /&gt;      sl = reverse $ dropWhile isSpace $ reverse $ dropWhile isSpace l &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-9179468821661009951?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/9179468821661009951/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=9179468821661009951' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/9179468821661009951'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/9179468821661009951'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/04/parser-for-my-kenken-input-format-is.html' title='kenken parser'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-100128200867720582</id><published>2009-04-24T07:42:00.000-07:00</published><updated>2009-04-29T08:39:57.786-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='kenken'/><category scheme='http://www.blogger.com/atom/ns#' term='haskell'/><title type='text'>Simple kenken solver in haskell</title><content type='html'>Recently the NY Times started doing Kenken puzzles.   These are numeric puzzles in the sudoku vein.   For a good overview, see&lt;a href="http://en.wikipedia.org/wiki/KenKen"&gt; the wikipedia page &lt;/a&gt;where they have a nice sample puzzle.&lt;br /&gt;&lt;br /&gt;I solved a couple of these and then decided that it was time to build a solver.   My first solver (in Python) dissolved in a flurry of overcomplicated algorithms and data structures and I decided to start from scratch in Haskell (in part to try to improve my Haskell skills).    First though, I needed an input format.     I constructed one that was simple, easy to derive from a puzzle and easy to edit.  In this format the puzzle grid is laid out with letters indicating the blocks and a list of constraints on the blocks on subsequent lines.  Each constraint is a label (from the grid), an equals sign, a target value (numeric) and an operator ("+", "-", "/", "*", "=" - used when the value in the cell is set).   This format has the advantage that it is easy for me to read and easy to parse.&lt;br /&gt;&lt;br /&gt;The puzzle from the wikipedia entry is given below. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;abbcdd&lt;br /&gt;aeecfd&lt;br /&gt;gghhfd&lt;br /&gt;ggijkk&lt;br /&gt;llijjm&lt;br /&gt;nnnoom&lt;br /&gt;&lt;br /&gt;a=11+&lt;br /&gt;b=2/&lt;br /&gt;c=20*&lt;br /&gt;d=6*&lt;br /&gt;e=3-&lt;br /&gt;f=3/&lt;br /&gt;g=240*&lt;br /&gt;h=6*&lt;br /&gt;i=6*&lt;br /&gt;j=7+&lt;br /&gt;k=30*&lt;br /&gt;l=6*&lt;br /&gt;m=9+&lt;br /&gt;n=8+&lt;br /&gt;o=2/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-family:Georgia,serif;"&gt;&lt;br /&gt;&lt;br /&gt;For example, the first block (labeled "a" in the square) needs to have a sum of 11.&lt;br /&gt;&lt;br /&gt;My first solver was a simple backtracking recursive solver.   It didn't use any constraint information except to verify that the current solution was ok.&lt;br /&gt;&lt;br /&gt;To do this, I built several data structures.   First, a Cell is an x,y location and a Constraint label (such as "a" above).   I use the (x,y) information in the Cell to locate it rather than keeping a two dimensional array (or list of lists).   This does mean that in several places I scan the list of cells to find a cell, but since the list of cells is typically small for these puzzles, that is not that much of a problem :&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data Cell = Cell {&lt;br /&gt;            clabel ::  String,&lt;br /&gt;            cx :: Int,&lt;br /&gt;            cy :: Int&lt;br /&gt;        }&lt;br /&gt;        deriving (Eq, Show)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Next, a Constraint is a label (from the puzzle input), a target value, an operation (as a string) and a list of cells.  The list of cells could also be constructed as needed, but since checking the constraint always required looking at the list, I put this in.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data Constraint =  Constraint&lt;br /&gt;                  { conlabel :: String,&lt;br /&gt;                    conop :: String,&lt;br /&gt;                    contarget :: Int,&lt;br /&gt;                    concells :: [Cell]&lt;br /&gt;                  }&lt;br /&gt;                 deriving Show&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;An Assignment is a Cell - value pair, and we build up a list of possible assignments in a Possibility (that is, a possible solution).  Assignments are not part of the puzzle, but are&lt;br /&gt;carried around in the recursive calls.  &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data Assignment = Assignment { acell :: Cell, avalue :: Int }&lt;br /&gt;               deriving (Show, Eq)&lt;br /&gt;&lt;br /&gt;type Possibility = [ Assignment ]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A puzzle has a size, the original input (which is useful for debugging), a list of constraints and a list of cells.   Since I'd like to pass the puzzle around in lots of places, I'm building a State Monad of this as well.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data Puzzle = Puzzle {&lt;br /&gt;     psize :: Int,&lt;br /&gt;     origInput ::  [String], &lt;br /&gt;     constraints :: [Constraint],&lt;br /&gt;     pcells :: [Cell]&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;type PuzzleM = StateT Puzzle IO&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next post: parsing the input.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-100128200867720582?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/100128200867720582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=100128200867720582' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/100128200867720582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/100128200867720582'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2009/04/recently-ny-times-started-doing-kenken.html' title='Simple kenken solver in haskell'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-108502703125930653</id><published>2004-05-19T21:19:00.000-07:00</published><updated>2004-05-19T21:23:51.260-07:00</updated><title type='text'>Music downloading</title><content type='html'>I'm getting increasingly irritated by the use of the words "theft" and "piracy" with respect to music (and such) downloading from the web. &lt;br /&gt;&lt;br /&gt;While I'm no IP radical by any means, downloading music is not stealing in any real way. Copyright (in the US anyway) is a limited term legal gift of a monopoly to content creators from the people of the country. I'll grant you that downloaders are taking back their gift early and without the full blessing of the law - but its not theft (or piracy).  &lt;br /&gt; &lt;br /&gt;If anything is stealing (or piracy) it is corporations like Disney getting copyright indefinitely extended. And in terms of the value that the corporations stole from us all with the copyright extension act, I'd say they are by far the bigger thieves.&lt;br /&gt;&lt;br /&gt;And I don't even download music.  &lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-108502703125930653?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/108502703125930653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=108502703125930653' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/108502703125930653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/108502703125930653'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2004/05/music-downloading.html' title='Music downloading'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7012287.post-108476220313577789</id><published>2004-05-16T19:49:00.000-07:00</published><updated>2004-05-16T19:50:03.136-07:00</updated><title type='text'>Just a first post</title><content type='html'>Trying a first post.&lt;br /&gt;&lt;br /&gt;Or, as the tradition goes....&lt;br /&gt;&lt;br /&gt;Hello world!&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7012287-108476220313577789?l=jefu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jefu.blogspot.com/feeds/108476220313577789/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7012287&amp;postID=108476220313577789' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/108476220313577789'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7012287/posts/default/108476220313577789'/><link rel='alternate' type='text/html' href='http://jefu.blogspot.com/2004/05/just-first-post.html' title='Just a first post'/><author><name>jefu</name><uri>http://www.blogger.com/profile/02233639532468393698</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
