<?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-190798191079317856</id><updated>2011-12-31T16:04:00.475-06:00</updated><category term='ccnet'/><category term='new york city'/><category term='wyoming'/><category term='civil engineering'/><category term='wittgenstein'/><category term='bug'/><category term='vienna'/><category term='latex'/><category term='continuation'/><category term='petzold'/><category term='monad'/><category term='argument'/><category term='rdbms'/><category term='pangram'/><category term='ny'/><category term='cs'/><category term='evidence'/><category term='england'/><category term='agile'/><category term='python'/><category term='public works'/><category term='mortimer'/><category term='epidemic'/><category term='.net'/><category term='hg'/><category term='dotnet'/><category term='tufte'/><category term='new york'/><category term='review'/><category term='yellowstone'/><category term='work'/><category term='rake'/><category term='sort'/><category term='science'/><category term='flyfishing'/><category term='msft'/><category term='story'/><category term='vs.net'/><category term='parkway'/><category term='computer science'/><category term='wwii'/><category term='java'/><category term='vacation'/><category term='engineering'/><category term='expressway'/><category term='politics'/><category term='austria'/><category term='programming'/><category term='dvcs'/><category term='whitehead'/><category term='trampoline'/><category term='lambda'/><category term='book'/><category term='highway'/><category term='halting problem'/><category term='montana'/><category term='turing'/><category term='palindrome'/><category term='voronoi'/><category term='interview'/><category term='infrastructure'/><category term='cps'/><category term='church'/><category term='ita'/><category term='orm'/><category term='cholera'/><category term='history'/><category term='search'/><category term='fishing'/><category term='cruisecontrol.net'/><category term='robert moses'/><category term='exception'/><category term='great war'/><category term='johnson'/><category term='cvcs'/><category term='general recursive'/><category term='computability'/><category term='nyc'/><category term='medieval'/><category term='biography'/><category term='snow'/><category term='vcs'/><category term='nhibernate'/><category term='medicine'/><category term='bugzilla'/><category term='svn'/><title type='text'>Sean Foy</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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>21</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-190798191079317856.post-2719141810671820158</id><published>2011-12-31T13:35:00.009-06:00</published><updated>2011-12-31T16:04:00.486-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='great war'/><category scheme='http://www.blogger.com/atom/ns#' term='austria'/><category scheme='http://www.blogger.com/atom/ns#' term='vienna'/><category scheme='http://www.blogger.com/atom/ns#' term='wittgenstein'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><category scheme='http://www.blogger.com/atom/ns#' term='wwii'/><category scheme='http://www.blogger.com/atom/ns#' term='biography'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book Review: The House of Wittgenstein</title><content type='html'>&lt;p&gt;This is a review of &lt;a href="http://openlibrary.org/works/OL2907264W/The_House_of_Wittgenstein"&gt;The House of Wittgenstein: A Family at War&lt;/a&gt; by Alexander Waugh.&lt;/p&gt;&lt;p&gt;This book traces the lives of four generations from the late nineteenth century to the early twenty-first. During this time: a great fortune was made in trade, real estate, and industry; a &lt;a href="http://en.wikipedia.org/wiki/Paul_Wittgenstein"&gt;pianist&lt;/a&gt; lost his right arm but continued to perform at a world-class level of proficiency; a &lt;a href="http://en.wikipedia.org/wiki/Ludwig_Wittgenstein"&gt;philosopher&lt;/a&gt; rose to international prominence; the family evaded Nazi persecution.&lt;/p&gt;&lt;p&gt;Some of the most interesting material for me concerns war time. For example, Paul was taken as a POW by the Russians during The Great War and was held at the krepostnaia katorga at Omsk. He was allowed to send letters home, but his letters do not describe the conditions for which the prison camp was infamous. Waugh notes that the Russians censored outbound letters and anyway many prisoners wanted to avoid worrying their families or felt shame for having been captured. Interestingly, the Austro-Hungarian Kriegsüberwachungsamt was censoring inbound mail:&lt;/p&gt;&lt;blockquote&gt;Letters have been received lately from our prisoners of war in enemy countries. In some of these letters the writers describe life in captivity in  a very favorable light. The spreading of such news among the troops and recruits is undesirable. The military censors are therefore to be instructed that such letters of our prisoners of war as may, by their contents, exercise an injurious influence are to be confiscated and not to be delivered to their addressees. [p. 75]&lt;/blockquote&gt;&lt;p&gt;The Austro-Hungarian government funded the war by printing money and consequently there was severe inflation during the twenties; "by August 1922 paper money was virtually worthless as consumer prices rose to a level 14,000 times higher than they had been before the war" [p. 128]. Trade embargoes further hampered commerce with the result that 96% of Austrian children were malnourished [ibid]. For the Wittgensteins, this led to conflicts between siblings about philanthropic and investment decisions.&lt;/p&gt;&lt;p&gt;It is pretty interesting to see how the Wittgenstein's understanding of the Nazis developed during WWII. Initially they supported the Heimwehr austrofascists against the Nazis in opposition of Anschluss, socialism, &amp;quot;exaggerated racial theories,&amp;quot; and &amp;quot;a semi-pagan German national religion&amp;quot;. When the Germans invaded, the Wittgensteins were frustrated in their attempt to sell some art abroad and annoyed by the new administration's insistence that they fly the Nazi flag over their palais, but they did not fear for their personal safety until later. They seem to have underestimated the sincerity of Nazi leaders with respect to racial rhetoric. In fact, their immense wealth and political connections outside the Reich did save them from the concentration camps even as strategic and tactical disagreements irreparably inflamed tensions among siblings.&lt;/p&gt;&lt;p&gt;This extraordinary group of people during an extraordinary period of time makes for an engaging narrative. It is competently written and my impression is that it is well-researched, but I certainly read it with variable velocity and interest. I recommend this book to those who are interested in the economic and political history of early twentieth century Europe, and curious about seeing those events through the perspective of one prominent family.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-2719141810671820158?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/2719141810671820158/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2011/12/book-review-house-of-wittgenstein.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2719141810671820158'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2719141810671820158'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2011/12/book-review-house-of-wittgenstein.html' title='Book Review: The House of Wittgenstein'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-6334701507489714224</id><published>2011-11-30T14:34:00.004-06:00</published><updated>2011-11-30T16:02:24.282-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lambda'/><category scheme='http://www.blogger.com/atom/ns#' term='general recursive'/><category scheme='http://www.blogger.com/atom/ns#' term='church'/><category scheme='http://www.blogger.com/atom/ns#' term='turing'/><category scheme='http://www.blogger.com/atom/ns#' term='computability'/><category scheme='http://www.blogger.com/atom/ns#' term='petzold'/><category scheme='http://www.blogger.com/atom/ns#' term='cs'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><category scheme='http://www.blogger.com/atom/ns#' term='computer science'/><category scheme='http://www.blogger.com/atom/ns#' term='halting problem'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book Review: The Annotated Turing</title><content type='html'>This is a review of &lt;a href="http://openlibrary.org/works/OL9372188W/The_Annotated_Turing"&gt;The Annotated Turing&lt;/a&gt; by Charles Petzold.&lt;br /&gt;&lt;br /&gt;The heart of the book is Turing's &lt;a href="http://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf"&gt;On Computable Numbers, with an Application to the Entscheidungsproblem&lt;/a&gt;, segmented and the segments interleaved with commentary and corrections from Petzold and Turing's own erratum. It is prefaced with some background material that will be a good refresher or even introduction for non-mathematicians. The end of the book draws connections between Turing and his/our contemporaries who carried forward his work.&lt;br /&gt;&lt;br /&gt;Before this, my exposure to the Church-Turing Thesis had always been indirect, via the later works of Kleene and others. It was interesting to see some of the differences in the original presentation of these ideas. For example, I didn't realize or remember that the Halting Problem was introduced by Martin Davis; Turing himself discussed mainly &amp;quot;circle-free&amp;quot; machines that would never halt. I also liked that Petzold gives a rounder portrait of Turing by the inclusion of material on Turing's personal history.&lt;br /&gt;&lt;br /&gt;I believe this book would be accessible to readers with no prior cs background. Check it out if you are interested in either computer science or the philosophy of mathematics.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-6334701507489714224?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/6334701507489714224/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2011/11/book-review-annotated-turing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/6334701507489714224'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/6334701507489714224'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2011/11/book-review-annotated-turing.html' title='Book Review: The Annotated Turing'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-4912615714251030983</id><published>2011-10-31T23:38:00.006-05:00</published><updated>2011-11-01T11:51:34.920-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='expressway'/><category scheme='http://www.blogger.com/atom/ns#' term='civil engineering'/><category scheme='http://www.blogger.com/atom/ns#' term='engineering'/><category scheme='http://www.blogger.com/atom/ns#' term='robert moses'/><category scheme='http://www.blogger.com/atom/ns#' term='highway'/><category scheme='http://www.blogger.com/atom/ns#' term='ny'/><category scheme='http://www.blogger.com/atom/ns#' term='nyc'/><category scheme='http://www.blogger.com/atom/ns#' term='parkway'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><category scheme='http://www.blogger.com/atom/ns#' term='new york'/><category scheme='http://www.blogger.com/atom/ns#' term='new york city'/><category scheme='http://www.blogger.com/atom/ns#' term='politics'/><category scheme='http://www.blogger.com/atom/ns#' term='public works'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book Review: The Power Broker: Robert Moses and the Fall of New York</title><content type='html'>&lt;a href="http://books.google.com/books/about/The_power_broker.html" title="The Power Broker: Robert Moses and the Fall of New York"&gt;Robert Moses&lt;/a&gt; was &lt;a href="http://www.joelonsoftware.com/articles/fog0000000073.html"&gt;smart and got things done&lt;/a&gt;. He showed extraordinary idealism in his early adulthood, and worked hard to apply his wealth, privilege, and considerable talents in hopes of making the world a better place. Over his lifetime, he mastered realpolitik and wielded considerable power; from his relatively modest nominal appointments, he dominated not only mayors of New York City but also the statehouse and in one conflict a sitting President of the United States. The legacy of his most effective decades using billions of dollars to remake the neighborhoods of New York City and establish the pattern of development for Long Island will persist for centuries, to the chagrin of many.&lt;br /&gt;&lt;br /&gt;Fittingly, this is a monumental biography with 1162 pages of main matter and followed by 84 pages of notes and references. It is occasionally indulgent, but on the whole I think it was well-edited. Moses was deeply flawed and immensely capable and driven; it is interesting to learn about how he amassed, used, and lost influence over his lifetime.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-4912615714251030983?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/4912615714251030983/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2011/10/book-review-power-broker-robert-moses.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/4912615714251030983'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/4912615714251030983'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2011/10/book-review-power-broker-robert-moses.html' title='Book Review: The Power Broker: Robert Moses and the Fall of New York'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-958399035013086647</id><published>2011-01-02T00:31:00.011-06:00</published><updated>2011-01-04T11:19:35.458-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mortimer'/><category scheme='http://www.blogger.com/atom/ns#' term='medieval'/><category scheme='http://www.blogger.com/atom/ns#' term='england'/><category scheme='http://www.blogger.com/atom/ns#' term='history'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book Review: The Time Traveler's Guide to Medieval England</title><content type='html'>This is a book review of &lt;a href="http://openlibrary.org/works/OL5852999W/The_time_traveler%27s_guide_to_medieval_England"&gt;The Time Traveler's Guide to Medieval England&lt;/a&gt; by Ian Mortimer.&lt;br /&gt;&lt;br /&gt;History is more fun without the politics&lt;br /&gt;&lt;br /&gt;Context is a vital consideration for historical interpretation, but ask a history student to summarize what he's learned and you'll likely hear exclusively about famous people and events. Some of these people were colorful characters, and many of the events make for good stories. But this book focuses on the complement of that history, highlighting the crucial but normally subordinated milieu of an age.&lt;br /&gt;&lt;br /&gt;Mortimer's guidebook tells of the customs, etiquette, food, clothing, laws, and economic conditions in which the 14th century English lived their lives. The book is written in a light-hearted style; it's a very entertaining way to cure some of your misconceptions and enrich your understanding of the period. Recommended.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-958399035013086647?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/958399035013086647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2011/01/book-review-time-travelers-guide-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/958399035013086647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/958399035013086647'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2011/01/book-review-time-travelers-guide-to.html' title='Book Review: The Time Traveler&apos;s Guide to Medieval England'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-8999135728286442647</id><published>2011-01-01T01:00:00.019-06:00</published><updated>2011-01-01T20:56:41.839-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='argument'/><category scheme='http://www.blogger.com/atom/ns#' term='medicine'/><category scheme='http://www.blogger.com/atom/ns#' term='johnson'/><category scheme='http://www.blogger.com/atom/ns#' term='epidemic'/><category scheme='http://www.blogger.com/atom/ns#' term='cholera'/><category scheme='http://www.blogger.com/atom/ns#' term='evidence'/><category scheme='http://www.blogger.com/atom/ns#' term='voronoi'/><category scheme='http://www.blogger.com/atom/ns#' term='tufte'/><category scheme='http://www.blogger.com/atom/ns#' term='whitehead'/><category scheme='http://www.blogger.com/atom/ns#' term='snow'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><category scheme='http://www.blogger.com/atom/ns#' term='science'/><category scheme='http://www.blogger.com/atom/ns#' term='book'/><title type='text'>Book Review: The Ghost Map</title><content type='html'>This is a review of &lt;a href="http://openlibrary.org/works/OL2668651W/The_Ghost_Map"&gt;The Ghost Map by Steven Johnson&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Johnson's account of John Snow, Henry Whitehead, and other key figures in the story of a nineteenth century London cholera epidemic is an engaging narrative with insights about scientific investigation and persuasive argument. Starting with an orientation to London c. 1854, the heart of the book explains how a particularly perspicacious investigator developed and tested hypotheses to reach correct and influential conclusions. Along the way we see how somewhat different approaches led others to different outcomes. Of course, the process of scientific discovery remains relevant to us in the 21st century, as are many of the issues concerning urban design and public policy.&lt;br /&gt;&lt;br /&gt;The Broad Street cholera epidemic of 1854 was in large part a consequence of urbanization with inadequate public works infrastructure, at a very early stage in the development of medical science and microbiology. Particularly in the last chapter, "Broad Street Revisited," Johnson discusses the relation between 1854 London and present-day first-world city planning. Citing &lt;a href="http://openlibrary.org/works/OL93641W/The_death_and_life_of_great_American_cities"&gt;Jane Jacobs&lt;/a&gt; and other contemporary sources, he discusses the architecture of cities, the role of government organizations such as the CDC, and even the War on Terror and nuclear non-proliferation. I think he overreached in this attempt to reinforce the continuing relevance of the Snow/Whitehead collaboration and to advocate for positions on tenuously connected modern issues.&lt;br /&gt;&lt;br /&gt;Readers of Tufte's &lt;a href="http://openlibrary.org/works/OL2824010W/Visual_explanations"&gt;Visual Explanations&lt;/a&gt; will recognize this epidemic from several pages in that book (it was also mentioned in &lt;a href="http://openlibrary.org/works/OL2824012W/The_visual_display_of_quantitative_information"&gt;The Visual Display of Quantitative Information&lt;/a&gt;). While Tufte focuses on an analysis of Snow's compelling maps as designed artifacts, Johnson describes in detail the scientific inquiry that led to the development of those maps. Johnson also offers a short critique of Tufte's treatment. It is disappointing that after describing &lt;a title="Detail from Snow's spot map of the Golden Square outbreak showing area enclosed within the Voronoi network diagram. Snow's original dotted line to denote equidistance between the Broad Street pump and the nearest alternative pump for procuring water has been replaced by a solid line for legibility." href="http://johnsnow.matrix.msu.edu/images/online_companion/chapter_images/fig12-6.jpg"&gt;Snow's second-edition Voronoi-enhanced map&lt;/a&gt; (&lt;a href="http://johnsnow.matrix.msu.edu/book_images12.php"&gt;see also&lt;/a&gt;) as &amp;quot;Snow's most significant contribution to the field of disease mapping&amp;quot; and calling out Tufte on having overlooked it, Johnson omits the diagram from his own book.&lt;br /&gt;&lt;br /&gt;The Ghost Map includes end notes, but the note numbers appear only in the notes and not in the text. The absence of a visual indication that more detail is available makes the end notes less likely to be used and made me less trusting about some of the author's assertions.&lt;br /&gt;&lt;br /&gt;I have pointed out some weaknesses in The Ghost Map, but on the whole I think it was well worth reading. If you are interested in Snow's maps, the history of epidemiology, or the process of scientific inquiry, I heartily recommend this book.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-8999135728286442647?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/8999135728286442647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2011/01/book-review-ghost-map.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8999135728286442647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8999135728286442647'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2011/01/book-review-ghost-map.html' title='Book Review: The Ghost Map'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-9064166640390707284</id><published>2010-04-22T16:48:00.032-05:00</published><updated>2010-07-04T10:10:35.759-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='palindrome'/><category scheme='http://www.blogger.com/atom/ns#' term='ita'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='interview'/><category scheme='http://www.blogger.com/atom/ns#' term='search'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='pangram'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Palindromic Pangrams</title><content type='html'>&lt;p&gt;This post details a solution to a disused hiring problem. If you are a graph search wiz, you will find yourself skimming; I assume readers either haven't done this before or have forgotten it mostly. Even experts might be interested in the pathology, so hopefully there's something here for everyone to enjoy.&lt;/p&gt;&lt;p&gt;I've been unusually quiet lately because I'm in the midst of a truly marathon interview cycle. The things I'm working on probably aren't interesting to other people, and they're taking up all of my time. But before this began I spent some time working on a hiring puzzle that I think is blog-worthy.&lt;/p&gt;&lt;p&gt;I couldn't share the details of my approach at the time that I was actively working on the problem, because that would hurt the company and other candidates by raising the question of plagiarism. In fact, ITA removed the problem I was working on from their web site, and when I wrote their recruiter to ask why, I found out it was because somebody else wrote about it! Now it seems the rumor that &lt;a href="http://news.ycombinator.com/item?id=1282583"&gt;Google is acquiring ITA&lt;/a&gt; is &lt;a href="http://googleblog.blogspot.com/2010/07/taking-off-with-ita.html"&gt;confirmed&lt;/a&gt; and some people are &lt;a href="http://news.ycombinator.com/item?id=1284587"&gt;talking about other hiring problems&lt;/a&gt;. So I'm doubly sure it's OK to post this now.&lt;/p&gt;&lt;p&gt;First, the problem specification:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;A palindrome is a sequence of words like &amp;quot;lid off a daffodil&amp;quot; or &amp;quot;shallot ayatollahs&amp;quot; that uses the same letters reading backwards as forwards. The words need not form a meaningful or grammatical sentence. A palindromic pangram is a multi-word palindrome that includes all 26 letters of the alphabet. Find the shortest sequence of words that is both a pangram and a palindrome.&lt;/blockquote&gt;&lt;p&gt;That sounded like something that could occupy me for a few hours. Of course ITA would have chosen the dictionary so that brute-force approach wouldn't be fast enough, but with a reasonable choice of data structures and algorithms, it shouldn't be too hard. Writing up my solution would require lots of care; I imagined it would be carefully code reviewed to see if I was worth talking to, in which case I'd spend most of the interview defending my solution and talking about how it could be improved. Maybe several days to edit for clarity and efficiency and so on. I wound up spending weeks trying with some success to make it run faster. Besides learning some python, some humility and having an excuse to actually do things I'd previously only read about, I learned this lesson: &lt;a href="http://news.ycombinator.com/item?id=1285359"&gt;the point of solving a puzzle is to GET A PHONE SCREEN. That's all&lt;/a&gt;. It was fun, but I won't repeat the mistake of undertaking substantial work without feedback.&lt;/p&gt;&lt;p&gt;If you want to play along at home, pause reading here and resume when you're happy with your own solution to compare notes.&lt;/p&gt;&lt;p&gt;Enough introduction. Let me show you some code along with commentary based on my notes. I started with a little brainstorming. I'll give you the main points first and then give a more thorough discussion later.&lt;/p&gt;&lt;p&gt;A quick note on optimization terminology: an optimization problem is normally posed with a set of constraints and an objective function. For example, the constraints for this problem specify that we are only interested in pangrams, and we are only interested in palindromes. We call solutions that satisfy the constraints &lt;dfn&gt;feasible.&lt;/dfn&gt; The &lt;dfn&gt;objective function&lt;/dfn&gt; grades the goodness of feasible solutions. For example, in this case our objective function is phrase length. An &lt;dfn&gt;optimal solution&lt;/dfn&gt; is a feasible solution for which no other feasible solution is scored better by the objective function.&lt;/p&gt;&lt;p&gt;First observation: For each letter, some word in our pangram must contain that letter.&lt;/p&gt;&lt;p&gt;First idea: &lt;a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.71.4147"&gt;beam stack search&lt;/a&gt; word-sets.&lt;/p&gt;&lt;p&gt;Elaboration: Here I'm considering pangram formation as a search problem in the space of words organized by letter inclusion. Each dictionary word is a child of the empty root node. Each node has the complement of dictionary words represented by its (improper) ancestors as children. Assuming a small constant bound c on word length, classifying words will cost |words| * 26 * c = O(n). Among sibling nodes, I can prioritize words containing the rarest letter in order to avoid unfruitful branches. Even so, the six-figure branching factor gives a very big search space.&lt;/p&gt;&lt;p&gt;Second observation: For each prefix p of the letter-sequence of the pangram, the reverse of p must exist in some concatenation of words.&lt;/p&gt;&lt;p&gt;Second idea: use a trie for reversed words to help prune the search for palindromes. The overall search tree has min(n, 26^c)! nodes, so let's only build tries for feasible word-sets.&lt;/p&gt;&lt;p&gt;We need a formal definition of our goal:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def palindromeP(seq):&lt;br /&gt;   """ Characteristic fn for palindromes.&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; palindromeP(['a'])&lt;br /&gt;   True&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; palindromeP(['a', 'b', 'a'])&lt;br /&gt;   True&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; palindromeP(['ab', 'c', 'ba'])&lt;br /&gt;   True&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; palindromeP(['ab', 'c', 'ab'])&lt;br /&gt;   False&lt;br /&gt;   """&lt;br /&gt;   return (lambda p: p[:int(len(p)/2)] == (p[int(math.ceil(len(p)/2.0)):])[::-1])(''.join(seq))&lt;br /&gt;&lt;/code&gt;&lt;p&gt;For those who don't use python, the triple-quotes delimit a &lt;a href="http://www.python.org/dev/peps/pep-0257/"&gt;docstring&lt;/a&gt; comment, and what looks like a toplevel transcript is a &lt;a href="http://docs.python.org/library/doctest.html"&gt;doctest&lt;/a&gt; specification showing inputs and expected outputs. The code just says that we are concerned with the concatenation of the given sequence: when the first half matches the reverse of the second half, we have a palindrome.&lt;/p&gt;&lt;p&gt;The definition of pangram depends on the letters that comprise our candidate solution:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class Histogram:&lt;br /&gt;   def __init__(self, seq):&lt;br /&gt;       self.h = collections.defaultdict(set)&lt;br /&gt;       for s in seq:&lt;br /&gt;           for i in s:&lt;br /&gt;               k = i.lower()&lt;br /&gt;               self.h[k].add(s)&lt;br /&gt;   def __len__(self):&lt;br /&gt;       return len(self.h)&lt;br /&gt;   def __getitem__(self, key):&lt;br /&gt;       return self.h[key]&lt;br /&gt;   def __iter__(self):&lt;br /&gt;       if (not hasattr(self, 'ordered_h')):&lt;br /&gt;           self.ordered_h = sorted(self.h.items(), key = lambda xy: -len(xy[1]))&lt;br /&gt;       return self.ordered_h.__iter__()&lt;br /&gt;   def top(self):&lt;br /&gt;       for x in self.__iter__():&lt;br /&gt;           return x[1]&lt;br /&gt;   def __repr__(self):&lt;br /&gt;       return '(Histogram %s)' % self.h.__repr__()&lt;br /&gt;&lt;/code&gt;&lt;p&gt;The histogram is a glorified dictionary mapping letters to words. It participates in the python idiomatic iteration scheme by partially ordering its values by overall letter frequency.&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def pangramP(seq):&lt;br /&gt;   """&lt;br /&gt;   Characteristic fn for pangrams.&lt;br /&gt;   (assumes the alphabet is limited to A-Za-z)&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; pangramP([''])&lt;br /&gt;   False&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; pangramP(['a'])&lt;br /&gt;   False&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; pangramP([string.ascii_lowercase])&lt;br /&gt;   True&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; pangramP(dict((x, x) for x in string.ascii_lowercase))&lt;br /&gt;   True&lt;br /&gt;   """&lt;br /&gt;   if isinstance(seq, collections.KeysView):&lt;br /&gt;       h = seq&lt;br /&gt;   elif isinstance(seq, str):&lt;br /&gt;       h = set(seq)&lt;br /&gt;   elif isinstance(seq, collections.Mapping):&lt;br /&gt;       h = seq.keys()&lt;br /&gt;   elif isinstance(seq, Histogram):&lt;br /&gt;       h = seq&lt;br /&gt;   else:&lt;br /&gt;       h = Histogram(seq)&lt;br /&gt;   return len(h) == 26&lt;br /&gt;&lt;/code&gt;&lt;p&gt;Now, let's look at the search implementation. I've characterized the problem in terms of tree search, so I need a representation for nodes:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class WordSetNode:&lt;br /&gt;   def __init__(self, word, parent = None):&lt;br /&gt;       if not hasattr(word, '__iter__'):&lt;br /&gt;           word = str(word)&lt;br /&gt;       self.word = word&lt;br /&gt;       self.parent = parent&lt;br /&gt;       self.covered = dict.fromkeys(word)&lt;br /&gt;       if parent:&lt;br /&gt;           self.covered.update(self.parent.covered)&lt;br /&gt;   def coveredP(self, letter):&lt;br /&gt;       return letter in self.covered&lt;br /&gt;   def pangramP(self):&lt;br /&gt;       return pangramP(self.covered.keys())&lt;br /&gt;   def ancestors(self):&lt;br /&gt;       n = self&lt;br /&gt;       while (n):&lt;br /&gt;           yield n&lt;br /&gt;           n = n.parent&lt;br /&gt;   def phrase(self):&lt;br /&gt;       return reversed([n.word for n in self.ancestors() if n.word])&lt;br /&gt;   def phrase_string_ns(self):&lt;br /&gt;       if not hasattr(self, 'phrase_string_ns_cache'):&lt;br /&gt;           self.phrase_string_ns_cache = ''.join(self.phrase())&lt;br /&gt;       return self.phrase_string_ns_cache&lt;br /&gt;   def __repr__(self):&lt;br /&gt;       if not self.parent:&lt;br /&gt;           return "WordSetNode('%s')" % self.word&lt;br /&gt;       return "WordSetNode('%s', %s)" % (self.word, repr(self.parent))&lt;br /&gt;&lt;/code&gt;&lt;p&gt;The first thing you might notice is that I have backlinks from children to parents, and a very odd thing you'll notice is that I have no edges from parents to children! I'm not planning to build a tree and then search it for solutions. I want to build my tree on-demand in order to avoid needless computation and limit my memory footprint. Basically, I move from parents to children by generating nodes rather than by following a path through some pre-existing structure. The backlinks are there for convenience.&lt;/p&gt;&lt;p&gt;As in the sketch above, the tree search is a quest for pangrams having the palindrome property, so nodes are viewed as contributing letter-coverage and paths through the tree represent words in the overall pangram. I cache information about the path to each node along the way.&lt;/p&gt;&lt;p&gt;So, about those pangrams: my idea for pruning infeasible word-sequences involved prefix trees (&amp;quot;tries&amp;quot;). This is the first time I've used them in real life, so I will remind you that prefix trees are all about paths; the element data aren't associated with particular nodes but rather with paths from the root. It's very efficient for queries of the form: which elements have x as a prefix? In this case, my trie nodes will represent letters; the trie itself represents a collection of letter-sequences:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class Trie:&lt;br /&gt;   def __init__(self, seq = []):&lt;br /&gt;       if type(seq) == type(''):&lt;br /&gt;           seq = [seq]&lt;br /&gt;       self.terminus = False&lt;br /&gt;       self.children = {}&lt;br /&gt;       for i in seq:&lt;br /&gt;           self.add(i)&lt;br /&gt;   def emptyP(self):&lt;br /&gt;       return not self.children&lt;br /&gt;   def match(self, word):&lt;br /&gt;       """Find the deepest subtrie representing&lt;br /&gt;       a prefix of word, and the remaining suffix.&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie().match('')&lt;br /&gt;       (Trie(), '')&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie().match('abc')&lt;br /&gt;       (Trie(), 'abc')&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie('abc').match('')&lt;br /&gt;       (Trie(['abc']), '')&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie('abc').match('a')&lt;br /&gt;       (Trie(['bc']), '')&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie('abc').match('aac')&lt;br /&gt;       (Trie(['bc']), 'ac')&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie(['abc', 'aac']).match('achtung')&lt;br /&gt;       (Trie(['ac', 'bc']), 'chtung')&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie(['abc', 'aac']).match('aardvark')&lt;br /&gt;       (Trie(['c']), 'rdvark')&lt;br /&gt;       """&lt;br /&gt;&lt;br /&gt;       if not word:&lt;br /&gt;           return (self, word)&lt;br /&gt;       if not word[0] in self.children:&lt;br /&gt;           return (self, word)&lt;br /&gt;       return self.children[word[0]].match(word[1:])&lt;br /&gt;   def add(self, word):&lt;br /&gt;       if not word:&lt;br /&gt;           self.terminus = True&lt;br /&gt;           return&lt;br /&gt;       subtree, remainder = self.match(word)&lt;br /&gt;       if not remainder:&lt;br /&gt;           # mark the word if it's not already marked&lt;br /&gt;           return subtree.add('')&lt;br /&gt;       subtree.children[remainder[0]] = Trie([remainder[1:]])&lt;br /&gt;   def search(self, w):&lt;br /&gt;       quarry, remnant = self.match(w)&lt;br /&gt;       if remnant:&lt;br /&gt;           return None&lt;br /&gt;       return quarry&lt;br /&gt;   def traverse(self, preorderVisit, postorderVisit, prefix = ''):&lt;br /&gt;       preorderVisit(self, prefix)&lt;br /&gt;       for c in self.children.items():&lt;br /&gt;           c[1].traverse(preorderVisit, postorderVisit, prefix + c[0])&lt;br /&gt;       postorderVisit(self, prefix)&lt;br /&gt;   def pre_order_nodes(self, ancestry = None):&lt;br /&gt;       if ancestry is None:&lt;br /&gt;           ancestry = []&lt;br /&gt;       yield (self)&lt;br /&gt;       for c in self.children.items():&lt;br /&gt;           for i in c[1].pre_order_nodes():&lt;br /&gt;               yield i&lt;br /&gt;   def words(self, prefix = ''):&lt;br /&gt;       if self.terminus:&lt;br /&gt;           yield prefix&lt;br /&gt;       for c in self.children.items():&lt;br /&gt;           for l in c[1].words(prefix + c[0]):&lt;br /&gt;               yield l&lt;br /&gt;   def __repr__(self):&lt;br /&gt;       W = ', '.join(repr(w) for w in sorted(self.words()))&lt;br /&gt;       if W:&lt;br /&gt;           return "Trie([%s])" % W&lt;br /&gt;       else:&lt;br /&gt;           return 'Trie()'&lt;br /&gt;&lt;/code&gt;&lt;div class="fig" style="float: right; width: 50%; height: 326pt;"&gt;&lt;br /&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/trie.svg" type="image/svg+xml" height="100%" width="100%"&gt;&lt;br /&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/trie.png?attredirects=0" alt="trie: {a, daffodil, lid, lug, off, offset}" /&gt;&lt;br /&gt;&lt;/object&gt;&lt;br /&gt;&lt;p&gt;a trie representing {a, daffodil, lid, lug, off, offset}&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;As I explore my overall search tree, building up a sequence of words, I want to ensure that that my sequence has palindromic symmetry. I can do this by querying a trie-filled-with-reverse-words for my current search tree path.&lt;/p&gt;&lt;p&gt;The basic idea seems OK, but I haven't addressed word segmentation within the pangram. We disregard whitespace in the problem definition, so we can't limit our search to pangrams with word-length symmetry. We've got to account for "massive … sam" wherein we could reach a terminal node of the prefix trie of reversed words before exhausting letters in the current search path.&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def loopyTrieSearch(t):&lt;br /&gt;   """Produce a function that searches&lt;br /&gt;   a trie augmented with backedges from&lt;br /&gt;   each leaf to the root.&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; t = Trie()&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s = loopyTrieSearch(t)&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, 'unmatchable')&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; t = Trie(['abc', 'aac', 'b'])&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s = loopyTrieSearch(t)&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, '')&lt;br /&gt;   [Trie(['aac', 'abc', 'b'])]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, 'a')&lt;br /&gt;   [Trie(['ac', 'bc'])]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, 'ba')&lt;br /&gt;   [Trie(['ac', 'bc'])]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(functools.reduce(Trie.merge, s(t, 'b')), 'ab')&lt;br /&gt;   [Trie(['c'])]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, 'az')&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; t = Trie(['ab', 'abc', 'd'])&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s = loopyTrieSearch(t)&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, 'abd')&lt;br /&gt;   [Trie([''])]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; s(t, 'azzz')&lt;br /&gt;   """&lt;br /&gt;   def s(t2, w):&lt;br /&gt;       ctx = t2 if isinstance(t2, collections.MutableSequence) else [t2]&lt;br /&gt;       if w == '':&lt;br /&gt;           return ctx&lt;br /&gt;       for l in w:&lt;br /&gt;           for i in ctx:&lt;br /&gt;               if i.terminus:&lt;br /&gt;                   ctx.append(t)&lt;br /&gt;                   break&lt;br /&gt;           ctx = [j[0] for j in (i.match(l) for i in ctx) if j[1] == '']&lt;br /&gt;       if not ctx:&lt;br /&gt;           return None&lt;br /&gt;       return ctx&lt;br /&gt;   return s&lt;br /&gt;&lt;/code&gt;&lt;p&gt;From these ideas, I built a program that ran very nicely on a contrived, tiny, dictionary, but when I ran it on the 200kword ITA dictionary, my cpu fan revved to 100% and stayed there indefinitely. I should show some profiling data here.&lt;/p&gt;&lt;p&gt;How could I build my search tree more cheaply? Initially, to generate a node's children, I would take my letter-coverage dictionary's complement to see what letters I needed. I'd find all the words that could contribute a needed letter. For each word, I'd use my loopyTrieSearch. This tactic does avoid exploring infeasible branches, but it involves a lot of unsuccessful trie queries. What if I started by loopyTrieSearching the prospective parent's path, and then considering the loopy suffixes as possible words containing a needed letter?&lt;/p&gt;&lt;p&gt;Consider a trie pair (A, B) with A containing "awk" and "awkward" and B containing only "awesome." The naive approach requires us to conduct separate trie searches for "awk" and "awkward," both times stopping when we fail to match the ks. The improved approach allows us to simultaneously eliminate both words as soon as we find that "aw" has no child "k." If the average branching factor in the trie over all words (26 at most) is less than the average number of words associated with each letter (200000/26), then the latter could be a superior tactic. This is the purpose of the Trie.filter method:&lt;/p&gt;&lt;p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class Trie:&lt;br /&gt;# ...&lt;br /&gt;   def filter(self, criteria, root = None):&lt;br /&gt;       """Return the words in this trie that&lt;br /&gt;       match the augmentation of criteria by&lt;br /&gt;       the addition of backedges from terminals&lt;br /&gt;       to the root.&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; sorted(w for w in d if s(T, w)) == [w for w, i in itertools.groupby(sorted(FT.filter(T)))]&lt;br /&gt;       True&lt;br /&gt;       """&lt;br /&gt;       def f(words, crit, prefix = ''):&lt;br /&gt;           if words.terminus:&lt;br /&gt;               yield prefix&lt;br /&gt;           if words.emptyP():&lt;br /&gt;               return&lt;br /&gt;           backedge = root.children.items() if crit.terminus else ()&lt;br /&gt;           for c in itertools.chain(crit.children.items(), backedge):&lt;br /&gt;               m = words.match(c[0])&lt;br /&gt;               if m[1] != '': continue&lt;br /&gt;               for w in f(m[0], c[1], prefix + c[0]):&lt;br /&gt;                   yield w&lt;br /&gt;       if not root: root = criteria&lt;br /&gt;       for i in f(self, criteria):&lt;br /&gt;           yield i&lt;br /&gt;   @staticmethod&lt;br /&gt;   def merge(a, b):&lt;br /&gt;       """Merge two tries.&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie.merge(Trie(['a']), Trie(['b']))&lt;br /&gt;       Trie(['a', 'b'])&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie.merge(Trie(['abc']), Trie(['bcd']))&lt;br /&gt;       Trie(['abc', 'bcd'])&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; Trie.merge(Trie(['abc', 'aac']), Trie(['abe']))&lt;br /&gt;       Trie(['aac', 'abc', 'abe'])&lt;br /&gt;       """&lt;br /&gt;       result = Trie()&lt;br /&gt;       result.children = a.children.copy()&lt;br /&gt;       for k, v in b.children.items():&lt;br /&gt;           if k in result.children:&lt;br /&gt;               result.children[k] = Trie.merge(v, result.children[k])&lt;br /&gt;           else:&lt;br /&gt;               result.children[k] = v&lt;br /&gt;       result.terminus = a.terminus or b.terminus&lt;br /&gt;       return result&lt;br /&gt;&lt;/code&gt;&lt;p&gt;For this, I'll use a pair of tries, one reverse and one forward. My search path corresponds to paths in each of those tries; as I query in the trie pair I need never pursue a branch that's available in only one of the tries.&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class TriePair:&lt;br /&gt;   def __init__(self, W, Wr = None):&lt;br /&gt;       if not Wr: Wr = W&lt;br /&gt;       self.fwd = W if isinstance(W, Trie) else Trie(W)&lt;br /&gt;       self.rev = Wr if isinstance(Wr, Trie) else Trie(w[::-1] for w in Wr)&lt;br /&gt;       self.s = loopyTrieSearch(self.rev)&lt;br /&gt;   def expand(self, node, advisoryBudget = None, peek = True):&lt;br /&gt;       self.setupContinuation(node)&lt;br /&gt;       if not node.trieContinuation: return&lt;br /&gt;       if peek and node.pangramP() and not node.parent.pangramP():&lt;br /&gt;           # don't peek in the recursive expansion&lt;br /&gt;           for i in self.completePalindrome(node, expand = functools.partial(self.expand, peek = False)):&lt;br /&gt;               yield i&lt;br /&gt;       W = itertools.chain(*(self.fwd.filter(c, self.rev) for c in node.trieContinuation))&lt;br /&gt;       awords = set(a.word for a in node.ancestors())&lt;br /&gt;       for w in set(W):&lt;br /&gt;           if w in awords: continue&lt;br /&gt;           n = WordSetNode(w, node)&lt;br /&gt;           # if pangram and \exists a word matching some trie&lt;br /&gt;           #  path to a leaf -&amp;gt; generate the easy palindrome&lt;br /&gt;           n.setupContinuation = self.setupContinuation&lt;br /&gt;           yield n&lt;br /&gt;&lt;/code&gt;&lt;p&gt;I mentioned earlier that the search tree is constructed during the search process. The expand function does that work, producing the child nodes of each parent. I'll explain peeking later. It would be possible to query our search tries for the string corresponding to our path whenever we expand a node, but then each descendent must redo its ancestors' trie search work. So, we copy the parent's search context into a cache (trieContinuation) on each child in order to avoid that extra work:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class TriePair:&lt;br /&gt;# ...&lt;br /&gt;   def setupContinuation(self, n):&lt;br /&gt;       if hasattr(n, 'trieContinuation'):&lt;br /&gt;           return&lt;br /&gt;       elif self == n and hasattr(n, 'setupContinuation'):&lt;br /&gt;           n.setupContinuation(n)&lt;br /&gt;       elif n.parent and hasattr(n.parent, 'trieContinuation'):&lt;br /&gt;           n.trieContinuation = (&lt;br /&gt;               self.s(n.parent.trieContinuation,&lt;br /&gt;                      n.word))&lt;br /&gt;       else:&lt;br /&gt;           n.trieContinuation = (&lt;br /&gt;               self.s(self.rev,&lt;br /&gt;                      n.phrase_string_ns()))&lt;br /&gt;&lt;/code&gt;&lt;p&gt;Let's take a closer look at beam-stack search.&lt;/p&gt;&lt;p&gt;Recall that &lt;a href="http://xkcd.com/761/"&gt;depth-first search always expands the deepest unexplored node&lt;/a&gt;, while breadth-first search always expands the shallowest unexplored node. Depth-first search uses memory proportional to the longest path to a leaf; breadth-first search uses memory proportional to the size of the largest layer, O(d^b) where d is the longest path to a leaf and b is the branching factor at each node. For many problems, the better of two feasible solutions is the one closer to the root, and for these problems breadth-first search finds good solutions faster than depth-first search (which can pay an infinite penalty for unlucky early expansions).&lt;/p&gt;&lt;p&gt;Best-first search algorithms take an intermediate course between depth-first and breadth-first search. They choose which node to expand next by applying a heuristic. A* is a well-known such algorithm, and it's the basis for beam-stack search. The exploration order (denoted f(n) = g(n) + h(n)) takes into account both the (known) cost of each node's path from the root (g(n)) and the (estimated) cost of its path to a solution (h(n)). The estimation must be conservative in the sense that it never overestimates a potential solution's cost; this allows admissible pruning: once we've found a solution, we can safely avoid expanding any subtrees with higher cost estimates.&lt;/p&gt;&lt;p&gt;Best-first search can degenerate into breadth-first search in the worst case for memory use; beam-stack search addresses real-world memory constraints by enabling inadmissible pruning without compromising completeness (we will find a solution if one exists) or optimality (no solution is better than the one we find). Beam-stack search does this by keeping metadata proportional to the depth of the search that allows it to backtrack, regenerating and thoroughly exploring inadmissibly pruned nodes after it has explored the more promising portion of the graph.&lt;/p&gt;&lt;p&gt;Note that beam-stack search is an anytime algorithm: it strives to return a feasible solution ASAP and then to return improved solutions as they discovered. It is also complete and optimal, so it will definitely return a solution if any exists and the final solution it returns is guaranteed to be at least as good as any that exist.&lt;/p&gt;&lt;div&gt;The following graphs illustrate the progress of a beam-stack search on a small graph with a simple cost function and heuristic.&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-0.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-0.png?attredirects=0" alt="root node with bounds" /&gt;&lt;/object&gt;Bounds on the f-cost of child nodes are associated with each layer. Initially, we consider all possible children. Whenever we generate a child, we check for solution feasibility.&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-1.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-1.png?attredirects=0" alt="first layer expansion" /&gt;&lt;/object&gt;&lt;p&gt;For each node, we can compute the (certain) g-cost of the partial solution and the (guessed) h-cost of the potential solution descendent of the node. In this example, we assume that g is phrase length (not including spaces) and h is the number of letters we need to add to produce a pangram. These are sensible, if naive, assumptions for the pangramic palindrome problem. But, to keep this example small, we will cheat by using an easier feasibility predicate: we will accept any palindrome; pangrams are not required.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-2.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-2.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;When memory is limited, we inadmissibly discard some children, updating the parent layer upper bound accordingly. If backtracking proves necessary, we can use the upper bound to regenerate only the inadmissibly discarded children, thus avoiding duplicated work. Assuming a beam width of 2 nodes, we partition the set of children by f-cost. In this case, a and lid survive while off and daffodil are pruned.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-3.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-3.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;We are ready to expand the frontier.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-4.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-4.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;The layer exceeds our memory threshold, so it's time to prune again.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-5.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-5.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;We retain the most promising nodes and update the layer bound.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-6.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-6.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;We are ready to expand the frontier.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-7.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-7.png?attredirects=0" alt="increased memory bound" /&gt;&lt;/object&gt;&lt;p&gt;Our frontier exceeds our memory limit, but I think I've made the point about pruning so let's double the limit to 4. OK then we can expand again.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-8.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-8.png?attredirects=0" alt="no further expansion is possible without backtracking" /&gt;&lt;/object&gt;&lt;p&gt;If the dictionary contains only the words {lid, off, a, daffodil} then we are unable to expand beyond this point without duplicating words. Notice that we have yet to find any palindromes. Because we cannot go forward, we must go backtrack.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-9.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-9.png?attredirects=0" alt="backtrack" /&gt;&lt;/object&gt;&lt;p&gt;We backtrack to the deepest layer at which inadmissible pruning occurred (i.e., where the upper bound is finite). There, we update the bounds to show that we have fully explored the space below the upper bound; now we will consider the space above. We are ready to expand the frontier.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-10.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-10.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;Inadmissible pruning works just the same after backtracking as before. To see this, let's tighten the memory limit back to two.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-11.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-11.png?attredirects=0" alt="pruning" /&gt;&lt;/object&gt;&lt;p&gt;As ever, we remove the nodes with high f-cost.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-12.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-12.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;We are ready to expand the frontier.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-13.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-13.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;This layer exceeds our memory limit, so we need to prune.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-14.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-14.png?attredirects=0" alt="pruning" /&gt;&lt;/object&gt;&lt;p&gt;We discard unpromising nodes.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-15.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-15.png?attredirects=0" alt="search progress" /&gt;&lt;/object&gt;&lt;p&gt;We are ready to expand the frontier.&lt;/p&gt;&lt;/div&gt;&lt;div class="fig"&gt;&lt;object data="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-16.svg" type="image/svg+xml"&gt;&lt;img src="http://sites.google.com/site/seanfoyblogspot/cab/beam-stack-prune-16.png?attredirects=0" alt="first candidate solution found" /&gt;&lt;/object&gt;&lt;p&gt;We find a feasible solution on the frontier: lidoffadaffodil. This is a proof by example of the maximum cost of the optimal solution to our problem. Our heuristic is admissible, meaning that it never underestimates the cost of the best solution that uses a given node. Therefore, from now on, we can admissibly prune nodes whose f-cost exceed the f-cost of our candidate solution. The memory limit might still cause us to do some inadmissible prunings in the future, and in that case we might need to backtrack. But, admissible prunings never create an obligation to backtrack. If we continue from here, we will eventually exhaust the space between 27 (where we inadmissibly pruned some nodes) and 35 (where we have a solution) and determine that in fact we have an optimal solution.&lt;/p&gt;&lt;/div&gt;&lt;div style="clear: both;"&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;So here are the parts to our cost function f:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def g(n):&lt;br /&gt;   # sort of like&lt;br /&gt;   #   lambda n: sum(len(w.word) for w in n.ancestors())&lt;br /&gt;   # but with caching&lt;br /&gt;   if not hasattr(n, 'g'):&lt;br /&gt;       result = len(n.word)&lt;br /&gt;       if n.parent:&lt;br /&gt;           result += g(n.parent)&lt;br /&gt;       n.g = result&lt;br /&gt;   return n.g&lt;br /&gt;def h(n):&lt;br /&gt;   """Admissible heuristic estimate of cost&lt;br /&gt;   remaining from this node.&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; h(WordSetNode('a')) == h(WordSetNode('aa'))&lt;br /&gt;   True&lt;br /&gt;&lt;br /&gt;   Though admissible, it's reasonable tight.&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; minimalCost = 26 * 2 - 1&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; inadmissibleMargin = 1&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; n = WordSetNode('a')&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; h(n) &amp;gt; minimalCost - inadmissibleMargin - len(''.join(n.phrase()))&lt;br /&gt;   True&lt;br /&gt;   """&lt;br /&gt;   phrase_width = g&lt;br /&gt;   tie_breaker = .01 * len(n.word) + .01 * abs(hash(n.phrase_string_ns()))/float(sys.maxsize)&lt;br /&gt;   covered = len(n.covered)&lt;br /&gt;   completely = len(string.ascii_lowercase)&lt;br /&gt;   # The symmetry of the palindrome guarantees&lt;br /&gt;   # that the first half is a pangram. So,&lt;br /&gt;   # there's no use evaluating palindrome-dness&lt;br /&gt;   # until we have a pangram.&lt;br /&gt;   if covered &amp;lt; completely:&lt;br /&gt;       # descendent nodes must:&lt;br /&gt;       #   cover the missing letters on the left&lt;br /&gt;       #     (just to the right of n)&lt;br /&gt;       #   match the missing letters on the right&lt;br /&gt;       #     (with a possible exception at the pivot)&lt;br /&gt;       #   match what we have so far&lt;br /&gt;       # TODO: improve this estimate by:&lt;br /&gt;       #   (a) looking ahead in the trie for the missing&lt;br /&gt;       #       letters&lt;br /&gt;       #   (b) using the max_missing(min(len(word) | missing \in word))&lt;br /&gt;       #       rather than 1 for the cost component due to&lt;br /&gt;       #       missing letters&lt;br /&gt;       missing = completely - covered&lt;br /&gt;       # strategy b:&lt;br /&gt;       # this doesn't help: when letterCosts&lt;br /&gt;       # wins the max, we lose our ability to&lt;br /&gt;       # distinguish between nodes that help&lt;br /&gt;       # us gain coverage and nodes that do not.&lt;br /&gt;       #needed = max(missing + (missing - 1), max(letterCosts[i] for i in string.ascii_lowercase if not i in n.covered))&lt;br /&gt;       needed = missing + (missing - 1)&lt;br /&gt;       return phrase_width(n) + needed - tie_breaker&lt;br /&gt;   else:&lt;br /&gt;       return palindrome_balance(n) - tie_breaker&lt;br /&gt;&lt;/code&gt;&lt;p&gt;And here's how those functions are used, along with parameters for tuning memory use (beam_width), the upper bound on cost for solutions we're willing to accept (U), a predicate to control which intermediate solutions we yield, and a way to inject a stack (which is handy for resuming cancelled searches):&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def bss(nodes, expand, g, h, beam_width, U = sys.maxsize, feasibleP = lambda n: False, stack = None):&lt;br /&gt;   """Beam-stack search, a variation on&lt;br /&gt;   breadth-first branch and bound with&lt;br /&gt;   backtracking.&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; [i for i in bss([], None, lambda n: 1, lambda n: 1, 10)]&lt;br /&gt;   []&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; [i for i in bss([1], lambda n: [], lambda n: 1, lambda n: 1, 10)]&lt;br /&gt;   [1]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; sorted([i for i in bss(range(15), lambda n: [], lambda n: 1, lambda n: 1, 10)])&lt;br /&gt;   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; sorted([i for i in bss(range(5), intexpand, lambda n: 1, lambda n: 0, 3)])&lt;br /&gt;   [1, 2]&lt;br /&gt;&lt;br /&gt;   """&lt;br /&gt;   def dedup(frontier):&lt;br /&gt;       # This is a warning rather than an error:&lt;br /&gt;       # some nodes really are equivalent though&lt;br /&gt;       # not identical. For example, spaces don't&lt;br /&gt;       # matter in pangramic palindromes.&lt;br /&gt;       frontier = sorted(frontier, key = lambda p: p[0])&lt;br /&gt;       groupedFrontier = (&lt;br /&gt;           itertools.groupby(&lt;br /&gt;               sorted(frontier,&lt;br /&gt;                      key = lambda p: p[0]),&lt;br /&gt;               lambda p: p[0]))&lt;br /&gt;       frontier = []&lt;br /&gt;       for i in groupedFrontier:&lt;br /&gt;           elts = list(itertools.islice(i[1], 2))&lt;br /&gt;           if (len(elts) &amp;lt;= 1):&lt;br /&gt;               frontier.extend(elts)&lt;br /&gt;           else:&lt;br /&gt;               frontier.append(elts[0])&lt;br /&gt;               print('f needs nuance: these nodes have the same f-cost: %d = f(%s) = f(%s)' % (elts[0][0], ' '.join(elts[0][1].phrase()), ' '.join(elts[1][1].phrase())))&lt;br /&gt;       return sorted(frontier, key = lambda p: p[0])&lt;br /&gt;   def prune(frontier, beam_width):&lt;br /&gt;       if len(frontier) &amp;lt;= beam_width:&lt;br /&gt;           f_max = frontier[-1][0]&lt;br /&gt;           print('pruned nodes, new f_max = %s' % f_max)&lt;br /&gt;           return frontier, f_max&lt;br /&gt;       # We'll never backtrack to the nodes&lt;br /&gt;       # we're pruning, even if these nodes&lt;br /&gt;       # are never admissibly discarded.&lt;br /&gt;       # beam_width may be smaller than the largest&lt;br /&gt;       # equivalence class defined by f.&lt;br /&gt;       # Ideally, these classes should contain&lt;br /&gt;       # one node each; otherwise it's difficult&lt;br /&gt;       # to choose a beam width that will avoid&lt;br /&gt;       # splitting an equivalence class.&lt;br /&gt;       s = dedup(frontier)&lt;br /&gt;       retained, pruned = s[:beam_width], s[beam_width:]&lt;br /&gt;       return prune(retained, beam_width)&lt;br /&gt;&lt;br /&gt;   # 63: verbify ma pluck now sh ad go jo zax eta qat ex azo jog dahs wonk culpa my fib rev&lt;br /&gt;   # 65: buckrams donzel ti pyx of vug we jo hm suq us mho jew guv foxy pit lez nods mark cub&lt;br /&gt;   # 65: verbify na ducks to hm we jo zax al pig suq us gip lax azo jew mho tsk cud any fib rev&lt;br /&gt;   # 67: bonked vulgars ti hm oy cap wo jo zax if suq us fix azo jow pac yom hits rag luv de knob&lt;br /&gt;   # 69: verbify locks twang up mho zax ed ma jab suq us ba jam dex azo hm pug naw tsk coly fib rev&lt;br /&gt;   # 71: verbify lock nut spaz we jig am dex eh ta qua sau qat hexed magi jew zap stunk coly fib rev&lt;br /&gt;   # 76: drawknife butyls go hm op ox azo jo we vac suq us suq us cave wo jo zax op om hog sly tube fink ward&lt;br /&gt;   # 86: de but way xi sh on vug la ef pa me re ka jo ah coz om ta qat ta qat mozo chao jake remap feal guv noh six yaw tubed&lt;br /&gt;   # 92: drawknife butyls go hm op ox azo jo we vac suq usherettes setter eh suq us cave wo jo zax opo mho g sly tube fink ward&lt;br /&gt;   # 101: underflow machs ti by xi pe ka jo zag eve suq aimu umiaq use vega zo jake pixy bits h cam wolfr ed nu&lt;br /&gt;&lt;br /&gt;   f = lambda n: g(n) + h(n)&lt;br /&gt;   if not stack:&lt;br /&gt;       oldFrontier = [(f(i), i) for i in nodes]&lt;br /&gt;       for i in filter(lambda x: feasibleP(x[1]), oldFrontier):&lt;br /&gt;           U = min(U, g(i[1]))&lt;br /&gt;           yield i[1]&lt;br /&gt;       oldFrontier = [i for i in oldFrontier if i[0] &amp;lt; U]&lt;br /&gt;       stack = [[0, U, oldFrontier]]&lt;br /&gt;   j = 0&lt;br /&gt;   while stack:&lt;br /&gt;       f_min, f_max, oldFrontier = stack[-1]&lt;br /&gt;       print('f_min, f_max, level = %s, %s, %d' % (f_min, f_max, len(stack)))&lt;br /&gt;       if oldFrontier: print('sample phrase: %s' % ' '.join(oldFrontier[0][1].phrase()))&lt;br /&gt;       frontier = []&lt;br /&gt;       for i, o in enumerate(oldFrontier):&lt;br /&gt;           visit(o[1])&lt;br /&gt;           j += 1&lt;br /&gt;           if not j % 32: print('nodes expanded: %d' % j)&lt;br /&gt;           k = 0&lt;br /&gt;           for n in expand(o[1], advisoryBudget = f_max):&lt;br /&gt;               k += 1&lt;br /&gt;#                if not k % 8196: print('progress on frontier: %d' % k)&lt;br /&gt;               if feasibleP(n):&lt;br /&gt;                   gn = g(n)&lt;br /&gt;                   if gn &amp;gt;= U: continue&lt;br /&gt;                   # g-values are integers, and f&lt;br /&gt;                   # is admissible, so all the f-values&lt;br /&gt;                   # above g - 1 have f-cost at least&lt;br /&gt;                   # as high as g (i.e., they're no&lt;br /&gt;                   # better than this solution).&lt;br /&gt;                   U = gn - 1 + (1.0 / sys.maxsize)&lt;br /&gt;                   f_max = U&lt;br /&gt;                   yield n&lt;br /&gt;                   oldFrontier[i + 1:] = filter(lambda p: p[0] &amp;lt; U, oldFrontier[i + 1:])&lt;br /&gt;                   continue&lt;br /&gt;               p = (f(n), n)&lt;br /&gt;               if f_min &amp;lt; p[0] &amp;lt; f_max:&lt;br /&gt;                   frontier.append(p)&lt;br /&gt;                   if len(frontier) &amp;gt; beam_width * 2:&lt;br /&gt;                       frontier, f_max = prune(frontier, beam_width)&lt;br /&gt;       # filter, sort, reduce equivalence classes to class representatives&lt;br /&gt;       prior_frontier = list(frontier)&lt;br /&gt;       frontier = filter(lambda p: p[0] &amp;lt;= f_max, frontier)&lt;br /&gt;       frontier = dedup(frontier)&lt;br /&gt;       if not frontier:&lt;br /&gt;           # backtrack&lt;br /&gt;           print('empty layer -&amp;gt; backtracking from %d to' % len(stack), end=' ')&lt;br /&gt;           while stack and stack[-1][1] &amp;gt;= U:&lt;br /&gt;               stack.pop()&lt;br /&gt;           print(len(stack), end=' ')&lt;br /&gt;           if stack:&lt;br /&gt;               frame = stack[-1]&lt;br /&gt;               frame[0], frame[1] = frame[1], U&lt;br /&gt;               frame[2] = list(filter(lambda n: n[0] &lt; end="'')&lt;br /&gt;&lt;/code&gt;&lt;p&gt;Now, a very obvious improvement can be had by noticing that we guarantee palindromes by construction. Once we attain the pangram property, it's entirely straightforward to complete the palindrome. I call that "peeking." Here's a way to take the short-cut at the appropriate moment during bss:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;class TriePair:&lt;br /&gt;   # ...&lt;br /&gt;   def pivotH(self, n):&lt;br /&gt;       revCompletions = self.s(self.rev, n.phrase_string_ns())&lt;br /&gt;       completionWords = itertools.chain(*(t.words() for t in revCompletions))&lt;br /&gt;       return min(len(w) for w in completionWords) - (32 - len(n.word)) / 32 - abs(hash(n.phrase_string_ns())) / sys.maxsize / 64&lt;br /&gt;   @staticmethod&lt;br /&gt;   def rightH(n, expected_length):&lt;br /&gt;       return expected_length - sum(len(i) for i in n.phrase()) - abs(hash(n.phrase_string_ns())) / sys.maxsize&lt;br /&gt;   def rightExpand(self, n, match, len_left):&lt;br /&gt;       """Restricted search space for quickly finding&lt;br /&gt;       pangramic palindrome completions.&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; left = 'drawknife butyls go hm op ox azo jo we vac suq us'.split()&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; right = 'suq us cave wo jo zax op om hog sly tube fink ward'.split()&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; extraneous = ['opossum']&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; palindrome = left + right&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; tries = TriePair(palindrome + extraneous)&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; all(tries.fwd.match(i)[1] == '' for i in palindrome)&lt;br /&gt;       True&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; n = functools.reduce(lambda a, i: WordSetNode(i, a), left, None)&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; match = ''.join(left)[::-1]&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; rx = functools.partial(tries.rightExpand, match)&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; x = list(rx(n))&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; [i.word for i in x]&lt;br /&gt;       ['suq']&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; n = functools.reduce(lambda a, i: WordSetNode(i, a), itertools.chain(left, itertools.takewhile(lambda p: p != 'op', right)), None)&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; m, r = tries.fwd.match('op')&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; r == '' and sum(1 for i in m.words()) &gt; 1&lt;br /&gt;       True&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; [i.word for i in list(rx(n))]&lt;br /&gt;       ['op']&lt;br /&gt;&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; n = functools.reduce(lambda a, i: WordSetNode(i, a), palindrome[:-1], None)&lt;br /&gt;       &amp;gt;&amp;gt;&amp;gt; palindrome[-1] in (i.word for i in list(rx(n)))&lt;br /&gt;       True&lt;br /&gt;       """&lt;br /&gt;       m = match[sum(len(i) for i in n.phrase()) - len_left:]&lt;br /&gt;       r = self.fwd.match(m)[1]&lt;br /&gt;       nextWordSupersequence = m[:-len(r)] if r else m&lt;br /&gt;       for i in self.fwd.filter(Trie([nextWordSupersequence]), Trie()):&lt;br /&gt;           yield WordSetNode(i, n)&lt;br /&gt;   def completePalindrome(self, pangram, expand, f_max = sys.maxsize, match = None, left = None):&lt;br /&gt;       # the pivot of the palindrome can be anywhere&lt;br /&gt;       # left of the last letter needed for the pangram.&lt;br /&gt;       # So it could be in the rightmost word of pangram,&lt;br /&gt;       # or it could be in words yet to be added to our&lt;br /&gt;       # phrase. We'll expand the pangram in the usual&lt;br /&gt;       # way elsewhere, so we don't need to be complete&lt;br /&gt;       # here. We do need to remain feasible, however.&lt;br /&gt;       # Our strategy here is to find a pivot that&lt;br /&gt;       # coincides with a word boundary, so that we can&lt;br /&gt;       # use the reverse of the phrase as the sequence&lt;br /&gt;       # of letters to appear on the right. This will&lt;br /&gt;       # give us a much faster way to complete a&lt;br /&gt;       # palindrome and establish a tighter upper bound&lt;br /&gt;       # for the rest of our search.&lt;br /&gt;       # TODO: also check for more interior pivots by&lt;br /&gt;       # extending keystoneP to enumerate all possible&lt;br /&gt;       # interior pivots rather than just the first one.&lt;br /&gt;       #  ababa -&amp;gt; 1, 2, 3&lt;br /&gt;       if match and left:&lt;br /&gt;           len_match = len(match)&lt;br /&gt;           len_left = len(left)&lt;br /&gt;           rx = functools.partial(self.rightExpand, match = match, len_left = l&lt;br /&gt;           rh = functools.partial(TriePair.rightH, expected_length = len_left + len_match)&lt;br /&gt;           print('building right with match = %s and left = %s' % (match, left))&lt;br /&gt;           for completion in bss([pangram], rx, g, rh, 1, len_left + len_match + 1/sys.maxsize, feasibleP):&lt;br /&gt;               yield completion&lt;br /&gt;           return&lt;br /&gt;       if keystoneP(pangram.word):&lt;br /&gt;           match = pangram.parent.phrase_string_ns() + pangram.word[:keystoneP(pangram.word)]&lt;br /&gt;           for i in self.completePalindrome(pangram, expand, f_max, match = match, left = pangram.phrase_string_ns()):&lt;br /&gt;               f_max = min(f_max, g(completion))&lt;br /&gt;               yield i&lt;br /&gt;       for left in bss([pangram], expand, g, self.pivotH, 1, f_max, lambda n: any(t.terminus for t in self.s(self.rev, n.phrase_string_ns()) + self.s(self.rev, n.phrase_string_ns()[:-1]))):&lt;br /&gt;           match = left.phrase_string_ns()[::-1]&lt;br /&gt;           for m in (match[1:], match):&lt;br /&gt;               for completion in self.completePalindrome(left, expand, f_max, match = m, left = match):&lt;br /&gt;                   yield completion&lt;br /&gt;&lt;/code&gt;&lt;p&gt;In essence, it comes down to switching to very well-informed heuristics that can exploit our foreknowledge of the letter sequence we want in the second half of the phrase.&lt;/p&gt;&lt;p&gt;This still tied up my laptop for many hours. I had two ideas for making progress: (1) reduce the dictionary in hopes of lowering the upper bound for the search and (2) experiment with different heuristics in hopes of dynamically finding better solutions more quickly. As an example of (1), I tried excluding words with multiple occurrences of any letter. As an example of (2), I tried using a piecewise heuristic function that penalizes letter repetition early in the search, but also recognizes that the pangram may contain a "keystone" word overlapping its palindromic midpoint.&lt;/p&gt;&lt;p&gt;Here's how I disallowed intraword letter duplication:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def minKeystoneP(w):&lt;br /&gt;   """Determine whether this word's interior&lt;br /&gt;   contains the palindromic pivot of a theoretically&lt;br /&gt;   minimal-sized pangramic palindrome.&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; minKeystoneP('abc')&lt;br /&gt;   False&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; minKeystoneP('aba')&lt;br /&gt;   True&lt;br /&gt;&lt;br /&gt;   &amp;gt;&amp;gt;&amp;gt; minKeystoneP('abaa')&lt;br /&gt;   False&lt;br /&gt;&lt;br /&gt;   """&lt;br /&gt;   k = keystoneP(w)&lt;br /&gt;   if not k: return False&lt;br /&gt;   sides = w[:k], w[k:]&lt;br /&gt;   return all(len(s) == len(set(s)) for s in sides)&lt;br /&gt;&lt;br /&gt;def mintries(d):&lt;br /&gt;   minKeystones = [w for w in d if minKeystoneP(w)]&lt;br /&gt;   nodups = [w for w in d if len(set(w)) == len(w)]&lt;br /&gt;   return TriePair(minKeystones + nodups)&lt;br /&gt;&lt;/code&gt;&lt;p&gt;Here's some profiling data from a run that used a mintries dictionary (stopped at the first feasible solution). I used beam_width = 512, U = 1024:&lt;/p&gt;&lt;table&gt;&lt;caption&gt;solution (65): buckrams donzel ti pyx of vug we jo hm suq us mho jew guv foxy pit lez nods mark cub&lt;br /&gt;3063736602 function calls (1650738620 primitive calls) in 15627.224 CPU seconds&lt;br /&gt;Ordered by: standard name&lt;/caption&gt;&lt;thead&gt; &lt;tr&gt;  &lt;th&gt;ncalls&lt;/th&gt;&lt;th&gt;tottime&lt;/th&gt;&lt;th&gt;percall&lt;/th&gt;&lt;th&gt;cumtime&lt;/th&gt;&lt;th&gt;percall&lt;/th&gt;&lt;th&gt;filename:lineno(function)&lt;/th&gt; &lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt; &lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;15627.223&lt;/td&gt;&lt;td&gt;15627.223&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt; &lt;td&gt;31125689&lt;/td&gt;&lt;td&gt;67.420&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;364.352&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:100(pangramP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt; &lt;td&gt;463188677&lt;/td&gt;&lt;td&gt;289.571&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;289.571&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:102(ancestors)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt; &lt;td&gt;31119396&lt;/td&gt;&lt;td&gt;148.473&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;562.384&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:107(phrase)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119396&lt;/td&gt;&lt;td&gt;258.985&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;413.911&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:108(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119402&lt;/td&gt;&lt;td&gt;137.097&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;896.415&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:109(phrase_string_ns)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;183/78&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:121(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;285653338&lt;/td&gt;&lt;td&gt;160.594&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;160.594&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:129(emptyP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-495413458/-781144130&lt;/td&gt;&lt;td&gt;3292.676&lt;/td&gt;&lt;td&gt;-0.000&lt;/td&gt;&lt;td&gt;3292.676&lt;/td&gt;&lt;td&gt;-0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:131(match)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;144/39&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:155(add)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;48/8&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:176(words)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1230988464/134840576&lt;/td&gt;&lt;td&gt;8569.205&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;12100.459&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:188(_filter_r)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;134840576&lt;/td&gt;&lt;td&gt;127.894&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;12228.353&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:199(filter)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1910&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.043&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:25(palindromeP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6248&lt;/td&gt;&lt;td&gt;0.159&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.931&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:260(s)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;156545&lt;/td&gt;&lt;td&gt;0.201&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.528&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:269(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;25939&lt;/td&gt;&lt;td&gt;0.181&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.709&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:269(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:303(pivotH)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:305(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:306(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;51&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.007&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:307(rightH)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;872&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:309(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;87&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.009&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:310(rightExpand)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;633&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:337(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8/3&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.391&lt;/td&gt;&lt;td&gt;0.130&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:343(completePalindrome)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1910&lt;/td&gt;&lt;td&gt;0.013&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.016&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:37(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:377(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:377(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6262&lt;/td&gt;&lt;td&gt;0.049&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;1.024&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:383(setupContinuation)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31125244&lt;/td&gt;&lt;td&gt;430.604&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;13368.363&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:397(expand)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;21700&lt;/td&gt;&lt;td&gt;0.037&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.037&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:404(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;216034002&lt;/td&gt;&lt;td&gt;221.931&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;356.529&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:406(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6301&lt;/td&gt;&lt;td&gt;0.075&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.168&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:416(visit)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6301&lt;/td&gt;&lt;td&gt;0.041&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.078&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:419(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:424(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;93363173/62244144&lt;/td&gt;&lt;td&gt;198.180&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;322.037&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:440(g)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;179&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:450(partition)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;179&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.036&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:458(palindrome_balance)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31118982&lt;/td&gt;&lt;td&gt;306.394&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;1302.780&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:521(h)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119035&lt;/td&gt;&lt;td&gt;48.462&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;412.754&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:563(feasibleP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;11/2&lt;/td&gt;&lt;td&gt;203.033&lt;/td&gt;&lt;td&gt;18.458&lt;/td&gt;&lt;td&gt;15627.223&lt;/td&gt;&lt;td&gt;7813.611&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:566(bss)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;256/128&lt;/td&gt;&lt;td&gt;0.195&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.298&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:580(prune)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;125068&lt;/td&gt;&lt;td&gt;0.044&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.044&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:585(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31125689&lt;/td&gt;&lt;td&gt;75.291&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;286.533&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:60(pangramP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119035&lt;/td&gt;&lt;td&gt;71.754&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;1642.539&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:615(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:617(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:618(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:621(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;660&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:648(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7511&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:657(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6931&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:658(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6931&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:659(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;45&lt;/td&gt;&lt;td&gt;0.012&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.016&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:659(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;641&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:669(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;15627.223&lt;/td&gt;&lt;td&gt;15627.223&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:677(profileDriver)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:682(keystoneP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119030&lt;/td&gt;&lt;td&gt;149.929&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;351.339&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:90(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_abcoll.py:122(__subclasshook__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:10(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:20(__iter__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31131950&lt;/td&gt;&lt;td&gt;48.803&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;48.803&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:29(__contains__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:36(add)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31131937&lt;/td&gt;&lt;td&gt;75.111&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;123.914&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;abc.py:118(__instancecheck__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4/3&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;abc.py:134(__subclasscheck__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;942&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.006&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;ascii.py:21(encode)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;codecs.py:164(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;codecs.py:937(getincrementalencoder)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;942&lt;/td&gt;&lt;td&gt;0.010&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.016&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1031(write)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;447&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.364&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;io.py:1069(flush)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;447&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.361&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;io.py:1073(_flush_unlocked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;447&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.365&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;io.py:1454(flush)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;942&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1465(closed)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;942&lt;/td&gt;&lt;td&gt;0.012&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.404&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1479(write)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1500(_get_encoder)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2331&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:755(closed)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6301&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method abs}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method any}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;942&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method ascii_encode}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1910&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method ceil}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;15627.224&lt;/td&gt;&lt;td&gt;15627.224&lt;/td&gt;&lt;td&gt;{built-in method exec}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119030&lt;/td&gt;&lt;td&gt;69.197&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;69.197&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method fromkeys}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method getattr}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;155614108&lt;/td&gt;&lt;td&gt;204.289&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;204.289&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method hasattr}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31118982&lt;/td&gt;&lt;td&gt;17.315&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;17.315&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method hash}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;53&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method id}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31133821&lt;/td&gt;&lt;td&gt;76.905&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;200.819&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method isinstance}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method issubclass}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;155695466&lt;/td&gt;&lt;td&gt;50.374&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;50.374&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method len}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method lookup}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;179&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method max}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method min}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6931&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method next}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;447&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.408&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;{built-in method print}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;90&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method sum}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1389&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method '__enter__' of '_thread.lock' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method '__subclasses__' of 'type' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'add' of 'set' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;82757&lt;/td&gt;&lt;td&gt;0.034&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.034&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'append' of 'list' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'disable' of '_lsprof.Profiler' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;942&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'extend' of 'bytearray' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;263060106&lt;/td&gt;&lt;td&gt;78.312&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;78.312&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'items' of 'dict' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31120994&lt;/td&gt;&lt;td&gt;126.532&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;126.532&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'join' of 'str' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31131990&lt;/td&gt;&lt;td&gt;10.402&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;10.402&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'keys' of 'dict' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'pop' of 'list' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;31119029&lt;/td&gt;&lt;td&gt;111.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;111.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'update' of 'dict' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;447&lt;/td&gt;&lt;td&gt;0.357&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.357&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;{method 'write' of '_FileIO' objects}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Using the same parameters but substituting a piecewise expand function to reduce the search space, I produced the same solution in 12949.838 CPU seconds (shortly after the 7232nd node expansion):&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;# this function takes a TriePair to an expand function&lt;br /&gt;# suitable for use with bss:&lt;br /&gt;def budgetExpand(ordinarytries):&lt;br /&gt;   def bex(n, advisoryBudget):&lt;br /&gt;       if n.pangramP():&lt;br /&gt;           for x in ordinarytries.expand(n):&lt;br /&gt;               yield x&lt;br /&gt;           return&lt;br /&gt;       letterAllowance = int((advisoryBudget / 2) - g(n))&lt;br /&gt;       if letterAllowance &amp;lt; 5:&lt;br /&gt;           missing = len(string.ascii_lowercase) - len(n.covered)&lt;br /&gt;           if missing &amp;lt; letterAllowance:&lt;br /&gt;               # we might be able to afford to waste the next word&lt;br /&gt;               for x in ordinarytries.expand(n):&lt;br /&gt;                   yield x&lt;br /&gt;               return&lt;br /&gt;           if missing == 2:&lt;br /&gt;               # find a suitable tp&lt;br /&gt;               for lp, t in raretries:&lt;br /&gt;                   if not any(l in n.covered for l in lp):&lt;br /&gt;                       for x in t.expand(n):&lt;br /&gt;                           yield x&lt;br /&gt;                       return&lt;br /&gt;           if missing &amp;lt;= 4:&lt;br /&gt;               letters = string.ascii_lowercase - n.covered.keys()&lt;br /&gt;               if all(l in lettertries for l in letters):&lt;br /&gt;                   for x in itertools.chain.from_iterable(t.expand(n) for l, t in lettertries.items() if not n.coveredP(l)):&lt;br /&gt;                       yield x&lt;br /&gt;                   return&lt;br /&gt;       for x in ordinarytries.expand(n):&lt;br /&gt;           yield x&lt;br /&gt;   return bex&lt;br /&gt;&lt;/code&gt;&lt;table&gt;&lt;caption&gt;2481633428 function calls (1172623990 primitive calls) in 12949.838 CPU seconds&lt;br /&gt;Ordered by: standard name&lt;br /&gt;&lt;/caption&gt;&lt;thead&gt; &lt;tr&gt;  &lt;th&gt;ncalls&lt;/th&gt;&lt;th&gt;tottime&lt;/th&gt;&lt;th&gt;percall&lt;/th&gt;&lt;th&gt;cumtime&lt;/th&gt;&lt;th&gt;percall&lt;/th&gt;&lt;th&gt;filename:lineno(function)&lt;/th&gt; &lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;12949.838&lt;/td&gt;&lt;td&gt;12949.838&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28221664&lt;/td&gt;&lt;td&gt;56.848&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;307.680&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:100(pangramP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;396097158&lt;/td&gt;&lt;td&gt;231.455&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;231.455&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:102(ancestors)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206562&lt;/td&gt;&lt;td&gt;130.097&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;464.824&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:107(phrase)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206562&lt;/td&gt;&lt;td&gt;210.286&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;334.727&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:108(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28207750&lt;/td&gt;&lt;td&gt;120.352&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;750.641&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:109(phrase_string_ns)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;75/32&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:121(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;265064474&lt;/td&gt;&lt;td&gt;140.758&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;140.758&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:129(emptyP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-776844655/-1041987795&lt;/td&gt;&lt;td&gt;2885.248&lt;/td&gt;&lt;td&gt;-0.000&lt;/td&gt;&lt;td&gt;2885.248&lt;/td&gt;&lt;td&gt;-0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:131(match)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;59/16&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:155(add)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;24/4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:176(words)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1140618203/124958593&lt;/td&gt;&lt;td&gt;6869.411&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;9963.410&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:188(_filter_r)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;124958593&lt;/td&gt;&lt;td&gt;113.443&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;10076.852&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:199(filter)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;394&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.145&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:25(palindromeP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7260&lt;/td&gt;&lt;td&gt;0.162&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.953&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:260(s)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;176364&lt;/td&gt;&lt;td&gt;0.208&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.545&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:269(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28381&lt;/td&gt;&lt;td&gt;0.188&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.733&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:269(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:303(pivotH)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:305(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:306(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;21&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:307(rightH)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;350&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:309(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;36&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:310(rightExpand)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;251&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:337(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5/2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.233&lt;/td&gt;&lt;td&gt;0.117&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:343(completePalindrome)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;394&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.142&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:37(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:377(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:377(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9180&lt;/td&gt;&lt;td&gt;0.052&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;1.037&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:383(setupContinuation)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28215608&lt;/td&gt;&lt;td&gt;356.822&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;11018.065&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:397(expand)&lt;/td&gt;&lt;tr&gt;&lt;td&gt;28840&lt;/td&gt;&lt;td&gt;0.049&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.049&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:404(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;183935843&lt;/td&gt;&lt;td&gt;180.895&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;287.857&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:406(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7287&lt;/td&gt;&lt;td&gt;0.070&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.174&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:416(visit)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7287&lt;/td&gt;&lt;td&gt;0.045&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.089&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:419(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:424(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;84626570/56420123&lt;/td&gt;&lt;td&gt;168.273&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;271.354&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:440(g)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:450(partition)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:458(palindrome_balance)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206428&lt;/td&gt;&lt;td&gt;257.713&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;1093.465&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:521(h)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206451&lt;/td&gt;&lt;td&gt;41.020&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;348.655&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:563(feasibleP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7/2&lt;/td&gt;&lt;td&gt;170.547&lt;/td&gt;&lt;td&gt;24.364&lt;/td&gt;&lt;td&gt;12949.838&lt;/td&gt;&lt;td&gt;6474.919&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:566(bss)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;252/126&lt;/td&gt;&lt;td&gt;0.193&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.258&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:580(prune)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;127106&lt;/td&gt;&lt;td&gt;0.048&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.048&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:585(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28221664&lt;/td&gt;&lt;td&gt;63.621&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;242.118&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:60(pangramP)&lt;/td&gt;&lt;/tr&gt;&lt;td&gt;28206450&lt;/td&gt;&lt;td&gt;63.101&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;1381.873&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:616(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:618(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:619(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:622(&amp;lt;listcomp&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;497&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:649(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8533&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:658(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7784&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:659(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7784&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.004&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:660(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;759&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:670(&amp;lt;lambda&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:683(keystoneP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28213699&lt;/td&gt;&lt;td&gt;29.299&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;11048.220&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:826(bex)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;35164&lt;/td&gt;&lt;td&gt;0.018&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.018&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:844(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3779&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:852(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3731&lt;/td&gt;&lt;td&gt;0.024&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.032&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:854(&amp;lt;genexpr&amp;gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206448&lt;/td&gt;&lt;td&gt;126.205&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;295.114&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:90(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;14576&lt;/td&gt;&lt;td&gt;0.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;&amp;lt;string&amp;gt;:98(coveredP)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_abcoll.py:122(__subclasshook__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:10(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:20(__iter__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28228934&lt;/td&gt;&lt;td&gt;40.811&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;40.811&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:29(__contains__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;_weakrefset.py:36(add)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28228924&lt;/td&gt;&lt;td&gt;63.055&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;103.866&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;abc.py:118(__instancecheck__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4/3&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;abc.py:134(__subclasscheck__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6399&lt;/td&gt;&lt;td&gt;0.014&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.037&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;ascii.py:21(encode)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;codecs.py:164(__init__)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;codecs.py:937(getincrementalencoder)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6399&lt;/td&gt;&lt;td&gt;0.066&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.103&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1031(write)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3190&lt;/td&gt;&lt;td&gt;0.014&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.670&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1069(flush)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3190&lt;/td&gt;&lt;td&gt;0.026&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.655&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1073(_flush_unlocked)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3190&lt;/td&gt;&lt;td&gt;0.009&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.679&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1454(flush)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6399&lt;/td&gt;&lt;td&gt;0.013&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.021&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1465(closed)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6399&lt;/td&gt;&lt;td&gt;0.075&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.919&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1479(write)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:1500(_get_encoder)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;15988&lt;/td&gt;&lt;td&gt;0.014&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.014&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;io.py:755(closed)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7287&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method abs}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;923&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.005&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method all}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;15357&lt;/td&gt;&lt;td&gt;0.013&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.024&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method any}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6399&lt;/td&gt;&lt;td&gt;0.023&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.023&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method ascii_encode}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;394&lt;/td&gt;&lt;td&gt;0.139&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.139&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method ceil}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;12949.838&lt;/td&gt;&lt;td&gt;12949.838&lt;/td&gt;&lt;td&gt;{built-in method exec}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;911&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method from_iterable}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206448&lt;/td&gt;&lt;td&gt;58.517&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;58.517&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method fromkeys}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method getattr}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;141057204&lt;/td&gt;&lt;td&gt;171.326&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;171.326&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method hasattr}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206428&lt;/td&gt;&lt;td&gt;14.606&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;14.606&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method hash}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;22&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method id}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28241722&lt;/td&gt;&lt;td&gt;65.730&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;169.595&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method isinstance}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method issubclass}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;141174090&lt;/td&gt;&lt;td&gt;42.904&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;42.904&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method len}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method lookup}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method max}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method min}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7766&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method next}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3190&lt;/td&gt;&lt;td&gt;0.031&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.949&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method print}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;37&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.001&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{built-in method sum}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9589&lt;/td&gt;&lt;td&gt;0.009&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.009&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method '__enter__' of '_thread.lock' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method '__subclasses__' of 'type' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'add' of 'set' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;85797&lt;/td&gt;&lt;td&gt;0.034&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.034&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'append' of 'list' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'disable' of '_lsprof.Profiler' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6399&lt;/td&gt;&lt;td&gt;0.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.008&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'extend' of 'bytearray' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;244196206&lt;/td&gt;&lt;td&gt;68.331&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;68.331&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'items' of 'dict' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206877&lt;/td&gt;&lt;td&gt;105.938&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;105.938&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'join' of 'str' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28229874&lt;/td&gt;&lt;td&gt;8.717&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;8.717&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'keys' of 'dict' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;15&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'pop' of 'list' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;28206447&lt;/td&gt;&lt;td&gt;92.287&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;92.287&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'update' of 'dict' objects}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3190&lt;/td&gt;&lt;td&gt;0.626&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;0.626&lt;/td&gt;&lt;td&gt;0.000&lt;/td&gt;&lt;td&gt;{method 'write' of '_FileIO' objects}&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;What about that penultimate case, searching up to four overlapping TriePairs each representing the set of words containing a missing letter? How much does it help? The mintriesd superset has 35103 words, and a sample of four-combinations range in size from 11263 to 24428 words. But, the average size lettertrie has 4784 words, so we get a noticeable speedup even though trie search has logarithmic time complexity. Without the lettertries case, we need 2656858411 function calls (1319973770 primitive calls) in 14205.551 CPU seconds.&lt;/p&gt;&lt;p&gt;Finally, I added to the budgetExpand repertoire with lengthtries.&lt;/p&gt;&lt;p&gt;The idea for lengthtries comes from the observation that it would be useful to limit the depth of trie traversals as we approach the current cost bound. With this technique I brought the first solution discovery down to 398442028 function calls in 9485.551 CPU seconds:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def budgetExpand(ordinarytries):&lt;br /&gt;    raretries = [(lp, TriePair([w for w in ordinarytries.fwd.words() if all(l in w for l in lp)])) for lp in 'jm vx jy fx jk gq mq jp qy bq hq pq kz jv wz hj fq qv xz kq jw fj qz wx jz kx qw jx qx jq'.split()]&lt;br /&gt;    hist = Histogram(ordinarytries.fwd.words())&lt;br /&gt;    lettertries = {l : TriePair(hist[l]) for l in [k for k, v in sorted(hist.h.items(), key = lambda i: len(i[1]))][:16]}&lt;br /&gt;    lengthtries = {i : TriePair((w for w in mintriesd.fwd.words() if len(w) &lt;= i or keystoneP(w)), mintriesd.rev.words()) for i in range(1, 8)}&lt;br /&gt;    def bex(n, advisoryBudget):&lt;br /&gt;        if n.pangramP():&lt;br /&gt;            for x in ordinarytries.expand(n):&lt;br /&gt;                yield x&lt;br /&gt;            return&lt;br /&gt;        letterAllowance = int((advisoryBudget / 2) - g(n))&lt;br /&gt;        if letterAllowance &lt; 5:&lt;br /&gt;            missing = len(string.ascii_lowercase) - len(n.covered)&lt;br /&gt;            if missing &amp;lt; letterAllowance:&lt;br /&gt;                # we might be able to afford to waste the next word&lt;br /&gt;                for x in lengthtries[letterAllowance].expand(n):&lt;br /&gt;                    yield x&lt;br /&gt;                return&lt;br /&gt;            if missing == 2:&lt;br /&gt;                # find a suitable tp&lt;br /&gt;                for lp, t in raretries:&lt;br /&gt;                    if not any(l in n.covered for l in lp):&lt;br /&gt;                        for x in t.expand(n):&lt;br /&gt;                            yield x&lt;br /&gt;                        return&lt;br /&gt;            if missing &amp;lt;= 4:&lt;br /&gt;                letters = string.ascii_lowercase - n.covered.keys()&lt;br /&gt;                if all(l in lettertries for l in letters):&lt;br /&gt;                    for x in itertools.chain.from_iterable(t.expand(n) for l, t in lettertries.items() if not n.coveredP(l)):&lt;br /&gt;                        yield x&lt;br /&gt;                    return&lt;br /&gt;        elif letterAllowance in lengthtries:&lt;br /&gt;            for x in lengthtries[letterAllowance].expand(n):&lt;br /&gt;                yield x&lt;br /&gt;            return&lt;br /&gt;        for x in ordinarytries.expand(n):&lt;br /&gt;            yield x&lt;br /&gt;    return bex&lt;br /&gt;&lt;/code&gt;&lt;p&gt;One last idea for search space reduction is to exclude words containing covered letters:&lt;/p&gt;&lt;code class="prettyprint lang-python" style="white-space: pre-wrap"&gt;&lt;br /&gt;def budgetExpand(ordinarytries):&lt;br /&gt;   # least-common 2-combinations of letters in d:&lt;br /&gt;   # [('kx', 53), ('qw', 27), ('jx', 27), ('qx', 26), ('jq', 8)]&lt;br /&gt;   raretries = [(lp, TriePair((w for w in ordinarytries.fwd.words() if all(l in w for l in lp)), ordinarytries.rev)) for lp in 'jm vx jy fx jk gq mq jp qy bq hq pq kz jv wz hj fq qv xz kq jw fj qz wx jz kx qw jx qx jq'.split()]&lt;br /&gt;   hist = Histogram(ordinarytries.fwd.words())&lt;br /&gt;   lettertries = {l : TriePair(hist[l], ordinarytries.rev) for l in [k for k, v in sorted(hist.h.items(), key = lambda i: len(i[1]))][:16]}&lt;br /&gt;   lengthtries = {i : TriePair((w for w in ordinarytries.fwd.words() if len(w) &amp;lt;= i or keystoneP(w)), ordinarytries.rev) for i in range(1, 8)}&lt;br /&gt;   def mfl(k, n):&lt;br /&gt;       """Kth most frequent letter k, n -&amp;gt; (#, a)"""&lt;br /&gt;       if not hasattr(n, 'hist'):&lt;br /&gt;           n.hist = Histogram(n.tp.fwd.words())&lt;br /&gt;       lf = list(itertools.islice(n.hist, k + 1))[-1]&lt;br /&gt;       return (sum(1 for i in lf[1]), lf[0])&lt;br /&gt;   exttltries = Trie()&lt;br /&gt;   exttltries.tp = mintriesd&lt;br /&gt;   # this isn't optimal; there may be unreachable&lt;br /&gt;   # nodes and some of those may be more selective&lt;br /&gt;   # than letter-equivalent reachable nodes. But&lt;br /&gt;   # it won't yield incorrect results and it's&lt;br /&gt;   # an approximation I can build quickly.&lt;br /&gt;   for i in range(32):&lt;br /&gt;       (freq, split_char), parent = max(&lt;br /&gt;           (mfl(len(n.children), n), n) for n in&lt;br /&gt;           exttltries.pre_order_nodes() if len(n.children) &amp;lt;= 4)&lt;br /&gt;       parent.add(split_char)&lt;br /&gt;       subtrie = parent.search(split_char)&lt;br /&gt;       subtrie.tp = TriePair(Trie(w for w in parent.tp.fwd.words() if split_char not in w), parent.tp.rev)&lt;br /&gt;   print([x for x in exttltries.pre_order_nodes()])&lt;br /&gt;   print([sum(1 for y in x.tp.fwd.words()) for x in exttltries.pre_order_nodes()])&lt;br /&gt;   def pickTP(t, n):&lt;br /&gt;       if (len(t.children) == 0): return t.tp&lt;br /&gt;       for l in t.children:&lt;br /&gt;           if not l in n.covered: continue&lt;br /&gt;           return pickTP(t.children[l], n)&lt;br /&gt;       return t.tp&lt;br /&gt;   def bex(n, advisoryBudget):&lt;br /&gt;       if n.pangramP():&lt;br /&gt;           for x in ordinarytries.expand(n):&lt;br /&gt;               yield x&lt;br /&gt;           return&lt;br /&gt;       letterAllowance = int((advisoryBudget / 2) - g(n))&lt;br /&gt;       if letterAllowance &amp;lt; 5:&lt;br /&gt;           missing = len(string.ascii_lowercase) - len(n.covered)&lt;br /&gt;           if missing &amp;lt; letterAllowance:&lt;br /&gt;               # we might be able to afford to waste the next word&lt;br /&gt;               for x in lengthtries[letterAllowance].expand(n):&lt;br /&gt;                   yield x&lt;br /&gt;               return&lt;br /&gt;           if missing == 2:&lt;br /&gt;               # find a suitable tp&lt;br /&gt;               for lp, t in raretries:&lt;br /&gt;                   if not any(l in n.covered for l in lp):&lt;br /&gt;                       for x in t.expand(n):&lt;br /&gt;                           yield x&lt;br /&gt;                       return&lt;br /&gt;           if missing &amp;lt;= 4:&lt;br /&gt;               letters = string.ascii_lowercase - n.covered.keys()&lt;br /&gt;               if all(l in lettertries for l in letters):&lt;br /&gt;                   for x in itertools.chain.from_iterable(t.expand(n) for l, t in lettertries.items() if not n.coveredP(l)):&lt;br /&gt;                       yield x&lt;br /&gt;                   return&lt;br /&gt;       if letterAllowance in lengthtries:&lt;br /&gt;           for x in lengthtries[letterAllowance].expand(n):&lt;br /&gt;               yield x&lt;br /&gt;           return&lt;br /&gt;       for x in pickTP(exttltries, n).expand(n):&lt;br /&gt;           yield x&lt;br /&gt;   return bex&lt;br /&gt;&lt;/code&gt;&lt;p&gt;There are unreachable TriePairs in exttltries, and some of those are smaller than letter-equivalent reachable ones. To avoid this, we should maintain the exttltries property that no node's letter should appear in the set of siblings of its ancestors. Together with the greedy order for splitting nodes, this minimizes size and optimizes expected search cost. But we do pretty well even with the quick and dirty experiment: we find a first solution (cost 67) before the 7808 node expansion in under 4562.899 CPU seconds. A 65 solution is found before the 9984 node expansion in under 5141.208 CPU seconds.&lt;/p&gt;&lt;p&gt;I never got around to rewriting variations in search of more beautiful code. I still wonder what other tricks I've overlooked for improving the heuristic function or reducing the search space.&lt;/p&gt;&lt;p&gt;Rumor has it that Common Lisp has been really well-optimized over the years, so I wonder how much faster this could go if rewritten in lisp. Also, I could go twice as fast on my dual-core CPU if python had threads (just partition the input) or if I quit some other apps in order to fit two python instances of this program in my 4GB of RAM.&lt;/p&gt;&lt;p&gt;Many have posted solutions for this problem. Solutions I can recommend reading if this post has piqued your interest include &lt;a href="http://www.jimvellenga.com/palpan/index.html"&gt;Jim Vellenga's&lt;/a&gt; and &lt;a href="http://coldhawaiian.com/"&gt;Keoki Zee's&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-9064166640390707284?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/9064166640390707284/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2010/04/palindromic-pangrams.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/9064166640390707284'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/9064166640390707284'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2010/04/palindromic-pangrams.html' title='Palindromic Pangrams'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-2204838646337233655</id><published>2010-02-10T17:09:00.005-06:00</published><updated>2010-02-10T17:47:20.195-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='trampoline'/><category scheme='http://www.blogger.com/atom/ns#' term='monad'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='cps'/><category scheme='http://www.blogger.com/atom/ns#' term='sort'/><category scheme='http://www.blogger.com/atom/ns#' term='continuation'/><title type='text'>Continuation Monad</title><content type='html'>&lt;p&gt;Last month I worked on a TopCoder problem that led me to continue my exploration of monads. The problem is to sort a set of elements using comparisons whose cost is a function of the comparands. Solutions comprise a pair of methods, one of which initializes the program with the costs, and the other of which is invoked repeatedly to effect the dialog between the program and the less-than oracle.&lt;/p&gt;&lt;p&gt;Now choose your own adventure. Do you:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ask &lt;a href="http://www.cs.cornell.edu/home/kleinber/stoc00-pi.ps"&gt;how a real computer scientist would solve it&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Watch me muddle through &lt;a href="#cbswvc"&gt;comparison-based sorting with variable costs&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Follow my &lt;a href="#continuation-monad"&gt;continuation monad implementation in Java&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h4 id="cbswvc"&gt;Comparison-based sorting with variable costs&lt;/h4&gt;&lt;div style="float: right; width: 33ex; border-left: solid thin black; font-size: 80%"&gt;&lt;h5&gt;Sidebar: true story&lt;/h5&gt;&lt;p&gt;I've seen a related problem in real life. Our app iDeal helped salespersons develop contract terms subject to Boolean constraints called guidelines, analogous to the Boolean trees examined in &lt;a href="http://www.cs.cornell.edu/home/kleinber/stoc00-pi.ps"&gt;Charikar et al.&lt;/a&gt; We didn't actually have to buy our guideline results, but we did care about evaluation speed. There were basically two prices: low (computable on our local app server) and high (computable at a minicomputer reached by at least two SOAP hops and a batch job. Unlike the Charikar model, our cost function was not &lt;a href="http://mathworld.wolfram.com/AdditiveFunction.html"&gt;additive&lt;/a&gt;: it behaved like &lt;a href="http://en.wikipedia.org/wiki/Logical_disjunction"&gt;or&lt;/a&gt;, giving a result of "expensive" whenever at least one guideline couldn't be computed locally and "cheap" otherwise. Consequently, the real-life challenge was less about algorithm design and more about software engineering: given that guideline evaluation is demanded at many call sites, how can we avoid unnecessarily consulting the minicomputer?&lt;/p&gt;&lt;/div&gt;&lt;p&gt;I first thought of divide and conquer approaches to avoid redundant queries (don't ask a &lt; c if it is already known that a &lt; b and b &lt; c). I thought about algorithms for the special case when C(a, b) = 1: mergesort, heapsort, and quicksort. All of these relate to binary trees, and I thought about properties a binary search tree constructed at optimal cost would have in the general case.&lt;/p&gt;&lt;p&gt;Shouldn't the root of the tree be the element for which the sum over pairs of itself with each of its descendants is minimal? Intuitively, we compare every element with the root to locate the element in the tree, and we want to minimize the cost of inserting each element by this recursive process. Not quite: this fails to account for tree shape (consider p &lt; q &lt; r with costs C(p, X) = 1 for all X in {p, q, r}; C(q, r) = 2). And thinking back to the special case of a constant cost function, it's clear that this root selection heuristic has no power. It's also often mentioned in discussions of e.g., quicksort, that it's important to have a balanced tree. But we're not looking just to balance and fill the tree; It's easy to ensure that every left child in an optimal tree is a leaf node by carefully choosing the cost function.&lt;/p&gt;&lt;p&gt;The shape of the tree is determined both by the sequence of queries the program makes and by the order of elements determined by the oracle. The best choice for the root is the median of the elements, so an exact solution is O(n^2) but we could think about approximate selection and average-case performance.&lt;/p&gt;&lt;p&gt;What about a bottom-up approach? Do our trees have optimal substructure? First: in an optimal tree, is every subtree optimal? Suppose not. Then some optimal tree has a subtree that can be rearranged for lower cost. But this doesn't affect the cost to construct the rest of the tree, so after the rearrangement the overall cost is lowered. This contradicts the supposition that our original tree was optimal with a suboptimal subtree. But second: can we efficiently combine optimal subsolutions to derive optimal solutions?&lt;/p&gt;&lt;p&gt;A straightforward mergesort would guarantee a minimal number of comparisons, but not necessarily a minimal-cost set of comparisons. Although a minimal tree implies minimal subtrees, not all combinations of minimal subtrees constitute a (rearrangement of a) minimal tree. To see this, imagine a cost function that does not have the triangle property. In this case, the cheapest way to combine two trees might involve using additional vertices not a part of either original tree.&lt;/p&gt;&lt;p&gt;Let g(t) = sum(C(n, n') for n in t for n' in ~t). Recursively merge the runs with the lowest g.&lt;/p&gt;&lt;p&gt;Pf. g-ordered mergesort yields optimal solutions.&lt;br /&gt;&lt;p&gt;Let A and B represent two C-optimal subsolutions whose g-costs are least among all subsolutions. Claim: the combination S of A and B is C-optimal. Suppose not. Then there is a (sub)solution S' containing all the elements of a and b such that C(S) &gt; C(S'). S' is a proper superset of S, so we can obtain it by adding elements from the complement of S. These missing elements must exist in other optimal subsolutions having higher g-costs than that of S. Also, each of these elements must have edges to each of a and b in the optimal tree of S. Consider any subsolution G hosting one of these missing elements. It's g-cost is \sum_{g \in G}(\sum_{a \in A}(C(g, a)) + \sum_{b \in B}(C(g, b)) + \sum_{c' \in c}(C(g, c'))), where a, b, and c' are drawn from our input sets and the complement of their union respectively. To simplify the notation, I'm going to switch now to using implicit summations: the g-cost of subsolution G is C(g, a) + C(g, b) + C(g, c). The g-cost of S is c(a, c) + c(b, c) + c(s, c). The g-cost of a is c(a, b) + c(a, c). The g-cost of b is c(a, b) + c(b, c). A little algebra and the observation that the complement of the union of a and b includes all the g \in G, we can derive a contradiction from the assumption that there exist elements outside the min-g-cost subsolutions a and b that would improve the C-cost of the combination of a and b:&lt;/p&gt;&lt;pre&gt;&lt;br /&gt;c(s a) + c(s b) + c(s c) &amp;gt; max(c(a c) + c(a b); c(b c) + c(a b))&lt;br /&gt;&amp;gt; max(c(S) - c(s, c) - c(b, c) + c(a, b);&lt;br /&gt;c(S) - c(s, c) - c(a, c) + c(a, b))&lt;br /&gt;c(s a) + c(s b) + 2c(s c) - c(a b) - c(S) &amp;gt; max(- c(b, c); - c(a c))&lt;br /&gt;- c(s c) - c(a c) - c(b c)&lt;br /&gt;c(s a) + c(s b) + c(s c) - c(a b) - c(a c) - c(b c) &amp;gt; -max(c(b c); c(a c))&lt;br /&gt;c(s a) + c(s b) + c(s c) - c(a b) - c(a c) &amp;gt; 0&lt;br /&gt;c(s a) + c(s b) + c(s c) - c(a b) - c(b c) &amp;gt; 0&lt;br /&gt;c(s a) + c(s b) + c(s c) &amp;gt; c(a b) + c(a c)&lt;br /&gt;c(s a) + c(s b) + c(s c) &amp;gt; c(a b) + c(b c)&lt;br /&gt;c(a c) includes c(a s) so c(a c) &amp;gt; c(a s)&lt;br /&gt;c(s b) + c(s c) &amp;gt; c(a b)&lt;br /&gt;c(s a) + c(s c) &amp;gt; c(a b)&lt;br /&gt;c(s b) - c(s a) &amp;gt; 0&lt;br /&gt;c(s a) - c(s b) &amp;gt; 0&lt;br /&gt;⟶⟵&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Sadly, although this heuristic won't mislead us, it may often fail to answer by telling us that more than two trees have the least g-cost.&lt;/p&gt;&lt;p&gt;A more dynamic heuristic could rely on probabilistic estimates of the information gain at each query. Assuming uniform random permutation, initially we should just do the cheapest comparison. As we progress, we'll develop equivalence classes: these elements could be anywhere; those elements could only be in the top 6. Comparisons involving elements in large classes or classes near the min or max yield relatively little information. We could combine estimates of value with the given cost information to get reasonable average-case performance.&lt;/p&gt;&lt;p&gt;I decided not to work harder to prove a tight lower bound on the solution to the problem or to find a better algorithm or heuristic. It's TopCoder after all, and speed is important. I decided instead to move on to implementation and Google for &lt;a href="http://www.cs.cornell.edu/home/kleinber/stoc00-pi.ps"&gt;a better answer&lt;/a&gt; later.&lt;/p&gt;&lt;p&gt;So in a nutshell my solution is: initially regard every element as a subsolution. Order them by g-cost and merge the two lowest-ranked solutions. Recurse.&lt;/p&gt;&lt;h4 id="continuation-monad"&gt;Continuation monad&lt;/h4&gt;&lt;p&gt;The online interactions between the TopCoder oracle and my solution raises an interesting question. The first thing that occurred to me of course was to write g and merge, arrange to store state in instance variables, and update my instance state incrementally in the two callback functions whose existence is mandated by the oracle. On second thought, I wondered if my code could more closely resemble the nutshell description I gave above, with the state maintenance handled separately. It seemed like a good excuse to get some first-hand experience with continuation-passing style, a canonical form for programs that can be paused and restarted. The continuation monad brings cps into a more general framework, improving modularity.&lt;/p&gt;&lt;p&gt;So I broke out my &lt;a href="http://seanfoy.blogspot.com/2009/06/andandnet-via-monads.html"&gt;old&lt;/a&gt; favorites &lt;a href="http://homepages.inf.ed.ac.uk/wadler/topics/monads.html#marktoberdorf"&gt;Monads for Functional Programming&lt;/a&gt; and &lt;a href="http://homepages.inf.ed.ac.uk/wadler/topics/monads.html#monads"&gt;Comprehending Monads&lt;/a&gt;. I also found a couple of new sources: &lt;a href="http://academic.udayton.edu/SaverioPerugini/courses/cps343/lecture_notes/continuations.html"&gt;Lecture notes: Continuations and call/cc&lt;/a&gt; and &lt;a href="http://intensivesystems.net/tutorials/cont_m.html"&gt;The Continuation Monad in Clojure&lt;/a&gt;. Continuations are also discussed at some length in the &lt;a href="http://repository.readscheme.org/ftp/papers/ai-lab-pubs/AIM-349.pdf"&gt;lambda papers&lt;/a&gt;, although the idea originated a decade earlier.&lt;/p&gt;&lt;p&gt;First, the monad preliminaries. When I implemented the maybe monad in C#, I had the ambition to support composition of different monads and that led to some difficulty with the type system. Also, I noticed how cumbersome the syntax was and what effect that had on my enthusiasm for explicit and separate expression of an abstraction for monads. And that was with C# 3, which has overtaken Java in its evolution towards greater expressive power. So this time I decided to focus strictly on the continuation monad and the question of whether it could enable me to separate the oracle i/o from the specification of my sort algorithm.&lt;/p&gt;&lt;p&gt;Recall the monad laws:&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;@Test&lt;br /&gt;// unit a ⋆ λb. n = n[a/b]&lt;br /&gt;public void leftUnit() {&lt;br /&gt;    Func1&amp;lt;String, CMonad&amp;lt;Integer&amp;gt;&amp;gt; f =&lt;br /&gt;        new Func1&amp;lt;String, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;            public CMonad&amp;lt;Integer&amp;gt; f(String a) {&lt;br /&gt;                return CMonad.unit(a.length());&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;    String a = &amp;quot;a&amp;quot;;&lt;br /&gt;    CMonad&amp;lt;Integer&amp;gt; expected =&lt;br /&gt;        f.f(a);&lt;br /&gt;    CMonad&amp;lt;Integer&amp;gt; actual =&lt;br /&gt;        CMonad.map(&lt;br /&gt;            f,&lt;br /&gt;            CMonad.unit(a));&lt;br /&gt;&lt;br /&gt;    assertMEquals(expected, actual);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;@Test&lt;br /&gt;// m ⋆ λa. unit a = m&lt;br /&gt;public void rightUnit() {&lt;br /&gt;    CMonad&amp;lt;Integer&amp;gt; m =&lt;br /&gt;        CMonad.unit(42);&lt;br /&gt;    Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt; id =&lt;br /&gt;        new Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;            public CMonad&amp;lt;Integer&amp;gt; f(Integer x) {&lt;br /&gt;                return CMonad.unit(x);&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;    assertMEquals(&lt;br /&gt;        m,&lt;br /&gt;        CMonad.map(id, m));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;@Test&lt;br /&gt;// m ⋆ (λa. n ⋆ λb. o) = (m ⋆ λa. n) ⋆ λb. o&lt;br /&gt;public void associative() {&lt;br /&gt;    Func1&amp;lt;String, CMonad&amp;lt;Integer&amp;gt;&amp;gt; fa =&lt;br /&gt;        new Func1&amp;lt;String, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;            public CMonad&amp;lt;Integer&amp;gt; f(String x) {&lt;br /&gt;&lt;br /&gt;                return CMonad.unit(x.length());&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;    Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt; fb =&lt;br /&gt;        new Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;            public CMonad&amp;lt;Integer&amp;gt; f(Integer x) {&lt;br /&gt;                return CMonad.unit(x * 2);&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;    final CMonad&amp;lt;String&amp;gt; m =&lt;br /&gt;        CMonad.unit(&amp;quot;m&amp;quot;);&lt;br /&gt;&lt;br /&gt;    final CMonad&amp;lt;Integer&amp;gt; n =&lt;br /&gt;        CMonad.unit(5);&lt;br /&gt;&lt;br /&gt;    final Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt; o =&lt;br /&gt;        new Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;            public CMonad&amp;lt;Integer&amp;gt; f(Integer x) {&lt;br /&gt;                return CMonad.unit(x - 1);&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;&lt;br /&gt;    CMonad&amp;lt;Integer&amp;gt; expected =&lt;br /&gt;        CMonad.map(&lt;br /&gt;            new Func1&amp;lt;String, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;                public CMonad&amp;lt;Integer&amp;gt; f(String x) {&lt;br /&gt;                    return&lt;br /&gt;                        CMonad.map(&lt;br /&gt;                            o,&lt;br /&gt;                            n);&lt;br /&gt;                }&lt;br /&gt;            },&lt;br /&gt;            m);&lt;br /&gt;&lt;br /&gt;    CMonad&amp;lt;Integer&amp;gt; actual =&lt;br /&gt;        CMonad.map(&lt;br /&gt;            o,&lt;br /&gt;            CMonad.map(&lt;br /&gt;                new Func1&amp;lt;String, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;                    public CMonad&amp;lt;Integer&amp;gt; f(String x) {&lt;br /&gt;                        return n;&lt;br /&gt;                    }&lt;br /&gt;                },&lt;br /&gt;                m));&lt;br /&gt;&lt;br /&gt;    assertMEquals(expected, actual);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;p&gt;(assertMEquals is more or less what you'd expect):&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;private &lt;T&gt; void assertMEquals(CMonad&lt;T&gt; expected, CMonad&lt;T&gt; actual) {&lt;br /&gt;    Func1&lt;T, T&gt; id =&lt;br /&gt;        new CostlySorting.Func1&lt;T, T&gt;() {&lt;br /&gt;            public T f(T x) {&lt;br /&gt;                return x;&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;    T exp = expected.f(id);&lt;br /&gt;    T act = actual.f(id);&lt;br /&gt;    assertEquals(exp, act);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;p&gt;My continuation monad satisfies those:&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;public abstract static class CMonad&amp;lt;X&amp;gt; {&lt;br /&gt;    private CMonad() {}&lt;br /&gt;    public abstract &amp;lt;R&amp;gt; R f(Func1&amp;lt;X, R&amp;gt; k);&lt;br /&gt;    public static &amp;lt;X&amp;gt; CMonad&amp;lt;X&amp;gt; unit(final X x) {&lt;br /&gt;        return&lt;br /&gt;            new CMonad&amp;lt;X&amp;gt;() {&lt;br /&gt;                public &amp;lt;R&amp;gt; R f(Func1&amp;lt;X, R&amp;gt; k) {&lt;br /&gt;                    return k.f(x);&lt;br /&gt;                }&lt;br /&gt;            };&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // aka bind aka ★&lt;br /&gt;    public static &amp;lt;X, R&amp;gt; CMonad&amp;lt;R&amp;gt; map(final Func1&amp;lt;X, CMonad&amp;lt;R&amp;gt;&amp;gt; f, final CMonad&amp;lt;X&amp;gt; mx) {&lt;br /&gt;        return new CMonad&amp;lt;R&amp;gt;() {&lt;br /&gt;            public &amp;lt;S&amp;gt; S f(Func1&amp;lt;R, S&amp;gt; k) {&lt;br /&gt;                return mx.f(f).f(k);&lt;br /&gt;            }&lt;br /&gt;        };&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;p&gt;call/cc seemed like a useful abstraction, so I implemented that in terms of my monad:&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;public Integer divideByZeroEg(final int x, final int y) {&lt;br /&gt;    CMonad&amp;lt;Integer&amp;gt; eg =&lt;br /&gt;        CMonad.callcc(&lt;br /&gt;            new Func1&amp;lt;Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt;, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;                public CMonad&amp;lt;Integer&amp;gt; f(Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt; esc) {&lt;br /&gt;                    return&lt;br /&gt;                        CMonad.map(&lt;br /&gt;                            new Func1&amp;lt;Integer, CMonad&amp;lt;Integer&amp;gt;&amp;gt;() {&lt;br /&gt;                                public CMonad&amp;lt;Integer&amp;gt; f(Integer z) {&lt;br /&gt;                                    return CMonad.unit(x / z);&lt;br /&gt;                                }&lt;br /&gt;                            },&lt;br /&gt;                            (y == 0 ? esc.f(42) : CMonad.unit(y)));&lt;br /&gt;                }&lt;br /&gt;            });&lt;br /&gt;    return eg.f(&lt;br /&gt;        new Func1&amp;lt;Integer, Integer&amp;gt;() {&lt;br /&gt;            public Integer f(Integer x) {&lt;br /&gt;                return x;&lt;br /&gt;            }&lt;br /&gt;        });&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;@Test&lt;br /&gt;public void divideByZeroEg() {&lt;br /&gt;    assertEquals((Object)5, divideByZeroEg(20, 4));&lt;br /&gt;    assertEquals((Object)42, divideByZeroEg(20, 0));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static &amp;lt;X, Y&amp;gt; CMonad&amp;lt;X&amp;gt; callcc(final Func1&amp;lt;Func1&amp;lt;X, CMonad&amp;lt;Y&amp;gt;&amp;gt;, CMonad&amp;lt;X&amp;gt;&amp;gt; g) {&lt;br /&gt;    // .\ k -&amp;gt; g(.\x -&amp;gt; .\k' -&amp;gt; kx)k&lt;br /&gt;    // g :: ((X -&amp;gt; MY) -&amp;gt; MX)&lt;br /&gt;    // g :: ((X -&amp;gt; (Y -&amp;gt; R)) -&amp;gt; (X -&amp;gt; R'))&lt;br /&gt;    // k :: X -&amp;gt; R''&lt;br /&gt;    // R'' = R&lt;br /&gt;    // R = X -&amp;gt; R'&lt;br /&gt;    return&lt;br /&gt;        map(&lt;br /&gt;            new Func1&amp;lt;X, CMonad&amp;lt;X&amp;gt;&amp;gt;() {&lt;br /&gt;                public CMonad&amp;lt;X&amp;gt; f(X x) {&lt;br /&gt;                    return unit(x);&lt;br /&gt;                }&lt;br /&gt;            },&lt;br /&gt;            new CMonad&amp;lt;X&amp;gt;() {&lt;br /&gt;                public &amp;lt;R&amp;gt; R f(final Func1&amp;lt;X, R&amp;gt; k) {&lt;br /&gt;                    CMonad&amp;lt;X&amp;gt; gresult =&lt;br /&gt;                        g.f(&lt;br /&gt;                            new Func1&amp;lt;X, CMonad&amp;lt;Y&amp;gt;&amp;gt;() {&lt;br /&gt;                                public CMonad&amp;lt;Y&amp;gt; f(final X x) {&lt;br /&gt;                                    return new CMonad&amp;lt;Y&amp;gt;() {&lt;br /&gt;                                        public &amp;lt;S&amp;gt; S f(Func1&amp;lt;Y, S&amp;gt; kprime) {&lt;br /&gt;                                            return (S)k.f(x);&lt;br /&gt;                                        }&lt;br /&gt;                                    };&lt;br /&gt;                                }&lt;br /&gt;                            });&lt;br /&gt;                    return gresult.f(k);&lt;br /&gt;                }&lt;br /&gt;            });&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;p&gt;and then I implemented a while loop atop continuation monad and call/cc:&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;@Test&lt;br /&gt;public void finiteWhile() {&lt;br /&gt;    final int [] countdown = new int [] {3};&lt;br /&gt;    CMonad.cpswhile(&lt;br /&gt;        new Func0&amp;lt;Boolean&amp;gt;() {&lt;br /&gt;            public Boolean f() {&lt;br /&gt;                return countdown[0] &amp;gt; 0;&lt;br /&gt;            }&lt;br /&gt;        },&lt;br /&gt;        new SideEffectful() {&lt;br /&gt;            public void f() {&lt;br /&gt;                countdown[0] -= 1;&lt;br /&gt;            }&lt;br /&gt;        });&lt;br /&gt;    assertEquals(0, countdown[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private static &amp;lt;T&amp;gt; CMonad&amp;lt;T&amp;gt; recurse(CMonad&amp;lt;T&amp;gt; m) {&lt;br /&gt;    final WhateverClosure&amp;lt;CMonad&amp;lt;T&amp;gt;&amp;gt; loop =&lt;br /&gt;        new WhateverClosure&amp;lt;CMonad&amp;lt;T&amp;gt;&amp;gt;();&lt;br /&gt;    loop.whatever =&lt;br /&gt;        CMonad.map(&lt;br /&gt;            new Func1&amp;lt;T, CMonad&amp;lt;T&amp;gt;&amp;gt;() {&lt;br /&gt;                public CMonad&amp;lt;T&amp;gt; f(T x) {&lt;br /&gt;                    System.out.println(&amp;quot;recurse&amp;quot;);&lt;br /&gt;                    return loop.whatever;&lt;br /&gt;                }&lt;br /&gt;            },&lt;br /&gt;            m);&lt;br /&gt;    return loop.whatever;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static void cpswhile(final Func0&amp;lt;Boolean&amp;gt; loopInvariant, final SideEffectful body) {&lt;br /&gt;    callcc(&lt;br /&gt;        new Func1&amp;lt;Func1&amp;lt;Object, CMonad&amp;lt;Object&amp;gt;&amp;gt;, CMonad&amp;lt;Object&amp;gt;&amp;gt;() {&lt;br /&gt;            public CMonad&amp;lt;Object&amp;gt; f(final Func1&amp;lt;Object, CMonad&amp;lt;Object&amp;gt;&amp;gt; esc) {&lt;br /&gt;                return recurse(&lt;br /&gt;                    new CMonad&amp;lt;Object&amp;gt;() {&lt;br /&gt;                        public &amp;lt;R&amp;gt; R f(Func1&amp;lt;Object, R&amp;gt; f) {&lt;br /&gt;                            System.out.println(&amp;quot;start.f&amp;quot;);&lt;br /&gt;                            if (!loopInvariant.f()) {&lt;br /&gt;                                System.out.println(&amp;quot;done&amp;quot;);&lt;br /&gt;                                return&lt;br /&gt;                                    esc.f(null).f(f);&lt;br /&gt;                            }&lt;br /&gt;                            System.out.println(&amp;quot;body.f&amp;quot;);&lt;br /&gt;                            body.f();&lt;br /&gt;                            return f.f(null);&lt;br /&gt;                        }&lt;br /&gt;                    });&lt;br /&gt;            }&lt;br /&gt;        }).f(new Func1&amp;lt;Object, Object&amp;gt;() {&lt;br /&gt;                 public Object f(Object x) {&lt;br /&gt;                     return x;&lt;br /&gt;                 }&lt;br /&gt;             });&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public interface SideEffectful {&lt;br /&gt;    public void f();&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;p&gt;Why reimplement the while? Java's while keyword only composes in the predefined ways that Java's language designers cared about. I wouldn't be able to effect oracle I/O between loop iterations, except by resorting to contrived method invocations in the loop body or test. Also, because of the communications mechanism is method invocation, I couldn't actually use while without also using threads. By using composable continuation monads for control flow, I hoped to express my sort algorithm separately from managing the data flow between the program and the TC oracle.&lt;/p&gt;&lt;p&gt;Of course, Lisp and Haskell don't always translate easily to Java:&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;@Test&lt;br /&gt;// (recipe for stack overflow)&lt;br /&gt;public void infiniteWhile() {&lt;br /&gt;    // customize this value for your stack size/patience&lt;br /&gt;    int approximatelyInfinity = 1;&lt;br /&gt;&lt;br /&gt;    final Calendar finish = Calendar.getInstance();&lt;br /&gt;    finish.add(Calendar.SECOND, approximatelyInfinity);&lt;br /&gt;    //CMonad.cpsbounce(&lt;br /&gt;    CMonad.cpsbouncewhile(&lt;br /&gt;        new Func0&amp;lt;Boolean&amp;gt;() {&lt;br /&gt;            public Boolean f() {&lt;br /&gt;                return finish.after(Calendar.getInstance());&lt;br /&gt;            }&lt;br /&gt;        },&lt;br /&gt;        new SideEffectful() {&lt;br /&gt;            public void f() {&lt;br /&gt;            }&lt;br /&gt;        });&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static void cpsbouncewhile(final Func0&amp;lt;Boolean&amp;gt; loopInvariant, final SideEffectful body) {&lt;br /&gt;    CMonad&amp;lt;Object&amp;gt; mo =&lt;br /&gt;        callcc(&lt;br /&gt;            new Func1&amp;lt;Func1&amp;lt;Object, CMonad&amp;lt;Object&amp;gt;&amp;gt;, CMonad&amp;lt;Object&amp;gt;&amp;gt;() {&lt;br /&gt;                public CMonad&amp;lt;Object&amp;gt; f(final Func1&amp;lt;Object, CMonad&amp;lt;Object&amp;gt;&amp;gt; esc) {&lt;br /&gt;                    final CMonad&amp;lt;Object&amp;gt; start =&lt;br /&gt;                        new CMonad&amp;lt;Object&amp;gt;() {&lt;br /&gt;                            public &amp;lt;R&amp;gt; R f(Func1&amp;lt;Object, R&amp;gt; f) {&lt;br /&gt;                                System.out.println(&amp;quot;start.f&amp;quot;);&lt;br /&gt;                                if (!loopInvariant.f()) {&lt;br /&gt;                                    System.out.println(&amp;quot;done&amp;quot;);&lt;br /&gt;                                    return&lt;br /&gt;                                        esc.f(null).f(f);&lt;br /&gt;                                }&lt;br /&gt;                                System.out.println(&amp;quot;body.f&amp;quot;);&lt;br /&gt;                                body.f();&lt;br /&gt;                                return f.f(null);&lt;br /&gt;                            }&lt;br /&gt;                        };&lt;br /&gt;&lt;br /&gt;                    final WhateverClosure&amp;lt;CMonad&amp;lt;Object&amp;gt;&amp;gt; restartHolder = new WhateverClosure&amp;lt;CMonad&amp;lt;Object&amp;gt;&amp;gt;();&lt;br /&gt;                    restartHolder.whatever =&lt;br /&gt;                        map(&lt;br /&gt;                            new Func1&amp;lt;Object, CMonad&amp;lt;Object&amp;gt;&amp;gt;() {&lt;br /&gt;                                public CMonad&amp;lt;Object&amp;gt; f(Object x) {&lt;br /&gt;                                    System.out.println(&amp;quot;restart.f&amp;quot;);&lt;br /&gt;                                    return&lt;br /&gt;                                        esc.f(&lt;br /&gt;                                            Bounce.wrap(&lt;br /&gt;                                                restartHolder.whatever));&lt;br /&gt;                                }&lt;br /&gt;                            },&lt;br /&gt;                            start);&lt;br /&gt;&lt;br /&gt;                    return restartHolder.whatever;&lt;br /&gt;                }&lt;br /&gt;            });&lt;br /&gt;    for (; mo != null;&lt;br /&gt;        mo =&lt;br /&gt;            (CMonad&amp;lt;Object&amp;gt;)mo.f(new Func1&amp;lt;Object, Object&amp;gt;() {&lt;br /&gt;                public Object f(Object x) {&lt;br /&gt;                    return x;&lt;br /&gt;                }&lt;br /&gt;            })) {&lt;br /&gt;        if (!(mo instanceof Bounce)) continue;&lt;br /&gt;        System.out.println(&amp;quot;bouncey&amp;quot;);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;p&gt;So then how'd I do? Well, the test code for while loops is fairly readable, but my goal was to write something like&lt;/p&gt;&lt;code class="prettyprint lang-java"&gt;&lt;br /&gt;// T is a priority queue of sorted runs&lt;br /&gt;while (T.size() &amp;gt; 1) {&lt;br /&gt;    T.add(merge(comparator, T.remove(), T.remove());&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;p&gt;and have that automatically translated to a continuation monad representing the progress of the loop as a function from Booleans (comparison results) to pairs of its own type and pairs of elements to compare next.&lt;/p&gt;&lt;p&gt;Dealing with the while per se was not bad, but translating the body requires quite a lot of fussiness. We need not only a restartable outer (while) loop, but also a restartable inner (merge) loop and we want these flattened so that the while's client code can be oblivious to the implementation choice to use functional composition in the while body. Looking at things a different way, we would like to encapsulate data flow so that a caller can repeatedly supply data to meet the demands of the callee until the callee finishes.&lt;/p&gt;&lt;p&gt;We could use call/cc whenever we need input to obtain a restart wherever we need input. These restarts are relatively easy to pass around (no need for joins), but they only support one standard operation (escape). Accordingly, each adjacent caller/callee pair have to be tweaked to expect/return restarts (analogously to monads in the above paragraph) &lt;em&gt;and&lt;/em&gt; each caller has to branch at each invocation to either pass back a restart or move on with its own computation.&lt;/p&gt;&lt;p&gt;Alternatively, we could add a function to represent function application within the continuation monad framework. We could tweak while so that its body argument has a result type of continuation-monad, and then this new apply function could yield such a monad; while could be responsible for composing that result into its own result (using the join operation from MMA -&gt; MA).&lt;/p&gt;&lt;p&gt;Although we're able to dispense with the repetitive branching at function invocations in the call/cc approach by using monads, we can't escape the need to reimplement Java all along the way. If the body of a while loop includes its own function delegation, we'll have make that monadic. If we need a conditional branch, we'll have to make that monadic. Even to implement merge sort we'd wind up with multiple nested anonymous class instance creation expressions. In between scads of braces, parens, brackets, and repetitions of type names like "Func1," we'd have the names of the functions we used to replace all the Java primitives. Lacking syntactic abstraction, the readability of the resulting DSL doesn't scale beyond the simple examples in this blog post.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-2204838646337233655?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/2204838646337233655/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2010/01/continuation-monad_13.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2204838646337233655'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2204838646337233655'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2010/01/continuation-monad_13.html' title='Continuation Monad'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-4659104411103686406</id><published>2009-12-04T15:09:00.011-06:00</published><updated>2009-12-17T16:22:15.064-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='interview'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>TopCoding</title><content type='html'>Since Cross Country Coaches Moss and Brandon had us running sprints, I've realized the importance of short-distance running for long-distance training. So I suppose I've always suspected that TopCoder could be worthwhile, even for those of us who don't care about who can implement bubble sort fastest. Still, it lingered at the bottom of my priority queue. I don't strongly enjoy competition so it wouldn't be first-order fun for me, and I never had trouble thinking of more interesting intellectual challenges than speed-writing fizzbuzz.&lt;br /&gt;&lt;br /&gt;But, &lt;a href="http://niniane.blogspot.com/"&gt;Niniane&lt;/a&gt; told me I should use &lt;a href="http://www.topcoder.com/"&gt;TopCoder&lt;/a&gt;, so I updated that priority. I'm not so haughty as to disregard Coach's advice.&lt;br /&gt;&lt;br /&gt;Well, it turns out I was wrong about TopCoder. It's not only &lt;a href="http://imranontech.com/2007/01/24/using-fizzbuzz-to-find-developers-who-grok-coding/"&gt;fizzbuzz&lt;/a&gt;: the problems with high point values do require some care with algorithm and datastructure selection. It's also not so much of a race as I'd thought. You get no points for an incorrect solution, and even very late answers (like with 12 hours of padding on a 75 minute problem) still get 30% credit.&lt;br /&gt;&lt;br /&gt;I think coding time is just about the least important thing in programming. I'm well aware of the pitfalls of perfectionism and the whole nuanced &lt;a href="http://www.dreamsongs.com/WorseIsBetter.html"&gt;worse is better&lt;/a&gt; concept. But I've very rarely been in a real-world programming situation in which the difference between 15 minutes and 1 hour made any practical difference, aside from labor cost. On the other hand, I've frequently seen that investing a little time in a thoughtful algorithm, test coverage, and an elegant implementation can pay off hugely. Naturally, there are many different assignments to the decision-theoretic parameters here; it's not surprising that TopCoder's scores are so simplistic (and well-suited to their business model).&lt;br /&gt;&lt;br /&gt;I'm TopCoding in Java. Of course, I found myself &lt;a href="http://philip.greenspun.com/research/" title="Greenspun's Tenth Rule of Programming: &amp;quot;Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified bug-ridden slow implementation of half of Common Lisp.&amp;quot;"&gt;Greenspunning&lt;/a&gt; almost immediately. The TopCoder rules explicitly allow code reuse, while penalizing dead code. Still, it feels like cheating to keep a warm Emacs buffer full of map/reduce/foldr/nth/setdefault around. A newbie will have to write and test that code, losing points against the veteran TopCoder for a reason that has nothing to do with skill.&lt;br /&gt;&lt;br /&gt;Having attacked the TopCoder measure of programmer performance, I'm ready to praise it. First, the emphasis on speed accords with interview conditions. The high pressure and short duration of an interview is not ideal for sampling anyone's performance, but it's better than any of the alternatives I know. Second, just as in athletic cross-training, I think there's value in practicing against TopCoder's measure. It works against my natural perfectionism and, because it is so different from my daily routine, it helps me to strengthen in areas that may be seldom used and hence relatively inefficient.&lt;br /&gt;&lt;br /&gt;TopCoder's FAQs are spartan, and the UI is confusing for me. There lots of sequential number ranges and acronyms, and after some fruitless web browsing/searching, I resorted to trial and error to infer their meanings. There aren't many context menus and the key bindings don't follow my OS standards (⌘-C/⌘-X/⌘-V) or the emacs defaults. Try ctrl-C/ctrl-X/ctrl-V for copy/cut/paste. The code editor is terrible, but for interview prep I'm using it in preference to Emacs; I won't have a kill ring or isearch-forward available at the whiteboard, either.&lt;br /&gt;&lt;br /&gt;Nobody is verbally critiquing you as you tap out your TopCoder solutions, so they provide a compiler and a very rudimentary test framework (you can feed your program arbitrary program inputs and see the result along with whatever it wrote to stdout and stderr). If interview prep is your goal, it's probably still a good idea to do some whiteboarding and paper exercises, but I think the TopCoder environment is impoverished enough that you'll have a heightened awareness of your dependence on compiler and test feedback.&lt;br /&gt;&lt;br /&gt;TopCoder is organized as a shallow, fixed-depth hierarchy of chat rooms. The leaf nodes are each associated with three problems of varying difficulty and point value. Problem statements give the general form of input and output along with several example i/o pairs and a sentence or two about each. The lowest-valued problem statements give an algorithm in prose. The statements of higher-valued problems give fewer hints. Also, higher-valued problems tend to require more work (compared to a hypothetical lower-valued problem whose hints have been erased).&lt;br /&gt;&lt;br /&gt;There are competition rooms and practice rooms; different rules governing your actions apply depending on the context. Specifically, in competitions, there are discrete, synchronized phases for coding, code review, and a final non-interactive test round; these activities are concurrent for practice rooms. The &lt;a href="http://www.topcoder.com/tc?module=Static&amp;d1=help&amp;d2=pracArena#practicerooms"&gt;practice rooms are created in anticipation of competitions&lt;/a&gt; and disappear later. It's too bad that the number of rooms and hence the number and variety of available practice problems swings so wildly (from hundreds of problems to 3). There is an &lt;a href="http://www.topcoder.com/tc?module=ProblemArchive"&gt;archive&lt;/a&gt; where you can read the problem statements and look at final stats, but solutions (including your own) and the test oracle are unavailable.&lt;br /&gt;&lt;br /&gt;Unsurprisingly, TopCoder problems are less overtly mathematical than those of &lt;a href="http://projecteuler.net/"&gt;Project Euler&lt;/a&gt;. Whereas my main annoyance with Project Euler was tedium associated with thematic repetition (e.g., &lt;a href="http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf"&gt;primality&lt;/a&gt; &lt;a href="http://terrytao.wordpress.com/2009/08/11/the-aks-primality-test/"&gt;testing&lt;/a&gt;), my main annoyance with TopCoder is tedium associated with parsing the regular language of each problem's input. Project Euler emphasizes mathematical insight and your own preferred blend of elegance/efficiency/creativity/..., while TopCoder emphasizes implementation speed. To be clear, "annoyance" is far from a primary adjective I'd use for either site; I recommend them both for pass-times or as interview preparation.&lt;br /&gt;&lt;br /&gt;The "summary" button in each chat room shows you the list of everyone's who's been there. For each person, you can see overall experience level, rank w/r/t the room, and status/earned points for each problem. By double-clicking on a score, you can read that person's solution and optionally challenge it by providing an input for which you think it fails; TopCoder will execute the solution against your input and either cancel the other person's points and reward you, or punish you for your false allegation by subtracting points from your score.&lt;br /&gt;&lt;br /&gt;My informal survey of submitted solutions shows that most coders leave sloppy mistakes in their final submissions, reflecting the time pressure and lack of incentive for craftsmanship. There are almost no code comments, but the code usually speaks for itself. I usually remove test and debugging code. I don't revise for clarity or efficiency. It is striking to me that apparently I value conceptual focus more highly than most other TopCoders: my solutions tend to explicitly compose well-known algorithms using the modularity features of my chosen language. Being uninterested in competition, I don't plan to destructure my own code, but I wonder if this is a competitive advantage for them? I like to think that under real-world conditions, my approach yields better reusability and code clarity, but I don't really have empirical evidence for that.&lt;br /&gt;&lt;br /&gt;If I have inspired you to try TopCoder, beware that opening any problem statement starts its timer, and closing the problem does not pause or stop the timer. I don't exactly regret having sampled problems from various rooms and point levels before starting, but I also don't care about how I rank relative to anyone else.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-4659104411103686406?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/4659104411103686406/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/12/topcoding.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/4659104411103686406'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/4659104411103686406'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/12/topcoding.html' title='TopCoding'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-2084287167089274925</id><published>2009-10-11T16:04:00.015-05:00</published><updated>2009-10-12T08:23:57.451-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dvcs'/><category scheme='http://www.blogger.com/atom/ns#' term='.net'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='hg'/><category scheme='http://www.blogger.com/atom/ns#' term='cvcs'/><category scheme='http://www.blogger.com/atom/ns#' term='vcs'/><category scheme='http://www.blogger.com/atom/ns#' term='svn'/><category scheme='http://www.blogger.com/atom/ns#' term='dotnet'/><title type='text'>Version numbers and (D)VCS</title><content type='html'>I've spent a lot of time this weekend trying to adapt &lt;a href="http://code.google.com/p/ormar/source/browse/paging/apply-version.nant"&gt;apply-version.nant&lt;/a&gt;, originally written for oopsnet and svn, to ormar and mercurial. It wasn't so easy to find &lt;a href="http://www.selenic.com/pipermail/mercurial/2007-November/015729.html" title="Noob Question: Build number"&gt;guidance from others with similar goals&lt;/a&gt;, so hopefully this post makes the information more accessible.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Definitions&lt;/h1&gt;&lt;br /&gt;The primary purpose of software version numbers is to identify the code. When we distribute to users, the version number gives them a concise reference for use in communicating about bugs and available features.&lt;br /&gt;&lt;br /&gt;A secondary purpose is to define a temporal order on distributions. Versions are normally numeric and monotonically increasing; higher versions are newer and hopefully better than older versions.&lt;br /&gt;&lt;br /&gt;Conventionally, there is at least some human involvement in assigning version numbers. In proprietary software, concerns about sales normally are important. Often times, the technical issue of compatibility is difficult to treat formally, but human judgment is used to &lt;a href="http://apr.apache.org/versioning.html"&gt;encode compatibility information in version numbers&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;But it's also conventional to let the low-order bits of a version number be determined automatically. Whenever build inputs change, the behavior of the software may change, but nobody wants to be bothered incrementing version numbers for routine small changes.&lt;br /&gt;&lt;br /&gt;Besides version numbers, it's common to hear of "build numbers." Sometimes the terms are used interchangeably, but I think it's useful to distinguish between (a) an identifier for the build input and (b) an identifier for the build event. Some people use (b) as a more convenient proxy for (a), and some people apparently really care about (b) itself, although I'm not sure why. Maybe it's because on some teams deployment is part of the build process&lt;a id="why-build-numbers" href="#svn-branch-strategy-footnote"&gt;*&lt;/a&gt;, and it's nice to have a formal record of deployments.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Theory and practice&lt;/h1&gt;&lt;br /&gt;I've used &lt;a href="http://subversion.tigris.org/"&gt;Subversion&lt;/a&gt; for nearly my whole career so far. It's a centralized version control system and sensibly enough it identifies historical events with natural numbers; revision 0 is repository creation. So, svn revision numbers are a very convenient basis for version numbers. Just check for consistency of the wc with a revision from the repository and use that revision number for the low-order version bits. Consistency between wc and repository for svn is a question of (a) uncommitted changes and (b) files or directories within the wc at different revisions.&lt;br /&gt;&lt;br /&gt;This is a bit harder to do with some other centralized version control systems. In &lt;a href="http://en.wikipedia.org/wiki/Source_Code_Control_System"&gt;SCCS&lt;/a&gt;, revision numbers (&lt;a href="http://publib.boulder.ibm.com/infocenter/systems/index.jsp?topic=/com.ibm.aix.genprogc/doc/genprogc/sccs.htm#a106c197c"&gt;SID&lt;/a&gt;s) apply not to repositories but to individual files. Microsoft TFS has SVN-style revision numbers that they call "changeset numbers," but their implementation choices and tools make it difficult and expensive to answer the wc-repository consistency question. But fundamentally, in a cvcs, there's a global clock and that can serve as a basis for version numbering. In every cvcs I've seen, it's practical to use it that way although it might be easier in some cases (svn) and harder in others (tfs).&lt;br /&gt;&lt;br /&gt;For distributed version control systems, we have no global clock. Fundamentally, &lt;a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/pubs.html#time-clocks"&gt;events are temporally ordered only by causal relationships&lt;/a&gt;, so you can really only establish the primary property for version numbers: identifying build inputs. There's no general way to establish the secondary property that allows users to compare version numbers and decide which is newer. And yet, the mercurial project itself produces monotonic version numbers! How do they do it? Apparently by &lt;a href="http://selenic.com/repo/hg/file/25858f9e65e8/setup.py#l100"&gt;manually tagging&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;How important is temporal ordering really? Certainly the most important thing is the capability for repeatable builds. Some DVCS projects have concise identifiers for build inputs; in hg and git we have hashcodes. Unfortunately for those of us on the .NET platform, Microsoft provides only &lt;a href="http://msdn.microsoft.com/en-us/library/system.reflection.assemblyversionattribute.aspx"&gt;4 * 16 bits&lt;/a&gt; of space for &lt;a href="http://msdn.microsoft.com/en-us/library/51ket42z%28VS.71%29.aspx"&gt;version numbers in their assembly metadata&lt;/a&gt; specification. This isn't nearly enough for &lt;a href="http://mercurial.selenic.com/wiki/FAQ#FAQ.2BAC8-Terminology.What_are_revision_numbers.2C_changeset_IDs.2C_and_tags.3F"&gt;hg 160 bit changeset id&lt;/a&gt;s (though it could accommodate the potentially ambiguous 48 bit short form), especially if we want to use one or more of those four fields for encoding compatibility data.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;A common special case&lt;/h1&gt;&lt;br /&gt;There's a very common special case of projects using dvcs for which we can establish an objective order on versions. There's often &lt;a href="http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=summary"&gt;an official repository&lt;/a&gt;, whose event history meets our need.&lt;br /&gt;&lt;br /&gt;Well that's fine in theory, but is it practical? Unfortunately for me, &lt;a href="http://selenic.com/repo/hg/file/25858f9e65e8/mercurial/commands.py#l1670"&gt;hg doesn't allow remote queries of a given repository's local timestamps ("revision numbers")&lt;/a&gt;. I hope that's due to an efficiency trade-off and not just a pedantic effort ("these revision numbers aren't guaranteed to match those in your clone; use changeset ids instead!").&lt;br /&gt;&lt;br /&gt;The good news is that &lt;a href="http://www.selenic.com/pipermail/mercurial/2007-November/015738.html"&gt;in hg, revision numbers consistency is preserved under clone and pull operations&lt;/a&gt;. If you commit in your repository, you may irreconcilably lose consistency, but as long as you abstain from making changes you and the other repo will agree on the bijective function between revision numbers and changeset ids. So my plan for .NET assembly versioning in my googlecode hg repositories is to use a pristine clone for official builds and at least one separate clone for feature development and bug fixes.&lt;br /&gt;&lt;br /&gt;&lt;div style="font-size: 80%"&gt;&lt;br /&gt;&lt;a id="svn-branch-strategy-footnote"&gt;*&lt;/a&gt;For the IT web apps I've worked on, we had automated deployment as part of our CI routine, but we were satisfied to have an svn branch per deployment target. Actually, we had one svn branch per deployment target equivalence class representative. Really we had a small number of user communities (e.g., end-users, beta-testers, trainees, programmers), and we had a branch for each of them and the server clusters for each. &lt;a href="#why-build-numbers"&gt;(back)&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-2084287167089274925?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/2084287167089274925/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/10/version-numbers-and-dvcs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2084287167089274925'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2084287167089274925'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/10/version-numbers-and-dvcs.html' title='Version numbers and (D)VCS'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-896987147591302425</id><published>2009-10-01T17:52:00.021-05:00</published><updated>2009-10-03T17:07:47.247-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flyfishing'/><category scheme='http://www.blogger.com/atom/ns#' term='montana'/><category scheme='http://www.blogger.com/atom/ns#' term='vacation'/><category scheme='http://www.blogger.com/atom/ns#' term='wyoming'/><category scheme='http://www.blogger.com/atom/ns#' term='fishing'/><category scheme='http://www.blogger.com/atom/ns#' term='yellowstone'/><title type='text'>What I did on my summer vacation</title><content type='html'>I spent the second week of September 2009 in Yellowstone Country. This is my trip report.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh5.ggpht.com/_p8seU-Q7z8I/SsVWw_wInUI/AAAAAAAAAUM/BLq1ulBR2Wc/s640/IMG_2880.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 640px; height: 480px;" src="http://lh5.ggpht.com/_p8seU-Q7z8I/SsVWw_wInUI/AAAAAAAAAUM/BLq1ulBR2Wc/s640/IMG_2880.JPG" border="0" alt="Sean, Mike, Aubrey, Colin" /&gt;&lt;/a&gt;My traveling companions were my parents, my brother, and his girlfriend. We met at BZN, where we rented a minivan for the drive to &lt;a href="http://yellowstonehouse.com/"&gt;Yellowstone House&lt;/a&gt;. It was a rainy afternoon but between showers we caught glimpses of that &lt;a href="http://photo.net/photodb/photo?photo_id=5535550"&gt;signature Montana light&lt;/a&gt; that plays over the foothills and dramatically highlights the mountain peaks. We ate dinner at the &lt;a href="http://chicohotsprings.com/"&gt;Chico&lt;/a&gt; Dining Room, always a safe bet for fine dining. Yellowstone House has wifi and a PowerMac G4.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVYfcAIfPI/AAAAAAAAAVg/3UqdRQ2d0iQ/s640/IMG_2899.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 640px; height: 480px;" src="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVYfcAIfPI/AAAAAAAAAVg/3UqdRQ2d0iQ/s640/IMG_2899.JPG" border="0" alt="Barb et al." /&gt;&lt;/a&gt;&lt;br /&gt;For the next two days we went flyfishing with &lt;a href="http://grossenbacherguides.com/"&gt;Grossenbacher Guides&lt;/a&gt; Bill, Bo, Brad Ehrnman, Brett Seng, and Brian Grossenbacher.&lt;br /&gt;&lt;br /&gt;The thing to love or hate about flyfishing is that it emphasizes technique. The motion of the fly is determined not by its own weight (as it is in baitfishing) by the weight distributed along your line; there is physical complexity here that corresponds to a high degree of choice in casting. Beyond that, there is a pretty large space of materials and configurations in the flies, and the trout discriminate taking into account the season, time of day, and past experience. I'm a novice, but more experienced flyfishermen explicitly use ecological knowledge in addition to recent observations of insect activity and fish feeding patterns. So, I can recommend flyfishing if you enjoy skill acquisition.&lt;br /&gt;&lt;br /&gt;Someone once expressed surprise that I'd participate in an activity that involves cruelty to fish. Well, I've not noticed a strong trend either way among baitfishermen, but every flyfishermen I've encountered has expressed concern for the health and well-being of the fish. They remove barbs from hooks in order to minimize injury during catch-and-release fishing, they take care to handle fish gently without damaging gills, and they release carefully. Those practices satisfy my moral compass.&lt;br /&gt;&lt;br /&gt;We were not the only ones enjoying the river those days; we also observed eagles, ospreys, otters, and mergansers fishing.&lt;br /&gt;&lt;br /&gt;On Monday we ate at the &lt;a href="http://www.pinecreeklodgemontana.com/"&gt;Pine Creek Lodge&lt;/a&gt;, and it was good. I will say though that the cinnamon fajita tastes like it sounds.&lt;br /&gt;&lt;br /&gt;On Tuesday we drove to the North entrance of &lt;a href="http://www.nps.gov/yell/"&gt;Yellowstone National Park&lt;/a&gt; at Gardiner, MT. On the way through Mammoth Hot Springs to Old Faithful Inn we saw elk, mule deer, bison, and a black bear but very sadly no moose. We stopped off to hike up the &lt;a href="http://www.nps.gov/archive/yell/tours/livecams/mtwashburn/index.htm"&gt;Mount Washburn Fire Lookout&lt;/a&gt;, where we saw many chipmunks, a couple of marmots, and some big horn sheep. Unfortunately Dad stopped about a half mile from the summit due to pain in his knees.&lt;br /&gt;&lt;br /&gt;At &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVZv8S-U0I/AAAAAAAAAWo/oWmg5FMu2_o/s640/IMG_2925.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 640px; height: 480px;" src="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVZv8S-U0I/AAAAAAAAAWo/oWmg5FMu2_o/s640/IMG_2925.JPG" border="0" alt="Old Faithful Inn" /&gt;&lt;/a&gt;Old Faithful Inn we had drinks and bar food at the bar and then dinner in the Dining Room. I was disappointed that the &lt;a href="http://lcweb2.loc.gov/cgi-bin/displayPhoto.pl?path=/pnp/habshaer/wy/wy0000/wy0093/photos&amp;topImages=174614pr.jpg&amp;topLinks=174614pv.jpg,174614pu.tif&amp;title=34.%20%20THE%20CROW%27S%20NEST.%20IN%20THE%20EARLY%20YEARS%20OF%20THE%20INN%20MUSICIANS%20SAT%20AND%20PLAYED%20FOR%20THE%20GUESTS%20IN%20THE%20LOBBY%20BELOW.%20THE%20EARTHQUAKE%20IN%201959%20CAUSED%20SOME%20STRUCTURAL%20DAMAGE%20AND%20NOW%20THE%20CROW%27S%20NEST%20IS%20NOT%20ACCESSIBLE%20TO%20THE%20PUBLIC.%3Cbr%3EHABS%20WYO,20-YELNAP,1-34&amp;displayProfile=0"&gt;Crow's Nest is off-limits&lt;/a&gt;, but I did see Old Faithful erupt. Our rooms were in a renovated section (early 1990s), so we had private bathrooms.&lt;br /&gt;&lt;br /&gt;On Wednesday we hiked half way to &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVaQs8zB9I/AAAAAAAAAXA/8Ybuh_ypqO0/s512/IMG_2931.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 384px; height: 512px;" src="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVaQs8zB9I/AAAAAAAAAXA/8Ybuh_ypqO0/s512/IMG_2931.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;Artist Point along the Grand Canyon of the Yellowstone. Dad stayed back. The canyonlands supposedly harbor moose, but not surprisingly we didn't see them along the heavily trafficked trail. We drove to view a petrified tree and then we drove on to the Lamar Valley. There we photographed a &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh5.ggpht.com/_p8seU-Q7z8I/SsVai55YsSI/AAAAAAAAAXQ/FXdRf3TC9dk/s640/IMG_2935.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 640px; height: 480px;" src="http://lh5.ggpht.com/_p8seU-Q7z8I/SsVai55YsSI/AAAAAAAAAXQ/FXdRf3TC9dk/s640/IMG_2935.JPG" border="0" alt="Bison at Lamar Valley" /&gt;&lt;/a&gt;buffalo at close range and did some fishing in the Lamar River, a tributary of the Yellowstone. At one point I misjudged the depth of the river and waded into chest-high water, drowning my phone.&lt;br /&gt;&lt;br /&gt;We tried to eat at Helen's, home of the infamous Hateful Burger, but it was closed and for sale. Instead we ate at &lt;a href="http://maps.google.com/places/us/livingston/s-11th-st/206/-zona-rosa"&gt;Zona Rosa&lt;/a&gt; in Livingston, where we had superb Latin American food at very reasonable prices. They were new at that time and did not accept credit cards or serve alcoholic beverages. Nearby landmarks include an enormous Taco John's sign and a car/truck wash.&lt;br /&gt;&lt;br /&gt;Thursday we did guided fishing on the Madison River. The fish are generally larger and smarter than those of the Yellowstone. We caught very few whitefish, but a number of &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVbxg3LAGI/AAAAAAAAAYU/8y4kaDw08-k/s640/IMG_2948.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 640px; height: 480px;" src="http://lh3.ggpht.com/_p8seU-Q7z8I/SsVbxg3LAGI/AAAAAAAAAYU/8y4kaDw08-k/s640/IMG_2948.JPG" border="0" alt="Large Brown Trout" /&gt;&lt;/a&gt;large trout. We ate at the Chico Saloon, which is definitely not served by the same kitchen as the dining room, while we observed some sporting event on TV.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://lh4.ggpht.com/_p8seU-Q7z8I/SsVW16R7a7I/AAAAAAAAAUQ/yV3LKdTTCw8/s640/IMG_2881.JPG"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 640px; height: 480px;" src="http://lh4.ggpht.com/_p8seU-Q7z8I/SsVW16R7a7I/AAAAAAAAAUQ/yV3LKdTTCw8/s640/IMG_2881.JPG" border="0" alt="Yellowstone House living room" /&gt;&lt;/a&gt;Friday we hung around Yellowstone House, doing a little fishing from the bank of the Yellowstone River and reading and puzzle-solving. We had dinner again at Chico Hot Springs Dining Room, and then back to Yellowstone House for our last night in Montana.&lt;br /&gt;&lt;br /&gt;We didn't see any grizzly bears this year, and I still have yet to see wild wolves or moose. Maybe next time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-896987147591302425?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/896987147591302425/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/10/what-i-did-on-my-summer-vacation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/896987147591302425'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/896987147591302425'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/10/what-i-did-on-my-summer-vacation.html' title='What I did on my summer vacation'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_p8seU-Q7z8I/SsVWw_wInUI/AAAAAAAAAUM/BLq1ulBR2Wc/s72-c/IMG_2880.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-190798191079317856.post-2876535076165244753</id><published>2009-09-13T20:39:00.015-05:00</published><updated>2009-12-04T16:33:08.403-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='msft'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='exception'/><category scheme='http://www.blogger.com/atom/ns#' term='bug'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>The trust relationship between the primary domain and the trusted domain failed.</title><content type='html'>Over the past month, I've spent a couple of hours puzzling over a bug in the implementation of Microsoft's &lt;a href="http://msdn.microsoft.com/en-us/library/system.security.principal.windowsprincipal.isinrole.aspx"&gt;System.Security.Principal.WindowsPrincipal.IsInRole&lt;/a&gt;. I want to say something about the general problem and then I'll talk specifically about this problem instance and how I worked around it.&lt;br /&gt;&lt;br /&gt;The general form of the problem is something I often cited in code reviews at Anheuser-Busch. Now I believe that Microsoft owes its success much more to salesmanship than  engineering ability, but this is a high-profile product with years of history in the field, so I'm surprised the bug has survived so long. I guess it's worth publishing my advice (which I'm sure did not originate with me):&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Throw or propagate exceptions at the right level of abstraction. The Java language designers were on to something with &lt;a href="http://java.sun.com/docs/books/jls/third_edition/html/exceptions.html#11.2"&gt;checked exceptions&lt;/a&gt;: exceptions really can be regarded as part of your API (see also &lt;a href="http://www.c2.com/cgi/wiki?CheckedException"&gt;criticism&lt;/a&gt; linked at c2). If you have a method &lt;code&gt;String Profile.GetSetting(String)&lt;/code&gt; with two implementations, one of which uses ADO.NET for relational database storage while the other uses &lt;code&gt;System.IO.File&lt;/code&gt; for filesystem storage, then callers should never see &lt;code&gt;System.Data.Common.DbException&lt;/code&gt;s. If, as the maintainer of &lt;code&gt;Profile.GetSetting&lt;/code&gt;, you anticipate ADO.NET or System.IO exceptions, then you should catch them and throw exceptions of some more appropriate type(s). It's OK and usually a good idea to use what you catch to initialize the &lt;code&gt;InnerException&lt;/code&gt; property of what you throw: that's useful diagnostic information. But, you should make the effort to wrap the exceptions you propagate when doing so mitigates a &lt;a href="http://www.joelonsoftware.com/articles/LeakyAbstractions.html"&gt;leaky abstraction&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Use distinct exception types for distinct equivalence classes of conditions you think callers might reasonably treat in different ways. I think it's helpful to think about &lt;a href="http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html"&gt;Lisp conditions&lt;/a&gt;, which generalize exceptions, when you make design decisions involving errors.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Document the exceptions you throw.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;And without further ado, the specifics:&lt;br /&gt;&lt;div id="bugzilla-body"&gt;&lt;h1&gt;Bug      4541   &lt;/h1&gt;    &lt;table class="bugfields"&gt;            &lt;tbody&gt;&lt;tr&gt;     &lt;th&gt;Summary:&lt;/th&gt;     &lt;td colspan="3"&gt;The trust relationship between the primary domain and the trusted domain failed.&lt;/td&gt;   &lt;/tr&gt;      &lt;tr&gt;       &lt;th&gt;Product:&lt;/th&gt;       &lt;td&gt;REDACTED       &lt;/td&gt; &lt;th class="rightcell"&gt;Reporter:&lt;/th&gt;       &lt;td&gt;Foy, Sean &lt;sean.foy@redacted&gt;&lt;/sean.foy@redacted&gt;&lt;/td&gt;     &lt;/tr&gt; &lt;tr&gt;     &lt;th&gt;Component:&lt;/th&gt;     &lt;td&gt;triage&lt;/td&gt;&lt;th class="rightcell"&gt;Assignee:&lt;/th&gt;       &lt;td&gt;Foy, Sean &lt;sean.foy@redacted&gt;&lt;/sean.foy@redacted&gt;&lt;/td&gt;   &lt;/tr&gt;      &lt;tr&gt;       &lt;th&gt;Status:&lt;/th&gt;       &lt;td&gt;ASSIGNED                &lt;/td&gt; &lt;td&gt;&lt;br /&gt;&lt;/td&gt;       &lt;td&gt;&lt;br /&gt;&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;th&gt;Severity:&lt;/th&gt;       &lt;td class="bz_blocker"&gt;blocker       &lt;/td&gt; &lt;th class="rightcell"&gt;CC:&lt;/th&gt;       &lt;td&gt;michael.jorgensen@REDACTED     &lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;       &lt;th&gt;Priority:&lt;/th&gt;       &lt;td class="bz_P3"&gt;P3       &lt;/td&gt; &lt;td&gt;&lt;br /&gt;&lt;/td&gt;       &lt;td&gt;&lt;br /&gt;&lt;/td&gt;     &lt;/tr&gt; &lt;tr&gt;     &lt;th&gt;Version:&lt;/th&gt;     &lt;td&gt;unspecified&lt;/td&gt;&lt;td&gt;&lt;br /&gt;&lt;/td&gt;       &lt;td&gt;&lt;br /&gt;&lt;/td&gt;   &lt;/tr&gt;&lt;tr&gt;     &lt;th&gt;Hardware:&lt;/th&gt;     &lt;td&gt;All&lt;/td&gt;&lt;td&gt;&lt;br /&gt;&lt;/td&gt;       &lt;td&gt;&lt;br /&gt;&lt;/td&gt;   &lt;/tr&gt;&lt;tr&gt;     &lt;th&gt;OS:&lt;/th&gt;     &lt;td&gt;All&lt;/td&gt;&lt;td&gt;&lt;br /&gt;&lt;/td&gt;       &lt;td&gt;&lt;br /&gt;&lt;/td&gt;   &lt;/tr&gt;        &lt;tr&gt;         &lt;th&gt;URL:&lt;/th&gt;         &lt;td colspan="3"&gt;             &lt;a href="http://tahaze-ww07.redacted/REDACTED/requests/19791117T120000?requestor=sean.foy@REDACTED"&gt;http://tahaze-ww07.REDACTED/REDACTED/requests/19791117T120000?requestor=sean.foy@REDACTED&lt;/a&gt;         &lt;/td&gt;       &lt;/tr&gt; &lt;tr&gt;     &lt;th&gt;Whiteboard:&lt;/th&gt;     &lt;td colspan="3"&gt;&lt;br /&gt;&lt;/td&gt;   &lt;/tr&gt;               &lt;tr&gt;         &lt;th&gt;Time tracking:&lt;/th&gt;         &lt;td colspan="3"&gt;           &lt;table class="timetracking"&gt;             &lt;tbody&gt;&lt;tr&gt;               &lt;th&gt;Orig. Est.&lt;/th&gt;               &lt;th&gt;Actual Hours&lt;/th&gt;               &lt;th&gt;Hours Worked&lt;/th&gt;               &lt;th&gt;Hours Left&lt;/th&gt;               &lt;th&gt;%Complete&lt;/th&gt;               &lt;th&gt;Gain&lt;/th&gt;             &lt;/tr&gt;             &lt;tr&gt;               &lt;td&gt;8.0               &lt;/td&gt;               &lt;td&gt;1.1               &lt;/td&gt;               &lt;td&gt;1.0&lt;/td&gt;               &lt;td&gt;0.1               &lt;/td&gt;               &lt;td&gt;90               &lt;/td&gt;               &lt;td&gt;6.9               &lt;/td&gt;             &lt;/tr&gt;           &lt;/tbody&gt;&lt;/table&gt;         &lt;/td&gt;       &lt;/tr&gt;&lt;tr&gt;     &lt;th&gt;Deadline:&lt;/th&gt;     &lt;td&gt;&lt;br /&gt;&lt;/td&gt;&lt;td&gt;&lt;br /&gt;&lt;/td&gt;       &lt;td&gt;&lt;br /&gt;&lt;/td&gt;   &lt;/tr&gt;           &lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;div class="bz_comment bz_first_comment"&gt;        &lt;div class="bz_first_comment_head"&gt;            &lt;span class="bz_comment_number"&gt;           &lt;a name="c0" href="http://www.blogger.com/post-create.g?blogID=190798191079317856#c0"&gt;Description&lt;/a&gt;         &lt;/span&gt;          &lt;span class="bz_comment_user"&gt;&lt;span class="vcard"&gt; &lt;span class="fn"&gt;Foy, Sean&lt;/span&gt; &lt;/span&gt;         &lt;/span&gt;          &lt;span class="bz_comment_user_images"&gt;         &lt;/span&gt;          &lt;span class="bz_comment_time"&gt;           2009-09-13 19:08:54 CDT         &lt;/span&gt;       &lt;/div&gt;    &lt;span style="whitespace: pre-wrap;" class="bz_comment_text"&gt;Steps to reproduce:&lt;br /&gt;0. logout if you're not using Windows Authentication.&lt;br /&gt;1. create a request&lt;br /&gt;2. follow the link to the request from the assignment page&lt;br /&gt;3. authenticate (implicitly or otherwise) using Windows Authentication&lt;br /&gt;Expected results: request edit page&lt;br /&gt;Actual results: authorization (IsInRole predicate evaluation on a&lt;br /&gt;WindowsPrincipal) fails.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;System.SystemException: The trust relationship between the primary domain and the trusted domain failed.&lt;br /&gt;&lt;br /&gt;at&lt;br /&gt;System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection&lt;br /&gt;sourceAccounts, Boolean&amp;amp; someFailed)&lt;br /&gt;at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection&lt;br /&gt;sourceAccounts, Type targetType, Boolean&amp;amp; someFailed)&lt;br /&gt;at System.Security.Principal.NTAccount.Translate(IdentityReferenceCollection&lt;br /&gt;sourceAccounts, Type targetType, Boolean forceSuccess)&lt;br /&gt;at System.Security.Principal.WindowsPrincipal.IsInRole(String role)&lt;br /&gt;at MVCUtils.IsInRole(String role) in&lt;br /&gt;c:\documents-and-settings\sean.foy\my-documents\REDACTED\MvcUtils.cs:line 218&lt;br /&gt;at REDACTED.REDACTED.IsInRole(String rolename) in&lt;br /&gt;c:\documents-and-settings\sean.foy\my-documents\REDACTED\REDACTED.cs:line 284&lt;br /&gt;at REDACTED.Viewee03e5938d284b4d944019fc1e9c8130.RenderViewLevel0() in&lt;br /&gt;c:\documents-and-settings\sean.foy\my-documents\REDACTED\Views\Request\requestform.spark:line&lt;br /&gt;20&lt;br /&gt;at REDACTED.Viewee03e5938d284b4d944019fc1e9c8130.RenderView(TextWriter writer) in&lt;br /&gt;c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET&lt;br /&gt;Files\REDACTED\930b9661\fd62837e49004099a1fddf1e5288ae22-1.cs:line 1240&lt;br /&gt;at Spark.Web.Mvc.SparkView.Render(ViewContext viewContext, TextWriter&lt;br /&gt;writer)&lt;br /&gt;at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext&lt;br /&gt;controllerContext, ActionResult actionResult)&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.ControllerActionInvoker.&amp;lt;&amp;gt;c__DisplayClass11.&amp;lt;invokeactionresultwithfilters&amp;gt;b__e()&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter&lt;br /&gt;filter, ResultExecutingContext preContext, Func`1 continuation)&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.ControllerActionInvoker.&amp;lt;&amp;gt;c__DisplayClass11.&amp;lt;&amp;gt;c__DisplayClass13.&amp;lt;invokeactionresultwithfilters&amp;gt;b__10()&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext&lt;br /&gt;controllerContext, IList`1 filters, ActionResult actionResult)&lt;br /&gt;at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext&lt;br /&gt;controllerContext, String actionName)&lt;br /&gt;at System.Web.Mvc.Controller.ExecuteCore()&lt;br /&gt;at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext&lt;br /&gt;requestContext)&lt;br /&gt;at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext)&lt;br /&gt;at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext)&lt;br /&gt;at&lt;br /&gt;System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext&lt;br /&gt;httpContext)&lt;br /&gt;at&lt;br /&gt;System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()&lt;br /&gt;at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&amp;amp;&lt;br /&gt;completedSynchronously)&lt;br /&gt;&lt;br /&gt;REDACTED, Version=1.0.4835.11, Culture=neutral, PublicKeyToken=null version&lt;br /&gt;1.0.4835.11&lt;/invokeactionresultwithfilters&gt;&lt;/invokeactionresultwithfilters&gt;&lt;/code&gt;&lt;br /&gt;     &lt;/div&gt;   &lt;div class="bz_comment"&gt;        &lt;div class="bz_comment_head"&gt;            &lt;span class="bz_comment_number"&gt;           &lt;a name="c1" href="http://www.blogger.com/post-create.g?blogID=190798191079317856#c1"&gt;Comment 1&lt;/a&gt;         &lt;/span&gt;          &lt;span class="bz_comment_user"&gt;&lt;span class="vcard"&gt;&lt;span class="fn"&gt;Foy, Sean&lt;/span&gt; &lt;/span&gt;         &lt;/span&gt;          &lt;span class="bz_comment_user_images"&gt;         &lt;/span&gt;          &lt;span class="bz_comment_time"&gt;           2009-09-13 20:08:54 CDT         &lt;/span&gt;       &lt;/div&gt;     &lt;br /&gt;    Additional hours worked: 1.0   &lt;span style="whitespace: pre-wrap" class="bz_comment_text"&gt;There are no documented exceptions for WindowsPrincipal.IsInRole and although Google finds many reports of this exception, the most promising resolutions are vaguely described patches from Microsoft for SharePoint. But empirically I've found that I get reasonable answers for queries of the form&lt;br /&gt;  p.IsInRole(@"domain\group") and&lt;br /&gt;  p.IsInRole(@"group@domain")&lt;br /&gt;where the domain name can be any non-empty string. But I get this exception for queries of the form&lt;br /&gt;  p.IsInRole(@"group").&lt;br /&gt;Evidently IsInRole expects a domain name and throws this exception when none is present.&lt;/span&gt;     &lt;/div&gt;  &lt;/div&gt;&lt;br /&gt;&lt;br /&gt;And here's the patch:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;Index: MvcUtils.cs&lt;br /&gt;===================================================================&lt;br /&gt;--- MvcUtils.cs    (revision 4851)&lt;br /&gt;+++ MvcUtils.cs    (working copy)&lt;br /&gt;@@ -1,6 +1,7 @@&lt;br /&gt; using System;&lt;br /&gt; using System.Collections.Generic;&lt;br /&gt; using System.Linq;&lt;br /&gt;+using System.Security.Principal;&lt;br /&gt; using System.Text;&lt;br /&gt; using System.Text.RegularExpressions;&lt;br /&gt; using System.Web;&lt;br /&gt;@@ -214,8 +215,22 @@&lt;br /&gt;         return result.ToString();&lt;br /&gt;     }&lt;br /&gt; &lt;br /&gt;+    public static Boolean IsInRole(IPrincipal p, String role) {&lt;br /&gt;+        try {&lt;br /&gt;+            return p.Identity.IsAuthenticated &amp;amp;amp;&amp;amp;amp; p.IsInRole(role);&lt;br /&gt;+        }&lt;br /&gt;+        catch (SystemException e) {&lt;br /&gt;+            // see bug 4541&lt;br /&gt;+            if (!e.Message.StartsWith("The trust relationship between the primary domain and the trusted domain failed.")) {&lt;br /&gt;+                throw;&lt;br /&gt;+            }&lt;br /&gt;+            &lt;br /&gt;+            return false;&lt;br /&gt;+        }&lt;br /&gt;+    }&lt;br /&gt;+&lt;br /&gt;     public static Boolean IsInRole(String role) {&lt;br /&gt;-        return HttpContext.Current.User.Identity.IsAuthenticated &amp;amp;amp;&amp;amp;amp; HttpContext.Current.User.IsInRole(role);&lt;br /&gt;+        return IsInRole(HttpContext.Current.User, role);&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt;Index: TestMvcUtils.cs&lt;br /&gt;===================================================================&lt;br /&gt;--- TestMvcUtils.cs    (revision 4851)&lt;br /&gt;+++ TestMvcUtils.cs    (working copy)&lt;br /&gt;@@ -175,6 +175,32 @@&lt;br /&gt;                 r =&amp;gt; authorized.IsInRole(r)));&lt;br /&gt;     }&lt;br /&gt; &lt;br /&gt;+    private System.Security.Principal.WindowsPrincipal getWindowsPrincipal() {&lt;br /&gt;+        return&lt;br /&gt;+            new System.Security.Principal.WindowsPrincipal(&lt;br /&gt;+                System.Security.Principal.WindowsIdentity.GetCurrent());&lt;br /&gt;+&lt;br /&gt;+    }&lt;br /&gt;+&lt;br /&gt;+    [Test]&lt;br /&gt;+    public void WindowsPrincipalIsInRoleExpectsDomain() {&lt;br /&gt;+        var p = getWindowsPrincipal();&lt;br /&gt;+        try {&lt;br /&gt;+            p.IsInRole("whatever");&lt;br /&gt;+            Assert.Fail("maybe we can simplify MvcUtils.IsInRole after all");&lt;br /&gt;+        }&lt;br /&gt;+        catch (SystemException) {&lt;br /&gt;+            //expected&lt;br /&gt;+        }&lt;br /&gt;+    }&lt;br /&gt;+&lt;br /&gt;+    [Test]&lt;br /&gt;+    public void IsInRoleToleratesAbsenceOfDomains() {&lt;br /&gt;+        var p = getWindowsPrincipal();&lt;br /&gt;+        MVCUtils.IsInRole(p, @"domain\whatever");&lt;br /&gt;+        MVCUtils.IsInRole(p, "whatever@domain");&lt;br /&gt;+    }&lt;br /&gt;+&lt;br /&gt;     public Mock&amp;lt;httpcontextbase&amp;gt; mockContext(String applicationRoot, String requestUrl) {&amp;lt;/httpcontextbase&amp;gt;&lt;br /&gt;         var appRoot = new Uri(applicationRoot);&lt;br /&gt;         var ctx = new Mock&amp;lt;httpcontextbase&amp;gt;();&amp;lt;/httpcontextbase&amp;gt;&lt;br /&gt;Index: Web.config&lt;br /&gt;===================================================================&lt;br /&gt;--- Web.config    (revision 4851)&lt;br /&gt;+++ Web.config    (working copy)&lt;br /&gt;@@ -12,8 +12,8 @@&lt;br /&gt;     &amp;lt;add key="ActiveDirectoryForestName" value="REDACTED"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;     &amp;lt;add key="ActiveDirectoryUserName" value="REDACTED\svc.tahaze.websql"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;     &amp;lt;add key="ActiveDirectoryPassword" value="REDACTED"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;-    &amp;lt;add key="map-to-role-msl" value="REDACTED_Form_Editors"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;-    &amp;lt;add key="map-to-role-admin" value="REDACTED_Admins"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;+    &amp;lt;add key="map-to-role-msl" value="REDACTED\REDACTED_Form_Editors"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;+    &amp;lt;add key="map-to-role-admin" value="REDACTED\REDACTED_Admins"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;     &amp;lt;add key="MVCAuthnz.CookieAuthenticationHttpModule.signingKey" value="REDACTED"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;     &amp;lt;add key="seanfoy.oopsnet.assemblyQualifiedName" value="seanfoy.oopsnet.ASPNetErrorHandler,seanfoy.oopsnet"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;     &amp;lt;add key="seanfoy.oopsnet.handlerName" value="handleError"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;@@ -84,7 +84,6 @@&lt;br /&gt;     &lt;br /&gt;   &lt;br /&gt;   --&amp;gt;&lt;br /&gt;-&lt;br /&gt;   &amp;lt;system.web&amp;gt;&amp;lt;/system.web&amp;gt;&lt;br /&gt;     &amp;lt;httpmodules&amp;gt;&amp;lt;/httpmodules&amp;gt;&lt;br /&gt;       &amp;lt;add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing"&amp;gt;&amp;lt;/add&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-2876535076165244753?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/2876535076165244753/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/09/trust-relationship-between-primary.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2876535076165244753'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/2876535076165244753'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/09/trust-relationship-between-primary.html' title='The trust relationship between the primary domain and the trusted domain failed.'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-919430370192409257</id><published>2009-08-02T20:43:00.021-05:00</published><updated>2009-12-04T16:32:26.850-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='orm'/><category scheme='http://www.blogger.com/atom/ns#' term='nhibernate'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='rdbms'/><title type='text'>Paging for Mutable Data</title><content type='html'>Many web applications provide segmented result sets in response to queries. This approach can conserve server-side effort when users decide to reformulate a query or use partial results rather than actually attending to all the results. Even if a user will eventually demand the complete set of results, paging can smooth the demand/time graph, lowering the peaks.&lt;br /&gt;&lt;br /&gt;It's easy to find library support for paging, but it's rare to find good support for paging mutable data. Typically the library provides a set of results given an integer offset and page size. Another way to characterize this functionality is to say that the library does integer arithmetic. I don't object to procedural abstraction, but it seems to me that this is not only simple but simplistic.&lt;br /&gt;&lt;br /&gt;We can always enumerate our datasets, but I think that users don't normally care about specific offsets. I for one normally care about the whole set of results that satisfy my query. Or at least I think I do, until I see from the results that my query spec wasn't quite right. Anyway, I think we users normally formulate queries in terms of the problem domain, not in terms of sequences of integers. I don't normally care about the eleventh or the twenty sixth result because of its ordinal rank; I just want the next element of my dataset.&lt;br /&gt;&lt;br /&gt;So where's the problem? We know that we can setup a one-to-one correspondence between integers and countable data sets, so why not use convenient integers to specify pages? As long as the data are immutable, or users are tolerant of certain inaccuracies, we can use integers to identify pages.&lt;br /&gt;&lt;br /&gt;But, in general the data might be changing even as users peruse the results. Suppose I specify a query q, which predicate is satisfied by the following ordered result elements at time 0: &amp;lt;A, C, E, G, I, J&amp;gt;. At time 1, F begins to satisfy q so the result set becomes &amp;lt;A, C, E, F, G, I, J&amp;gt;. If my page size is 2 and I was looking at the second page at time 0, what should I see when I proceed to the third page at time 2? Do I see G and I because results_t2[(page - 1) * size] = results_t2[4] identifies G? Would the apparent duplicate result in that case be confusing or just annoying? Should I see I and J because I started viewing at t_0 and therefore my page request should be interpreted as results_t0[4] = I?&lt;br /&gt;&lt;br /&gt;Suppose an item I've already seen is deleted as I page through results. Will I see the item that would have been first on the next page at all?&lt;br /&gt;&lt;br /&gt;If we're going to avoid anomalous disappearances and false duplicates, we can't rely on naively recomputing the function from integers to results on-demand. Perhaps we can store history and use an (page-number, timestamp) pair to index our pages.&lt;br /&gt;&lt;br /&gt;If the cost of storing history is too great, there are compromises available aside from allowing both false apparent duplicates and omissions of elements that have existed throughout. We can allow repetition in order to avoid erroneous omission, or we can allow omission to avoid false repetition. Finally, if we disallow duplicates, we can use observable properties of the elements themselves as page indices. Often, (apparent) duplicates are undesirable anyway:&lt;br /&gt;&lt;br /&gt;&lt;table&gt;  &lt;caption&gt;Summaries are often preferred over raw data&lt;/caption&gt;  &lt;thead&gt;    &lt;tr&gt;      &lt;th&gt;A&lt;/th&gt;      &lt;th&gt;B&lt;/th&gt;    &lt;/tr&gt;  &lt;/thead&gt;  &lt;tbody&gt;    &lt;tr&gt;      &lt;td&gt;pepperoni&lt;/td&gt;      &lt;td&gt;pepperoni: 4&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;mushroom&lt;/td&gt;      &lt;td&gt;mushroom: 2&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;pepperoni&lt;/td&gt;      &lt;td&gt;cheese: 1&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;mushroom&lt;/td&gt;      &lt;td&gt;&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;pepperoni&lt;/td&gt;      &lt;td&gt;&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;cheese&lt;/td&gt;      &lt;td&gt;&lt;/td&gt;    &lt;/tr&gt;    &lt;tr&gt;      &lt;td&gt;pepperoni&lt;/td&gt;      &lt;td&gt;&lt;/td&gt;    &lt;/tr&gt;  &lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;In my experience, the most common scenario involves concurrent usage, mutable underlying data, and two laws of user expectation: (1) if an item satisfied the query predicate continuously from before the query was issued to after the user reached the last page of results, the user should see the item and (2) one unchanging item should appear at most once in the pages.&lt;br /&gt;&lt;br /&gt;Where these requirements are satisfiable, we'd like to encapsulate and reuse the implementation details. The interface should work in domain terms: given a data element, total order relation, and window size as input, it could provide the next &lt;code&gt;n&lt;/code&gt; elements.&lt;br /&gt;&lt;br /&gt;The main idea here is pretty simple, both in conception and in implementation. Part of the motivation for this blog post though was to discuss the details of client interface design and testing.&lt;br /&gt;&lt;br /&gt;Here's my first test design for the core functionality:&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void paging() {&lt;br /&gt;    const int count =&lt;br /&gt;        (int)(&lt;br /&gt;              // first page: stable + deleted&lt;br /&gt;              (pageSize + 1.0) * 3.0 / (1 + 1 + 0) +&lt;br /&gt;              // second page: stable + deleted + ins&lt;br /&gt;              (pageSize + 1.0) * 3.0 / (1 + 1 + 0) +&lt;br /&gt;              // third page: stable + ins&lt;br /&gt;              (pageSize + 1.0) * 3.0 / (1 + 1 + 0));&lt;br /&gt;    var items = Enumerable.Range(0, count).ToList();&lt;br /&gt;    var stable =&lt;br /&gt;        Enumerable.Where(&lt;br /&gt;            items,&lt;br /&gt;            x =&gt; x % 3 == 0).ToList();&lt;br /&gt;    var inserting =&lt;br /&gt;        Enumerable.Where(&lt;br /&gt;            items,&lt;br /&gt;            x =&gt; x % 3 == 1).ToList();&lt;br /&gt;    var deleting =&lt;br /&gt;        Enumerable.Where(&lt;br /&gt;            items,&lt;br /&gt;            x =&gt; x % 3 == 2).ToList();&lt;br /&gt;    var attendance =&lt;br /&gt;        Enumerable.ToDictionary(&lt;br /&gt;            items,&lt;br /&gt;            x =&gt; x,&lt;br /&gt;            x =&gt; 0);&lt;br /&gt;    try {&lt;br /&gt;        foreach (var i in stable) {&lt;br /&gt;            persist(i);&lt;br /&gt;        }&lt;br /&gt;        foreach (var i in deleting) {&lt;br /&gt;            persist(i);&lt;br /&gt;        }&lt;br /&gt;        int timer = 0;&lt;br /&gt;        foreach (var p in getPages()) {&lt;br /&gt;            foreach (var i in p) {&lt;br /&gt;                ++attendance[i];&lt;br /&gt;            }&lt;br /&gt;            if (timer == 1) {&lt;br /&gt;                foreach (var ins in inserting) {&lt;br /&gt;                    persist(ins);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            else if (timer == 2) {&lt;br /&gt;                foreach (var del in deleting) {&lt;br /&gt;                    delete(del);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            ++timer;&lt;br /&gt;        }&lt;br /&gt;        Assert.IsTrue(timer &gt; 3, &amp;quot;There were too few pages in this test to see both kinds of concurrency anomaly.&amp;quot;);&lt;br /&gt;        Assert.IsTrue(&lt;br /&gt;            Enumerable.All(&lt;br /&gt;                stable,&lt;br /&gt;                x =&gt; attendance[x] == 1),&lt;br /&gt;            &amp;quot;Every item that is present throughout the process should be seen exactly once.&amp;quot;);&lt;br /&gt;        Assert.IsTrue(&lt;br /&gt;            Enumerable.Any(&lt;br /&gt;                deleting,&lt;br /&gt;                x =&gt; attendance[x] == 0),&lt;br /&gt;            &amp;quot;Some item deleted during this test should not have been seen. This may indicate eager retrieval of pages, which is isn't feasible in general (for large datasets).&amp;quot;);&lt;br /&gt;        Assert.IsFalse(&lt;br /&gt;            Enumerable.Any(&lt;br /&gt;                items,&lt;br /&gt;                x =&gt; attendance[x] &gt; 1),&lt;br /&gt;            &amp;quot;No item should be seen twice.&amp;quot;);&lt;br /&gt;        // it's OK if items inserted during the test are seen&lt;br /&gt;    }&lt;br /&gt;    finally {&lt;br /&gt;        foreach (var i in items) {&lt;br /&gt;            delete(i);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;For me, the best thing about this code is its strong focus on two key axioms. I partition all possible inputs in such a way as to demonstrate those, and then I arrange to draw samples from those partitions in what I hope is pretty unobtrusive way. I check that I've covered the necessary cases and then I show that the axioms are true for my sample data. getPages simulates sequential uses of the paging abstraction by a client:&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;protected override IEnumerable&amp;lt;IEnumerable&amp;lt;int&amp;gt;&amp;gt; getPages() {&lt;br /&gt;    const int pageSize = 10;&lt;br /&gt;    LocalItem memento = null;&lt;br /&gt;    IEnumerable&amp;lt;LocalItem&amp;gt; p;&lt;br /&gt;    do {&lt;br /&gt;        var slicedCriteria =&lt;br /&gt;            Potpourri.Page&amp;lt;LocalItem&amp;gt;.page(&lt;br /&gt;                DetachedCriteria.For(typeof(LocalItem)),&lt;br /&gt;                new [] {&amp;quot;itemNumber&amp;quot;},&lt;br /&gt;                memento);&lt;br /&gt;        p = LocalItem.SlicedFindAll(0, pageSize + 1, slicedCriteria);&lt;br /&gt;        memento =&lt;br /&gt;            Enumerable.LastOrDefault(p);&lt;br /&gt;        yield return&lt;br /&gt;            Potpourri.slice(&lt;br /&gt;                Enumerable.Select(&lt;br /&gt;                    p,&lt;br /&gt;                    i =&amp;gt; elementToInt(i)),&lt;br /&gt;                0,&lt;br /&gt;                pageSize);&lt;br /&gt;    } while (Enumerable.Count(p) &amp;gt; pageSize);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Details: in real life this code runs against shared relational database instances, but for testing purposes I execute against SQLite for isolation. This test occurs in an abstract &lt;code&gt;TestFixture&lt;/code&gt; with two concrete subclasses, one of whose getPages method is shown above.&lt;br /&gt;&lt;br /&gt;The other uses the raw NHibernate windowing primitives in the naive way; that test class is marked with the &lt;code&gt;IgnoreAttribute&lt;/code&gt; because that approach is doomed. I actually started by writing that test case as an executable demonstration of the motivation for the less obvious technique.&lt;br /&gt;&lt;br /&gt;Here's my second test design. I think on balance it's actually less readable than the first attempt. I wonder how many people would agree?&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void insertionSansDuplicates() {&lt;br /&gt;    insertion(fetchPageByValueEquality);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;[Test]&lt;br /&gt;[ExpectedException(typeof(DuplicateDetectedException))]&lt;br /&gt;public void insertionAnomalies() {&lt;br /&gt;    insertion(fetchPageByOffset);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void deletionSansSkips() {&lt;br /&gt;    deletion(fetchPageByValueEquality);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;[Test]&lt;br /&gt;[ExpectedException(typeof(AnomalyDetectedException))]&lt;br /&gt;public void deletionAnomalies() {&lt;br /&gt;    deletion(fetchPageByOffset);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Again, I wrote working test code for both straightforward, broken use of primitives and for the approach that suits my needs.&lt;br /&gt;&lt;br /&gt;Here's how I address the two kinds of trouble that can result from trying to use offsets for paging in the face of concurrent modification:&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;private class AnomalyDetectedException : Exception {}&lt;br /&gt;private class DuplicateDetectedException : AnomalyDetectedException {}&lt;br /&gt;private void insertion(FetchPage fetchPage) {&lt;br /&gt;    var q = DetachedCriteria.For&amp;lt;Beer&amp;gt;();&lt;br /&gt;    var O = new [] {&amp;quot;ABV&amp;quot;, &amp;quot;BrewDate&amp;quot;, &amp;quot;Variety&amp;quot;};&lt;br /&gt;    Paging.AddOrder(q, O);&lt;br /&gt;    var seenit = new HashSet&amp;lt;Beer&amp;gt;();&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            var page =&lt;br /&gt;                fetchPage(&lt;br /&gt;                    s,&lt;br /&gt;                    q,&lt;br /&gt;                    O,&lt;br /&gt;                    null,&lt;br /&gt;                    pageSize + 1);&lt;br /&gt;            foreach (Beer i in Enumerable.TakeWhile(page, (elt, i) =&amp;gt; i &amp;lt; pageSize)) {&lt;br /&gt;                if (seenit.Contains(i)) throw new DuplicateDetectedException();&lt;br /&gt;                seenit.Add(i);&lt;br /&gt;            }&lt;br /&gt;            var first = (Beer)page[0];&lt;br /&gt;            var nouveau =&lt;br /&gt;                new Beer {&lt;br /&gt;                    Variety = &amp;quot;not&amp;quot; + first.Variety,&lt;br /&gt;                    BrewDate = first.BrewDate,&lt;br /&gt;                    ABV = first.ABV / 2};&lt;br /&gt;            s.Save(nouveau);&lt;br /&gt;            s.Flush();&lt;br /&gt;            page =&lt;br /&gt;                fetchPage(s, q, O, Enumerable.Last(page), pageSize);&lt;br /&gt;            foreach (Beer i in page) {&lt;br /&gt;                if (seenit.Contains(i)) throw new DuplicateDetectedException();&lt;br /&gt;                seenit.Add(i);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private void deletion(FetchPage fetchPage) {&lt;br /&gt;    var q = DetachedCriteria.For&amp;lt;Beer&amp;gt;();&lt;br /&gt;    var O = new [] {&amp;quot;ABV&amp;quot;, &amp;quot;BrewDate&amp;quot;, &amp;quot;Variety&amp;quot;};&lt;br /&gt;    Paging.AddOrder(q, O);&lt;br /&gt;    var expected = new HashSet&amp;lt;Object&amp;gt;();&lt;br /&gt;    var actual = new HashSet&amp;lt;Object&amp;gt;();&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            var page = fetchPage(s, q, O, null, pageSize);&lt;br /&gt;            foreach (var i in page) expected.Add(i);&lt;br /&gt;            foreach (var i in page) actual.Add(i);&lt;br /&gt;            foreach (var i in fetchPage(s, q, O, Enumerable.Last(page), pageSize)) {&lt;br /&gt;                expected.Add(i);&lt;br /&gt;            }&lt;br /&gt;            s.Delete(page[0]);&lt;br /&gt;            s.Flush();&lt;br /&gt;            foreach (var i in fetchPage(s, q, O, Enumerable.Last(page), pageSize)) {&lt;br /&gt;                actual.Add(i);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            actual.SymmetricExceptWith(expected);&lt;br /&gt;            if (0 != actual.Count) throw new AnomalyDetectedException();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The test data are much less carefully prepared:&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;[TestFixtureSetUp]&lt;br /&gt;public void setup() {&lt;br /&gt;    Persistence.rebuildDB();&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            Assert.That(s.Linq&amp;lt;Bottle&amp;gt;().Count(), Is.GreaterThan(pageSize));&lt;br /&gt;            Assert.That(s.Linq&amp;lt;Beer&amp;gt;().Count(), Is.GreaterThan(pageSize));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That rebuildDB method isn't particularly geared towards the paging problem. It's really intended for use with a broader class of NHibernate-related addons.&lt;br /&gt;&lt;br /&gt;I also have boundary test cases:&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void empty() {&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            var max =&lt;br /&gt;                Queryable.Max(&lt;br /&gt;                    s.Linq&amp;lt;Bottle&amp;gt;(),&lt;br /&gt;                    i =&amp;gt; i.Serial);&lt;br /&gt;            var source =&lt;br /&gt;                DetachedCriteria.For(&lt;br /&gt;                    typeof(Bottle));&lt;br /&gt;            Assert.IsFalse(&lt;br /&gt;                Enumerable.Any(&lt;br /&gt;                    Paging.Page(&lt;br /&gt;                        s,&lt;br /&gt;                        source,&lt;br /&gt;                        new [] {&amp;quot;Serial&amp;quot;},&lt;br /&gt;                        new Bottle { Serial = max + 1 },&lt;br /&gt;                        pageSize)));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void singleton() {&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            var max =&lt;br /&gt;                Queryable.Max(&lt;br /&gt;                    s.Linq&amp;lt;Bottle&amp;gt;(),&lt;br /&gt;                    i =&amp;gt; i.Serial);&lt;br /&gt;            var source =&lt;br /&gt;                DetachedCriteria.For(&lt;br /&gt;                    typeof(Bottle)).&lt;br /&gt;                AddOrder(Order.Asc(&amp;quot;Serial&amp;quot;));&lt;br /&gt;            Assert.AreEqual(&lt;br /&gt;                1,&lt;br /&gt;                Enumerable.Count(&lt;br /&gt;                    Paging.Page(&lt;br /&gt;                        s,&lt;br /&gt;                        source,&lt;br /&gt;                        new [] {&amp;quot;Serial&amp;quot;},&lt;br /&gt;                        new Bottle { Serial = max },&lt;br /&gt;                        pageSize)));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;When there's no concurrent data modification, we get equivalent results with the simpler, direct use of NHibernate primitives as with the more complicated use of my paging abstraction:&lt;br /&gt;&lt;br /&gt;&lt;code class="prettyprint lang-cs"&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void naturals() {&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            var Q =&lt;br /&gt;                Enumerable.Select(&lt;br /&gt;                    Enumerable.Range(0, 2),&lt;br /&gt;                    x =&amp;gt; DetachedCriteria.For&amp;lt;Bottle&amp;gt;().AddOrder(Order.Asc(&amp;quot;Serial&amp;quot;))).ToArray();&lt;br /&gt;            Assert.AreEqual(&lt;br /&gt;                Q[0].GetExecutableCriteria(s).SetMaxResults(pageSize).List&amp;lt;Bottle&amp;gt;(),&lt;br /&gt;                Paging.Page&amp;lt;Bottle&amp;gt;(s, Q[1], new [] {"Serial"}, null, pageSize));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;[Test]&lt;br /&gt;public void beers() {&lt;br /&gt;    using (var factory = Persistence.makeSessionFactory()) {&lt;br /&gt;        using (var s = factory.OpenSession()) {&lt;br /&gt;            var O = new [] {&amp;quot;ABV&amp;quot;, &amp;quot;BrewDate&amp;quot;, &amp;quot;Variety&amp;quot;};&lt;br /&gt;            // ICriterion instances are mutable and don't&lt;br /&gt;            // expose a copy constructor or clone method,&lt;br /&gt;            // so we must make two criteria, just alike.&lt;br /&gt;            var Q =&lt;br /&gt;                Enumerable.Select(&lt;br /&gt;                    Enumerable.Range(0, 2),&lt;br /&gt;                    x =&gt; DetachedCriteria.For&amp;lt;Beer&amp;gt;()).ToArray();&lt;br /&gt;            foreach (var q in Q) {&lt;br /&gt;                Paging.AddOrder(q, O);&lt;br /&gt;            }&lt;br /&gt;            Assert.AreEqual(&lt;br /&gt;                Q[0].GetExecutableCriteria(s).SetMaxResults(pageSize).List&amp;lt;Beer&amp;gt;(),&lt;br /&gt;                Paging.Page&lt;Beer&gt;(s, Q[1], O, null, pageSize));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;I welcome your comments and criticism. Please see &lt;a href="http://code.google.com/p/ormar/"&gt;ormar&lt;/a&gt; for details or to publish your own code review.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-919430370192409257?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/919430370192409257/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/08/paging-for-mutable-data.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/919430370192409257'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/919430370192409257'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/08/paging-for-mutable-data.html' title='Paging for Mutable Data'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-8127824958230740915</id><published>2009-08-02T13:12:00.025-05:00</published><updated>2011-01-01T21:04:46.471-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bugzilla'/><category scheme='http://www.blogger.com/atom/ns#' term='latex'/><category scheme='http://www.blogger.com/atom/ns#' term='ccnet'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='infrastructure'/><category scheme='http://www.blogger.com/atom/ns#' term='cruisecontrol.net'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='rake'/><category scheme='http://www.blogger.com/atom/ns#' term='svn'/><title type='text'>vogon fodder</title><content type='html'>&lt;p&gt;In this post, I show-and-tell about automated paper-pushing.&lt;/p&gt;&lt;p&gt;I spent considerable effort at work last week building something that is &lt;a href="http://www.google.com/#q=drucker+useless+efficiently"&gt;minimally useful&lt;/a&gt;. I am preoccupied by the conflict between the demands of &lt;a href="http://philip.greenspun.com/ancient-history/professionalism-for-software-engineers"&gt;professionalism&lt;/a&gt; and the idiotic/cynical mockery of professionalism that is usual (though not universal) in large organizations. But I've &lt;a href="http://en.wikipedia.org/wiki/Maslow%27s_hierarchy_of_needs"&gt;got to eat&lt;/a&gt; and I should focus on problems I can &lt;a href="http://www.cs.virginia.edu/~robins/YouAndYourResearch.html"&gt;realistically solve&lt;/a&gt;. For now, let's accept the demands of management without question and focus on how to satisfy the demands at minimum cost to human life. In the words of Steve Jobs:&lt;/p&gt;&lt;blockquote cite="http://www.folklore.org/StoryView.py?project=Macintosh&amp;amp;story=Saving_Lives.txt"&gt;Well, let's say you can shave 10 seconds off of the boot time. Multiply that by five million users and thats 50 million seconds, every single day. Over a year, that's probably dozens of lifetimes. So if you make it boot ten seconds faster, you've saved a dozen lives. That's really worth it, don't you think?&lt;/blockquote&gt;&lt;p&gt;Your manager emails you an MSWord document and asks you fill it out once per software deployment. It should list every affected "object" and indicate when if ever "User" has been notified, when and who if ever "BAM" has approved, and it should reference the other paperwork associated with the project. What do you do?&lt;/p&gt;&lt;p&gt;Clearly, the right answer involves CI and LaTeX. In my case: svn, ccnet, Bugzilla, python, xslt, ruby, LaTeX, perl, and pdf.&lt;/p&gt;&lt;p&gt;We've been using CI with SVN to do deployments since 2004. There's a branch for each deployment target, and a CI project associated with each of those branches. The CI builds usually correspond to a single commit, but we don't insist on that; when commits come fast enough we go for speed over precision in who's "on the hook" for breaking changes. So the first problem is to find the union of affected "objects" over the set of &lt;a href="http://subversion.tigris.org/"&gt;svn&lt;/a&gt; commits in the deployment.&lt;/p&gt;&lt;p&gt;The term "object" is taken from the argot of the RPGers, who are the oldest and largest group of software people at our company. For them, "object" often refers to a &lt;a href="http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/cl/crtsqlrpgi.htm"&gt;printed tabular summary of data&lt;/a&gt; but more generally can refer to a program for any purpose. The usual custom outside the RPG tribe is to list as "objects" ui elements, but sometimes instead OO classes or source files are listed. It's not worth being too careful about this, because the real goal is pro forma compliance with a vague demand for documentation. Users basically want release notes, and engineers already get precise, accurate, and efficient answers to technical questions from svn and Bugzilla. The form really just needs to exist and, &lt;a href="http://garage.freebsd.pl/pulp.html"&gt;at a glance, to appear normal&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Our CI system &lt;a href="http://confluence.public.thoughtworks.org/display/CCNET/Welcome+to+CruiseControl.NET"&gt;CruiseControl.NET&lt;/a&gt; has a feature called &lt;a href="http://confluence.public.thoughtworks.org/display/CCNET/Modification+Writer+Task"&gt;modification writer&lt;/a&gt; that renders metadata about a build to XML. This provides a list of added, deleted, and textually-modified files for each commit that's new since the last build:&lt;/p&gt;&lt;pre class="brush:xml"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;br /&gt;&amp;lt;ArrayOfModification xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&amp;gt;&lt;br /&gt;&amp;lt;Modification&amp;gt;&lt;br /&gt;  &amp;lt;Type&amp;gt;Modified&amp;lt;/Type&amp;gt;&lt;br /&gt;  &amp;lt;FileName&amp;gt;ccnet-self.config&amp;lt;/FileName&amp;gt;&lt;br /&gt;  &amp;lt;FolderName&amp;gt;/ccnet&amp;lt;/FolderName&amp;gt;&lt;br /&gt;  &amp;lt;ModifiedTime&amp;gt;2009-07-24T10:23:08.162158-05:00&amp;lt;/ModifiedTime&amp;gt;&lt;br /&gt;  &amp;lt;UserName&amp;gt;sean.foy&amp;lt;/UserName&amp;gt;&lt;br /&gt;  &amp;lt;ChangeNumber&amp;gt;4413&amp;lt;/ChangeNumber&amp;gt;&lt;br /&gt;  &amp;lt;Version /&amp;gt;&lt;br /&gt;  &amp;lt;Comment&amp;gt;whitespace&lt;br /&gt;[Bug 50,51,3783]&amp;lt;/Comment&amp;gt;&lt;br /&gt;  &amp;lt;IssueUrl&amp;gt;https://bugzilla.thcg.net/buglist.cgi?bug_id=50,51,3783&amp;lt;/IssueUrl&amp;gt;&lt;br /&gt;&amp;lt;/Modification&amp;gt;&lt;br /&gt;&amp;lt;/ArrayOfModification&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We integrated SVN and &lt;a href="http://bugzilla.mozilla.org/"&gt;Bugzilla&lt;/a&gt; in 2004, and so since that time we've been able to correlate commits with bugs. This gives us informal commentary about the motivations and the design considerations for each commit, plus metadata including who was involved in the process, how much time each of us spent, whether we paired or got code review, whether dbas got involved, who approved deployment, etc. Well, at least in principle. Actually, only one or two projects have ever really taken advantage of these capabilities. But the point is that this metadata fits the &lt;em&gt;purported&lt;/em&gt; goal of our paperwork, and it's useful and easily accessible, and it exists for at least one project. It's the best we can do, and it's pretty good. So we've got to use it.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt; is a fairly pleasant language to work in and nobody's &lt;a href="http://en.wikiquote.org/wiki/Lisp_programming_language#Parentheses"&gt;terrified by its syntax&lt;/a&gt;. It also has great libraries for screen-scraping. Accordingly, we used a python program to augment ccnet's modifications xml. We read the ccnet xml and use the &lt;a href="http://guest@tortoisesvn.tigris.org/svn/tortoisesvn/trunk/doc/issuetrackers.txt"&gt;bugtraq&lt;/a&gt; standard to write back the set of relevant bugs:&lt;/p&gt;&lt;pre class="brush:xml"&gt;&amp;lt;ArrayOfModification&amp;gt;&lt;br /&gt;&amp;lt;Modification&amp;gt;&lt;br /&gt;  &amp;lt;Type&amp;gt;Modified&amp;lt;/Type&amp;gt;&lt;br /&gt;  &amp;lt;FileName&amp;gt;ccnet-self.config&amp;lt;/FileName&amp;gt;&lt;br /&gt;  &amp;lt;FolderName&amp;gt;/ccnet&amp;lt;/FolderName&amp;gt;&lt;br /&gt;  &amp;lt;ModifiedTime&amp;gt;2009-07-24T10:23:08.162158-05:00&amp;lt;/ModifiedTime&amp;gt;&lt;br /&gt;  &amp;lt;UserName&amp;gt;sean.foy&amp;lt;/UserName&amp;gt;&lt;br /&gt;  &amp;lt;ChangeNumber&amp;gt;4413&amp;lt;/ChangeNumber&amp;gt;&lt;br /&gt;  &amp;lt;Version /&amp;gt;&lt;br /&gt;  &amp;lt;Comment&amp;gt;whitespace&lt;br /&gt;[Bug 50,51,3783]&amp;lt;/Comment&amp;gt;&lt;br /&gt;  &amp;lt;IssueUrl&amp;gt;https://bugzilla.thcg.net/buglist.cgi?bug_id=50,51,3783&amp;lt;/IssueUrl&amp;gt;&lt;br /&gt;  &amp;lt;database /&amp;gt;&lt;br /&gt;  &amp;lt;flags&amp;gt;&lt;br /&gt;    &amp;lt;flag id="1882" name="QCApproved" setter="michael.jorgensen@covidien.com" status="+" /&amp;gt;&lt;br /&gt;  &amp;lt;/flags&amp;gt;&lt;br /&gt;&amp;lt;/Modification&amp;gt;&lt;br /&gt;&amp;lt;/ArrayOfModification&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now we have all the answers we need to fill out our worksheet in XML. We just need to produce MSWord output (&lt;a href="http://www.tbray.org/ongoing/When/200x/2008/03/02/On-OOXML"&gt;good luck&lt;/a&gt;). Well, MSWord probably isn't really necessary. That's what's expected, but also we should keep in mind that the goal is pro forma compliance. This document's real use is to ward off auditors, so it just needs to exist and not look unusual. So, we want to typeset XML? Obviously we should consider DocBook, XSL-FO, and &lt;a href="http://www.latex-project.org/"&gt;LaTeX&lt;/a&gt;. I looked into XSL-FO c. 2001 and DocBook c. 2002, but I know LaTeX best. I wanted to spend less than 4 hours on this little project, so I was satisfied to use LaTeX.&lt;/p&gt;&lt;p&gt;For processing XML, you could do worse than &lt;a href="http://www.w3.org/TR/xslt20"&gt;XSLT&lt;/a&gt;. It can be particularly nice as a declarative template specification: "make my input look like this". So, I used xslt to process the bz-augmented ccnet modifications xml into LaTeX:&lt;/p&gt;&lt;pre class="brush:xml; collapse: true"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;br /&gt;&amp;lt;xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:smf="http://seanfoy.thcg.net/xslt"&amp;gt;&lt;br /&gt;&amp;lt;xsl:output method="text" /&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:param name="asr"&amp;gt;application service request number&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="builddate"&amp;gt;the date these changes became effective&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="trunkdeploymenttarget"&amp;gt;unc path&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="trunkdbserver"&amp;gt;hostname&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="trunkdbname"&amp;gt;dbname&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="productiondeploymenttarget"&amp;gt;unc path&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="productiondbserver"&amp;gt;hostname&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&amp;lt;xsl:param name="productiondbname"&amp;gt;dbname&amp;lt;/xsl:param&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:function name="smf:latexlit"&amp;gt;&lt;br /&gt;  &amp;lt;xsl:param name="raw" /&amp;gt;&lt;br /&gt;  &amp;lt;xsl:value-of select="fn:replace(fn:replace($raw, '([$&amp;amp;amp;%#_{}~^\\])', '\\$1'), '\\\\', '\$\\backslash\$')" /&amp;gt;&lt;br /&gt;&amp;lt;/xsl:function&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;xsl:template match="@*|node()"&amp;gt;&lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&amp;lt;xsl:template match="/"&amp;gt;&lt;br /&gt;  &amp;lt;xsl:text&amp;gt;&lt;br /&gt;\documentclass{article}&lt;br /&gt;&lt;br /&gt;\usepackage{hyperref}&lt;br /&gt;\usepackage{longtable}&lt;br /&gt;\usepackage{latexsym}&lt;br /&gt;&lt;br /&gt;\usepackage[document]{ragged2e}&lt;br /&gt;\usepackage{wordlike}&lt;br /&gt;\renewcommand{\familydefault}{phv} % Arial&lt;br /&gt;\sloppy&lt;br /&gt;\makeatletter&lt;br /&gt;\renewcommand{\subsection}{\@startsection%&lt;br /&gt;{subsection}%&lt;br /&gt;{1}&lt;br /&gt;{0em}&lt;br /&gt;{-\baselineskip}%&lt;br /&gt;{0.5\baselineskip}%&lt;br /&gt;{\bfseries\LARGE\sffamily}}&lt;br /&gt;\makeatother&lt;br /&gt;&lt;br /&gt;\author{Sean Foy}&lt;br /&gt;\title{Sample}&lt;br /&gt;&lt;br /&gt;\begin{document}&lt;br /&gt;\section*{Internet/Intranet Production Move Request}&lt;br /&gt;\textbf{ASR /Track-it WO\#:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($asr)" /&amp;gt;&amp;lt;xsl:text&amp;gt;\hfill\textbf{Move Date:}&lt;br /&gt;&lt;br /&gt;\textbf{Requestor/ User Notification Sent:} $\Box$ \textbf{Dated:}&lt;br /&gt;&lt;br /&gt;\subsection*{Object(s): $\Box$Web \qquad $\Box$WebMethods (WM)}&lt;br /&gt;\begin{longtable}{|l|l|}\hline&lt;br /&gt;  &amp;lt;/xsl:text&amp;gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="ArrayOfModification/Modification"&amp;gt;&lt;br /&gt;    &amp;lt;xsl:value-of select="smf:latexlit(FileName)" /&amp;gt;&lt;br /&gt;    &amp;lt;xsl:text&amp;gt;&amp;amp;amp; -r&amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit(ChangeNumber)" /&amp;gt;&lt;br /&gt;    &amp;lt;xsl:if test="fn:not(fn:empty(IssueUrl))"&amp;gt;&lt;br /&gt;      &amp;lt;xsl:text&amp;gt; (&amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit(IssueUrl)" /&amp;gt;&amp;lt;xsl:text&amp;gt;)&amp;lt;/xsl:text&amp;gt;&lt;br /&gt;    &amp;lt;/xsl:if&amp;gt;&lt;br /&gt;    &amp;lt;xsl:text&amp;gt; \\&amp;lt;/xsl:text&amp;gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&amp;gt;&lt;br /&gt;  &amp;lt;xsl:text&amp;gt;\hline&lt;br /&gt;\end{longtable}&lt;br /&gt;&lt;br /&gt;\subsection*{DB Object(s):}&lt;br /&gt;\begin{longtable}{|l|l|}\hline&lt;br /&gt;  &amp;lt;/xsl:text&amp;gt;&lt;br /&gt;  &amp;lt;xsl:for-each select="ArrayOfModification/Modification[database]"&amp;gt;&lt;br /&gt;    &amp;lt;xsl:value-of select="smf:latexlit(FileName)" /&amp;gt;&lt;br /&gt;    &amp;lt;xsl:text&amp;gt;&amp;amp;amp; -r&amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit(ChangeNumber)" /&amp;gt;&lt;br /&gt;    &amp;lt;xsl:if test="fn:not(fn:empty(IssueUrl))"&amp;gt;&lt;br /&gt;      &amp;lt;xsl:text&amp;gt; (&amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit(IssueUrl)" /&amp;gt;&amp;lt;xsl:text&amp;gt;)&amp;lt;/xsl:text&amp;gt;&lt;br /&gt;    &amp;lt;/xsl:if&amp;gt;&lt;br /&gt;    &amp;lt;xsl:text&amp;gt; \\&amp;lt;/xsl:text&amp;gt;&lt;br /&gt;  &amp;lt;/xsl:for-each&amp;gt;&lt;br /&gt;  &amp;lt;xsl:text&amp;gt;&lt;br /&gt;    \hline&lt;br /&gt;\end{longtable}&lt;br /&gt;&lt;br /&gt;\subsection*{Deployment Servers:}&lt;br /&gt;\begin{tabular}{|l|l|l|}\hline&lt;br /&gt;     &amp;amp;amp;&lt;br /&gt;     Development&amp;amp;amp;&lt;br /&gt;     Production\\\hline&lt;br /&gt;     Web:&amp;amp;amp;&lt;br /&gt;     \textbf{UNC:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($trunkdeploymenttarget)" /&amp;gt;&amp;lt;xsl:text&amp;gt;&amp;amp;amp;&lt;br /&gt;     \textbf{UNC:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($productiondeploymenttarget)" /&amp;gt;&amp;lt;xsl:text&amp;gt;\\\hline&lt;br /&gt;     DB:&amp;amp;amp;&lt;br /&gt;     \textbf{Server:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($trunkdbserver)" /&amp;gt;&amp;lt;xsl:text&amp;gt; \textbf{Database:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($trunkdbname)" /&amp;gt;&amp;lt;xsl:text&amp;gt;&amp;amp;amp;&lt;br /&gt;     \textbf{Server:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($productiondbserver)" /&amp;gt;&amp;lt;xsl:text&amp;gt; \textbf{Database:} &amp;lt;/xsl:text&amp;gt;&amp;lt;xsl:value-of select="smf:latexlit($productiondbname)" /&amp;gt;&amp;lt;xsl:text&amp;gt;\\\hline&lt;br /&gt;     WM:&amp;amp;amp;&lt;br /&gt;     \textbf{UNC:}            &amp;amp;amp;&lt;br /&gt;     \textbf{UNC:}            \\\hline&lt;br /&gt;\end{tabular}&lt;br /&gt;&lt;br /&gt;\rule{\textwidth}{0.25em}&lt;br /&gt;&lt;br /&gt;\textbf{Move Approved by BAM}:&lt;br /&gt;&lt;br /&gt;Signature: \underline{\makebox[2in][l]{}}&lt;br /&gt;&lt;br /&gt;Date: \underline{\makebox[1in][l]{}}&lt;br /&gt;&lt;br /&gt;\end{document}&lt;br /&gt;  &amp;lt;/xsl:text&amp;gt;&lt;br /&gt;&amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&amp;lt;/xsl:stylesheet&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;LaTeX is based on Don Knuth's TeX typesetting system. It's serious business: Turing-complete and designed specifically for the needs of the world's most demanding typographical experts.&lt;/p&gt;&lt;p&gt;For a while I thought about merging the bug and svn data here in LaTeX. My TeX-hacking skills are very weak now so I had in mind the python package. As I escaped for XSL the escape for LaTeX of the escape for Python of the LaTeX output I wanted, I thought I was probably making a mistake. LaTeX is great for typesetting, and capable of general computation, but it's awkward to express the heavy-lifting there (even in an embedded language). The python package is probably better used tactically, for simple comprehensions and the like -- not for elaborating significantly on the structure of the LaTeX markup.&lt;/p&gt;&lt;p&gt;We must compile LaTeX to obtain our output (normally PostScript or PDF). I use a perl script, &lt;a href="http://ctan.tug.org/tex-archive/support/latexmk/"&gt;latexmk&lt;/a&gt;, to manage the multi-phase compilation process and keep track of cleaning up the intermediate products.&lt;/p&gt;&lt;p&gt;So we want to combine (((xml and Buzilla) by python to xml) and xsl) by xslt to LaTeX by latexmk to PDF). We need to process our inputs and intermediate products in topological order; this is a build process. I've used make before, but it's a bit crufty. I prefer NAnt, whose XML syntax is more explicit and discoverable. From what I've seen of &lt;a href="http://rake.rubyforge.org/"&gt;rake&lt;/a&gt;, I like it better than NAnt. NAnt's a priori boundary between its implementation and your build specification is confining. Rake draws no distinction between itself and your build specification.&lt;/p&gt;&lt;pre class="brush:ruby"&gt;# -*- mode: ruby -*-&lt;br /&gt;# Render modifications as beautiful&lt;br /&gt;# (or hideous!) PDF&lt;br /&gt;#&lt;br /&gt;# sample invocation:&lt;br /&gt;# rake latexmk=vendor/latexmk/latexmk.pl modifications.pdf&lt;br /&gt;&lt;br /&gt;require 'rake/clean'&lt;br /&gt;require 'rexml/document'&lt;br /&gt;require 'rexml/xpath'&lt;br /&gt;&lt;br /&gt;transform = "#{ENV['transform'] || 'vendor/saxonb9-1-0-7n/bin/Transform.exe'}"&lt;br /&gt;latexmk = "#{ENV['latexmk'] || 'vendor/latexmk/latexmk.pl'}"&lt;br /&gt;&lt;br /&gt;modifications_xml = "#{ENV['modifications_xml'] || '../Artifacts/modifications.xml'}"&lt;br /&gt;&lt;br /&gt;task :default =&amp;gt; 'modifications.pdf'&lt;br /&gt;&lt;br /&gt;task :clean =&amp;gt; ['clobberhelper'] do&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;task :clobber =&amp;gt; ['clobberhelper'] do&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;def contains_clobber(ts)&lt;br /&gt;ts.each { |i|&lt;br /&gt;  return true if (i == 'clobber') || contains_clobber(Rake::Task[i].prerequisites)&lt;br /&gt;}&lt;br /&gt;return false&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;task :clobberhelper do |t|&lt;br /&gt;if contains_clobber(Rake::application().top_level())&lt;br /&gt;  sh latexmk, '-C'&lt;br /&gt;else&lt;br /&gt;  sh latexmk, '-c'&lt;br /&gt;end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;file 'modifications.tex' =&amp;gt; [modifications_xml, 'modifications.xsl'] do |t|&lt;br /&gt;xsl_params = {}&lt;br /&gt;begin&lt;br /&gt;  f = File.new('modifications.xsl')&lt;br /&gt;  doc = REXML::Document.new(f)&lt;br /&gt;  REXML::XPath.each(doc.root, 'xsl:param', {'xsl' =&amp;gt; 'http://www.w3.org/1999/XSL/Transform'}) do |elt|&lt;br /&gt;    xsl_params[elt.attributes['name']] = ENV[elt.attributes['name']] || elt.text&lt;br /&gt;  end&lt;br /&gt;ensure&lt;br /&gt;  f.close unless f.nil?&lt;br /&gt;end&lt;br /&gt;xsl_args = xsl_params.inject([]){ |accum, (key, value)|&lt;br /&gt;  accum &amp;lt;&amp;lt; "#{key}=#{value}"&lt;br /&gt;}&lt;br /&gt;sh transform, "-s:#{modifications_xml}", '-xsl:modifications.xsl', '-o:modifications.tex', *xsl_args&lt;br /&gt;end&lt;br /&gt;CLEAN.include('modifications.tex')&lt;br /&gt;&lt;br /&gt;file 'modifications.pdf' =&amp;gt; ['modifications.tex'] do |t|&lt;br /&gt;sh latexmk, '-pdf', 'modifications.tex'&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;If you don't know about rake, one point of confusion is that rake clean conventionally means "remove intermediates" while rake clobber means "remove intermediate and final outputs".&lt;/p&gt;&lt;p&gt;With my use of &lt;code&gt;latexmk&lt;/code&gt;, I encountered a technical problem in the implementation of the standard clean and clobber rake tasks: &lt;code&gt;latexmk&lt;/code&gt; needs the .tex file to decide what to clean up, but rake wants to clean (removing the .tex) before it clobbers. I needed to let &lt;code&gt;latexmk&lt;/code&gt; do its job before clean, and &lt;code&gt;latexmk&lt;/code&gt; needs to know whether to clean up the PDF or just the intermediates. My solution was to add a dependency &lt;code&gt;clobberhelper&lt;/code&gt; for the clean task to call &lt;code&gt;latexmk&lt;/code&gt;'s cleanup routine. &lt;code&gt;clobberhelper&lt;/code&gt; searches rake's dependency graph for &lt;code&gt;clobber in order to decide how to call &lt;/code&gt;latexmk.&lt;/p&gt;&lt;p&gt;Finally, we have a corporate commitment to .NET and so most of our projects benefit from &lt;a href="http://nant.sourceforge.net/"&gt;NAnt&lt;/a&gt;'s understanding of .NET. We want an easy way to use this motley assortment of tools with our existing NAnt build files:&lt;/p&gt;&lt;pre class="brush:xml"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;br /&gt;&amp;lt;project name="sdim" default="move-worksheet"&amp;gt;&lt;br /&gt;&amp;lt;property name="artifacts_dir" value="." overwrite="false" /&amp;gt;&lt;br /&gt;&amp;lt;!-- nant for Windows doesn't use the cygwin path so&lt;br /&gt;     you need to chdir to %cygwin_home%\bin and then&lt;br /&gt;     run %cygwin_home%\path\to\python or&lt;br /&gt;     install Windows python and do e.g.,&lt;br /&gt;     C:\Python31\python.exe --&amp;gt;&lt;br /&gt;&amp;lt;property name="python" value="C:\Python31\python.exe" overwrite="false" /&amp;gt;&lt;br /&gt;&amp;lt;property name="rake" value="C:\Ruby\bin\rake.bat" overwrite="false" /&amp;gt;&lt;br /&gt;&amp;lt;property name="modifications_xml" value="modifications.xml" overwrite="false" /&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;target name="move-worksheet"&amp;gt;&lt;br /&gt;  &amp;lt;exec program="${python}"&amp;gt;&lt;br /&gt;    &amp;lt;arg line="bzaugment.py ${path::combine(artifacts_dir, modifications_xml)} -o ${path::combine(artifacts_dir, 'modifications.augmented.xml')}" /&amp;gt;&lt;br /&gt;  &amp;lt;/exec&amp;gt;&lt;br /&gt;  &amp;lt;exec program="${rake}"&amp;gt;&lt;br /&gt;    &amp;lt;arg line="modifications_xml=${path::combine(artifacts_dir, 'modifications.augmented.xml')}" /&amp;gt;&lt;br /&gt;  &amp;lt;/exec&amp;gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;target name="clean"&amp;gt;&lt;br /&gt;  &amp;lt;exec program="${rake}"&amp;gt;&lt;br /&gt;    &amp;lt;arg line="clobber" /&amp;gt;&lt;br /&gt;  &amp;lt;/exec&amp;gt;&lt;br /&gt;  &amp;lt;delete file="${path::combine(artifacts_dir, 'modifications.augmented.xml')}" /&amp;gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;I still have some reservations about the morality of this. On the one hand, no human should waste even 30 minutes of precious life preparing ambiguous, incomplete, inaccessible and uninsightful summaries of data that's already available, accurate, complete, and easily-accessible. On the other hand, you can lead a manager to water but you can't make him drink. This solution seems to balance those two forces, but by reducing the cost of useless redundancy, am I encouraging further bureaucratic malignancy?&lt;/p&gt;&lt;p&gt;Viola:&lt;/p&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_p8seU-Q7z8I/Sng_4c8C6UI/AAAAAAAAATo/g-eeAGGXtRE/s1600-h/modifications.png"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 227px;" src="http://2.bp.blogspot.com/_p8seU-Q7z8I/Sng_4c8C6UI/AAAAAAAAATo/g-eeAGGXtRE/s320/modifications.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5366109195085211970" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;p&gt;Anyway, I hope you enjoyed the tour of this motley assemblage of languages and tools.&lt;/p&gt;&lt;br /&gt;&lt;p style="clear:left;"&gt;Thanks to &lt;a href="http://landofjosh.com/"&gt;Josh Buedel&lt;/a&gt; for comments and suggestions during the preparation of this post.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shCore.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCSharp.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushXml.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushRuby.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script type='text/javascript'&gt;&lt;br /&gt;    SyntaxHighlighter.config.bloggerMode = true;&lt;br /&gt;    SyntaxHighlighter.all();&lt;br /&gt;  &lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-8127824958230740915?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/8127824958230740915/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/08/vogon-fodder.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8127824958230740915'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8127824958230740915'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/08/vogon-fodder.html' title='vogon fodder'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_p8seU-Q7z8I/Sng_4c8C6UI/AAAAAAAAATo/g-eeAGGXtRE/s72-c/modifications.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-190798191079317856.post-8594549228426089825</id><published>2009-08-02T09:32:00.003-05:00</published><updated>2009-08-02T09:54:58.027-05:00</updated><title type='text'>23.1: new .emacs</title><content type='html'>&lt;code class="prettyprint lang-el"&gt;&lt;br /&gt;(setq ns-command-modifier 'meta)&lt;br /&gt;(if (functionp 'tool-bar-mode)&lt;br /&gt;   (tool-bar-mode -1))&lt;br /&gt;&lt;br /&gt;(setq default-buffer-file-coding-system 'utf-8-unix)&lt;br /&gt;(setq-default indent-tabs-mode nil)&lt;br /&gt;(put 'erase-buffer 'disabled nil)&lt;br /&gt;&lt;br /&gt;(cond&lt;br /&gt;((file-exists-p "/sw/bin")&lt;br /&gt; ;; MacOS X + fink&lt;br /&gt; (setenv "PATH" (concat (getenv "PATH") ":/sw/bin"))&lt;br /&gt; (setq exec-path (append exec-path '("/sw/bin"))))&lt;br /&gt;((file-exists-p "c:/")&lt;br /&gt; ;; Windows&lt;br /&gt; (setenv "PATH" (concat (getenv "PATH") ";c:\\cygwin-1.7\\bin"))&lt;br /&gt; (setq exec-path (append exec-path '("c:\\cygwin-1.7\\bin")))&lt;br /&gt; (if (require 'cygwin-mount nil t)&lt;br /&gt;     (progn&lt;br /&gt;       (cygwin-mount-activate)&lt;br /&gt;       (setq shell-file-name "bash")&lt;br /&gt;       (setenv "SHELL" shell-file-name)&lt;br /&gt;       (setq w32-quote-process-args ?\")&lt;br /&gt;       ;; for subprocesses invoked via the shell&lt;br /&gt;       ;; (e.g., "shell -c command")&lt;br /&gt;       (setq explicit-shell-file-name shell-file-name)&lt;br /&gt;       (setq explicit-sh-args '("-login" "-i"))))&lt;br /&gt; ; (if I'm using Windows, I must be at work!)&lt;br /&gt; (setq bug-reference-url-format&lt;br /&gt;       "https://bugzilla.thcg.net/show_bug.cgi?id=%s")&lt;br /&gt; (setq bug-reference-bug-regexp&lt;br /&gt;       "\\(?:[Bb]ug ?#?\\|PR [a-z-+]+/\\)\\([0-9]+\\)")))&lt;br /&gt;&lt;br /&gt;(dolist (&lt;br /&gt;        prog-mode-hook&lt;br /&gt;        '(python-mode java-mode ruby-mode js2-mode nxml-mode))&lt;br /&gt; (add-hook&lt;br /&gt;  (car (read-from-string (concat (symbol-name prog-mode-hook) "-hook")))&lt;br /&gt;  (lambda ()&lt;br /&gt;    (setq indent-tabs-mode nil))))&lt;br /&gt;&lt;br /&gt;(autoload 'python-mode "python-mode" "Python editing mode." t)&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.py$" . python-mode))&lt;br /&gt;(add-to-list 'interpreter-mode-alist '("python" . python-mode))&lt;br /&gt;(add-hook 'python-mode-hook&lt;br /&gt;         (if (null (getenv "LC_CTYPE"))&lt;br /&gt;             (setenv "LC_CTYPE" "C")))&lt;br /&gt;&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.cs$" . java-mode))&lt;br /&gt;&lt;br /&gt;(autoload 'js2-mode "js2" nil t)&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))&lt;br /&gt;&lt;br /&gt;(autoload 'ruby-mode "ruby-mode" "Ruby editing mode." t)&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))&lt;br /&gt;&lt;br /&gt;(setq auto-mode-alist&lt;br /&gt;     (cons '("\\.\\(xml\\|xsl\\|rng\\|xhtml\\)'" . nxml-mode)&lt;br /&gt;           auto-mode-alist))&lt;br /&gt;(add-to-list 'magic-mode-alist '("&lt;\\?xml " . nxml-mode))&lt;br /&gt;&lt;br /&gt;(if (load "auctex.el" t t t)&lt;br /&gt;   (load "preview-latex.el" nil t t))&lt;br /&gt;(setq ispell-program-name "aspell")&lt;br /&gt;(setq ispell-extra-args '("--sug-mode=ultra"))&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-8594549228426089825?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/8594549228426089825/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/08/231-new-emacs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8594549228426089825'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8594549228426089825'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/08/231-new-emacs.html' title='23.1: new .emacs'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-8986665854239693499</id><published>2009-06-06T16:50:00.029-05:00</published><updated>2011-01-01T22:17:11.059-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='monad'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Andand.NET via monads</title><content type='html'>In my previous post, I discussed &lt;a href="http://seanfoy.blogspot.com/2009/06/andandnet-sans-monads.html"&gt;a direct implementation of andand in C#&lt;/a&gt;. This time, I'll show a monadic implementation.&lt;br /&gt;&lt;br /&gt;As Justin Wick pointed out on Reg Braithwaite's blog announcing Object#andand, &lt;a href="http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html?showComment=1201469160000#c1273495864160958511"&gt;andand can be viewed as a special case of the Maybe Monad&lt;/a&gt;. I've been hearing about how useful monads are for a few years, but until recently I hadn't occasion to use them directly. Would a monad formulation of andand be simpler and clearer to read? Would it be easier to understand, if not for everyone then at least for readers who are already familiar with the monad formalism? Here was an opportunity to find out.&lt;br /&gt;&lt;br /&gt;For those who are totally unfamiliar with monads: they're a means of accommodating side-effects within the functional paradigm. By structuring input, output, exceptions, etc. with monads, you can restore referential transparency to functions that make use of these kinds of features. Monads are surprisingly versatile. You can build lists and arrays from monads, and in the case of arrays the use of monads makes it easy to use the type system to guarantee that client code fulfills a contract on which an efficient implementation of arrays depends. The famous applications of all the functional goodies in LINQ, such as data access and data-parallel computation, are organized around monads.&lt;br /&gt;&lt;br /&gt;So, I refreshed my memory with &lt;a href="http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf"&gt;Monads for Functional Programming&lt;/a&gt; and &lt;a href="http://research.microsoft.com/en-us/um/people/emeijer/Papers/ICFP06.pdf"&gt;Confessions of a Used Programming Language Salesman&lt;/a&gt; followed some of their references: &lt;a href="http://groups.csail.mit.edu/pag/reading-group/wadler-monads.pdf"&gt;Comprehending Monads&lt;/a&gt;, &lt;a href="http://www.cs.bu.edu/~hwxi/academic/papers/popl03.ps"&gt;Guarded Recursive Datatype Constructors&lt;/a&gt;, and &lt;a href="http://research.microsoft.com/en-us/um/people/akenn/generics/gadtoop.pdf"&gt;Generalized Algebraic Data Types and Object-Oriented Programming&lt;/a&gt;. Thus fortified, I was ready to write some tests:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:csharp"&gt;&lt;br /&gt;public class TestMonad&amp;lt;M&amp;gt; where M : MonadImpl&amp;lt;M&amp;gt;, new() {&lt;br /&gt;    [Test]&lt;br /&gt;    public void leftUnit() {&lt;br /&gt;        var a = new Object();&lt;br /&gt;        Assert.AreEqual(a, Monad.Star(Monad.Unit&amp;lt;Object, M&amp;gt;(() =&amp;gt; a), b =&amp;gt; Monad.Unit&amp;lt;Object, M&amp;gt;(() =&amp;gt; b)).Just());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    [Test]&lt;br /&gt;    public void rightUnit() {&lt;br /&gt;        var a = new Object();&lt;br /&gt;        var m = Monad.Unit&amp;lt;Object, M&amp;gt;(() =&amp;gt; a);&lt;br /&gt;        Assert.AreEqual(&lt;br /&gt;            Monad.Just(m),&lt;br /&gt;            Monad.Just(&lt;br /&gt;                Monad.Star(m, b =&amp;gt; Monad.Unit&amp;lt;Object, M&amp;gt;(() =&amp;gt; b))));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Monad&amp;lt;String, M&amp;gt; an(Object o) {&lt;br /&gt;        return Monad.Unit&amp;lt;String, M&amp;gt;(() =&amp;gt; o.ToString());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Monad&amp;lt;Int32, M&amp;gt; bo(Object o) {&lt;br /&gt;        return Monad.Unit&amp;lt;Int32, M&amp;gt;(() =&amp;gt; o.GetHashCode());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    [Test]&lt;br /&gt;    public void associative() {&lt;br /&gt;        var m = Monad.Unit&amp;lt;Object, M&amp;gt;(() =&amp;gt; new Object());&lt;br /&gt;        Assert.AreEqual(&lt;br /&gt;            Monad.Just(&lt;br /&gt;                Monad.Star(&lt;br /&gt;                    m,&lt;br /&gt;                    a =&amp;gt; Monad.Star(an(a), b =&amp;gt; bo((String)b)))),&lt;br /&gt;            Monad.Just(&lt;br /&gt;                Monad.Star(&lt;br /&gt;                    Monad.Star(m, a =&amp;gt; an(a)),&lt;br /&gt;                    b =&amp;gt; bo(b))));&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm following the definitions from &lt;a href="http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf"&gt;Monads for Functional Programming&lt;/a&gt; here: a Monad is a triple &amp;lt;M, Unit, ★&amp;gt; where M is a type constructor (generic type), Unit is a function from computations of type a to M a, and ★ applies a function to a monad.&lt;br /&gt;&lt;br /&gt;My use of the name "Just" here is regrettable. The Maybe monad's type constructor is conventionally defined as a simple union: &lt;pre&gt;Maybe&amp;nbsp;a&amp;nbsp;= Nothing&amp;nbsp;| Just&amp;nbsp;a&lt;/pre&gt;. So, by convention, &lt;pre&gt;Just&amp;nbsp;a&lt;/pre&gt; takes a computation of arbitrary type &lt;pre&gt;a&lt;/pre&gt; to a monad representing that computation. My function "Just" is the inverse, taking a Monad to the computation it represents. I gather that the primitive monad operators are normally encapsulated so that control of side-effects is well localized/modularized. Usually, function composition on monad values is accomplished by ★, so it's not often necessary to explicitly extract raw computations from monads. Finally, monads seem to be most popular within the Haskell community, and pattern-matching is a pretty ubiquitous in Haskell programs. I think these reasons explain why (as far as I know) there's not much use and no standard name for the operation I call "Just" in my code.&lt;br /&gt;&lt;br /&gt;Having translated the mathematical and Haskell definitions of monad into C#, I was ready to check that my translation could extend to an example application shown elsewhere:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: csharp"&gt;&lt;br /&gt;[TestFixture]&lt;br /&gt;public class TestMaybeMonad : TestMonad&amp;lt;MaybeMonadImpl&amp;gt; {&lt;br /&gt;    private Monad&amp;lt;String, MaybeMonadImpl&amp;gt; lookup(string name, Dictionary&amp;lt;String, Monad&amp;lt;String, MaybeMonadImpl&amp;gt;&amp;gt; db) {&lt;br /&gt;        Monad&amp;lt;String, MaybeMonadImpl&amp;gt; result;&lt;br /&gt;        db.TryGetValue(name, out result);&lt;br /&gt;        return result;&lt;br /&gt;    }&lt;br /&gt;        &lt;br /&gt;    [Test]&lt;br /&gt;    public void MailSystem() {&lt;br /&gt;        //based very loosely on haskell.org's example&lt;br /&gt;        // (I don't understand why their mplus is relevant)&lt;br /&gt;        var fullnamedb = new Dictionary&amp;lt;String, Monad&amp;lt;String, MaybeMonadImpl&amp;gt;&amp;gt;();&lt;br /&gt;        var nicknamedb = new Dictionary&amp;lt;String, Monad&amp;lt;String, MaybeMonadImpl&amp;gt;&amp;gt;();&lt;br /&gt;        fullnamedb.Add(&amp;quot;nicholas&amp;quot;, Monad.Unit&amp;lt;String, MaybeMonadImpl&amp;gt;(&amp;quot;n@m.com&amp;quot;));&lt;br /&gt;        nicknamedb.Add(&amp;quot;nick&amp;quot;, Monad.Unit&amp;lt;String, MaybeMonadImpl&amp;gt;(&amp;quot;nicholas&amp;quot;));&lt;br /&gt;        var mnick = Monad.Unit&amp;lt;String, MaybeMonadImpl&amp;gt;(&amp;quot;nick&amp;quot;);&lt;br /&gt;        Assert.AreEqual(&lt;br /&gt;            &amp;quot;n@m.com&amp;quot;,&lt;br /&gt;            Monad.Star(&lt;br /&gt;                Monad.Star(mnick, nick =&amp;gt; lookup(nick, nicknamedb)),&lt;br /&gt;                name =&gt; lookup(name, fullnamedb)).Just());&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And finally, I can restate andand in terms of monads:&lt;br /&gt;&lt;pre class="brush:csharp"&gt;&lt;br /&gt;private class D {}&lt;br /&gt;private class C {&lt;br /&gt;    public D d;&lt;br /&gt;}&lt;br /&gt;private class B {&lt;br /&gt;    public C c;&lt;br /&gt;}&lt;br /&gt;private class A {&lt;br /&gt;    public B b;&lt;br /&gt;}&lt;br /&gt;[Test]&lt;br /&gt;public void andand() {&lt;br /&gt;    A nil = null;&lt;br /&gt;    A a = new A();&lt;br /&gt;    A ab = new A() { b = new B() };&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand&amp;lt;A&amp;gt;(() =&amp;gt; null));&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; (A)null));&lt;br /&gt;    Assert.AreEqual(a, MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; a));&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; a.b));&lt;br /&gt;    Assert.AreEqual(ab.b, MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; ab.b));&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; ab.b.c));&lt;br /&gt;    Func&amp;lt;Object&amp;gt; x = null;&lt;br /&gt;    Func&amp;lt;Func&amp;lt;Object&amp;gt;&amp;gt; y = null;&lt;br /&gt;    y = () =&amp;gt; null;&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; y()()));&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; a.b.c));&lt;br /&gt;    Assert.IsNull(MaybeMonad&amp;lt;D&amp;gt;.Andand(() =&amp;gt; a.b.c.d));&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;OK then, let's see how this is implemented:&lt;br /&gt;&lt;pre class="brush:csharp"&gt;&lt;br /&gt;public class MonadImpl&amp;lt;M&amp;gt; where M : MonadImpl&amp;lt;M&amp;gt;, new() {&lt;br /&gt;    public virtual Monad&amp;lt;B, M&amp;gt; Star&amp;lt;A, B&amp;gt;(Monad&amp;lt;A, M&amp;gt; ma, Func&amp;lt;A, Monad&amp;lt;B, M&amp;gt;&amp;gt; f) {&lt;br /&gt;        return f(ma.Just());&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;public class Monad&amp;lt;T, M&amp;gt; where M : MonadImpl&amp;lt;M&amp;gt;, new() {&lt;br /&gt;    public M impl = new M();&lt;br /&gt;    public T Just() {&lt;br /&gt;        return a();&lt;br /&gt;    }&lt;br /&gt;    internal protected Func&amp;lt;T&amp;gt; a;&lt;br /&gt;    public Monad&amp;lt;B, M&amp;gt; Star&amp;lt;B&amp;gt;(Func&amp;lt;T, Monad&amp;lt;B, M&amp;gt;&amp;gt; f) {&lt;br /&gt;        return impl.Star&amp;lt;T, B&amp;gt;(this, f);&lt;br /&gt;    }&lt;br /&gt;    public static Monad&amp;lt;A, M&amp;gt; Unit&amp;lt;A&amp;gt;(A a) {&lt;br /&gt;        return new Monad&amp;lt;A, M&amp;gt;() { a = () =&amp;gt; a };&lt;br /&gt;    }        &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A monad represents a computation of arbitrary type and some sort of side-effect whose control motivates the use of monads. Wrapping and unwrapping the computation associated with the monad is a straightforward example of parametric polymorphism. Slightly more interesting is recursive nature of the Monad type, whose method signatures involve the type on which they are defined. Simple parametric polymorphism permits us to define methods that behave uniformly on values of any type. If we want to make use of non-universal structure on those values we can restrict the values to a range of types that share the structure we need. Often, in a recursively-defined type, we want this range of types to itself involve a type parameter. For example, in this case we want Star to take a Monad and to return a Monad of a similar type. This requires &lt;a href="http://portal.acm.org/citation.cfm?id=99392"&gt;f-bounded polymorphism&lt;/a&gt;, which is supported in .NET's generics; c#'s where clause not only binds type parameters, but can (re)use those parameters in the bounds.&lt;br /&gt;&lt;br /&gt;Managing the side-effects is less straightforward. Here we want uniform packing/unpacking behavior across all computations, but side-effect management behavior that is peculiar to the kind of side-effect captured by the monad. We've had some support for behavioral refinement that follows subtyping relationships since the beginning of .NET, with virtual methods. But the Star function takes a monad (with behavior b and computation c) and a function from c to d and returns a monad with behavior b and computation d. It builds a value whose type is merely related and not identical to any of its type arguments. This is characteristic of &lt;a href="http://www.cs.bu.edu/~hwxi/academic/papers/popl03.ps"&gt;Guarded Recursive Datatype Constructors&lt;/a&gt;. These g.r. datatypes, also known as &lt;a href="http://research.microsoft.com/en-us/um/people/akenn/generics/gadtoop.pdf"&gt;Generalized Algebraic Data Types (GADTs), are not yet well-supported in .NET&lt;/a&gt;. Fortunately, the work-around shown above is sufficient for seeing the connection between monads and andand.&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.haskell.org/all_about_monads/html/maybemonad.html"&gt;Maybe Monad&lt;/a&gt; represents "just" a computation, or "nothing." The monad structure makes it easy and convenient to treat Maybes in a uniform way, regardless of whether a particular Maybe represents "nothing" or a non-nothing computation. If you're familiar with the &lt;a href="http://c2.com/cgi/wiki?NullObject"&gt;Null Object pattern&lt;/a&gt;, you'll recognize the benefit of this uniformity and you'll see that Maybe monad is a more general alternative to Null Object in many contexts.&lt;br /&gt;&lt;br /&gt;So finally we're ready to see andand, implemented in terms of the MaybeMonad:&lt;br /&gt;&lt;pre class="brush:csharp"&gt;&lt;br /&gt;public class MaybeMonadImpl : MaybeMonadImpl&amp;lt;MaybeMonadImpl&amp;gt; {}&lt;br /&gt;public class MaybeMonad&amp;lt;T&amp;gt; : Monad&amp;lt;T, MaybeMonadImpl&amp;gt; {&lt;br /&gt;    private static Expression andandifyme(MemberExpression me) {&lt;br /&gt;        var objectParameter = Expression.Parameter(me.Expression.Type, &amp;quot;target&amp;quot;);&lt;br /&gt;        var parameters = new ParameterExpression [] {objectParameter};&lt;br /&gt;        //TODO: simplify by using the parameter f rather than by duplicating&lt;br /&gt;        // the subexpression: andandify(me.Expression) in slotValue and&lt;br /&gt;        // in the Just of Star of Unit below (the subexpression could occur&lt;br /&gt;        // solely in slotValue, and then its value could be used by&lt;br /&gt;        // dereferencing f in the Just of Star of Unit.&lt;br /&gt;        var slotValue =&lt;br /&gt;            andandifyinr(&lt;br /&gt;                Expression.Invoke(&lt;br /&gt;                    Expression.Lambda(&lt;br /&gt;                        Expression.MakeMemberAccess(&lt;br /&gt;                            objectParameter,&lt;br /&gt;                            me.Member),&lt;br /&gt;                        parameters),&lt;br /&gt;                    andandify(me.Expression)));&lt;br /&gt;        var f = Expression.Parameter(me.Expression.Type, &amp;quot;f&amp;quot;);&lt;br /&gt;        return&lt;br /&gt;            Expression.Call(&lt;br /&gt;                typeof(Monad),&lt;br /&gt;                &amp;quot;Just&amp;quot;,&lt;br /&gt;                new Type [] { me.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                Expression.Call(&lt;br /&gt;                    typeof(Monad),&lt;br /&gt;                    &amp;quot;Star&amp;quot;,&lt;br /&gt;                    new Type [] { me.Expression.Type, me.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                    Expression.Call(&lt;br /&gt;                        typeof(Monad),&lt;br /&gt;                        &amp;quot;Unit&amp;quot;,&lt;br /&gt;                        new Type [] { me.Expression.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                        andandify(me.Expression)),&lt;br /&gt;                    Expression.Lambda(&lt;br /&gt;                        Expression.Call(&lt;br /&gt;                            typeof(Monad),&lt;br /&gt;                            &amp;quot;Unit&amp;quot;,&lt;br /&gt;                            new Type [] { me.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                            slotValue),&lt;br /&gt;                        f)));&lt;br /&gt;    }&lt;br /&gt;    private static Expression andandifyi(InvocationExpression i) {&lt;br /&gt;        return&lt;br /&gt;            andandifyinr(&lt;br /&gt;                Expression.Invoke(&lt;br /&gt;                    andandify(i.Expression),&lt;br /&gt;                    i.Arguments));&lt;br /&gt;    }&lt;br /&gt;    private static Expression andandifyinr(InvocationExpression i) {&lt;br /&gt;        var l = i.Expression;&lt;br /&gt;        var f = Expression.Parameter(l.Type, &amp;quot;f&amp;quot;);&lt;br /&gt;        return&lt;br /&gt;            Expression.Call(&lt;br /&gt;                typeof(Monad),&lt;br /&gt;                &amp;quot;Just&amp;quot;,&lt;br /&gt;                new Type [] { i.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                Expression.Call(&lt;br /&gt;                    typeof(Monad),&lt;br /&gt;                    &amp;quot;Star&amp;quot;,&lt;br /&gt;                    new Type [] { l.Type, i.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                    Expression.Call(&lt;br /&gt;                        typeof(Monad),&lt;br /&gt;                        &amp;quot;Unit&amp;quot;,&lt;br /&gt;                        new Type [] { l.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                        l),&lt;br /&gt;                    Expression.Lambda(&lt;br /&gt;                        Expression.Call(&lt;br /&gt;                            typeof(Monad),&lt;br /&gt;                            &amp;quot;Unit&amp;quot;,&lt;br /&gt;                            new Type [] { i.Type, typeof(MaybeMonadImpl) },&lt;br /&gt;                            i),&lt;br /&gt;                        f)));&lt;br /&gt;    }&lt;br /&gt;    private static LambdaExpression andandifyl(LambdaExpression l) {&lt;br /&gt;        return&lt;br /&gt;            Expression.Lambda(&lt;br /&gt;                andandify(l.Body),&lt;br /&gt;                Enumerable.Select(&lt;br /&gt;                    l.Parameters,&lt;br /&gt;                    p =&amp;gt; (ParameterExpression)andandify(p)).ToArray());&lt;br /&gt;    }&lt;br /&gt;    private static Expression andandify(Expression expr) {&lt;br /&gt;        var me = expr as MemberExpression;&lt;br /&gt;        if (me != null) {&lt;br /&gt;            return andandifyme(me);&lt;br /&gt;        }&lt;br /&gt;        var i = expr as InvocationExpression;&lt;br /&gt;        if (i != null) {&lt;br /&gt;            return andandifyi(i);&lt;br /&gt;        }&lt;br /&gt;        var l = expr as LambdaExpression;&lt;br /&gt;        if (l != null) {&lt;br /&gt;            return andandifyl(l);&lt;br /&gt;        }&lt;br /&gt;        return expr;&lt;br /&gt;    }&lt;br /&gt;    public static U Andand&amp;lt;U&amp;gt;(Expression&amp;lt;Func&amp;lt;U&amp;gt;&amp;gt; expr) {&lt;br /&gt;        var aexpr = (Expression&amp;lt;Func&amp;lt;U&amp;gt;&amp;gt;)andandify(expr);&lt;br /&gt;        try {&lt;br /&gt;            return (U)aexpr.Compile().DynamicInvoke();&lt;br /&gt;        }&lt;br /&gt;        catch (Exception e) {&lt;br /&gt;            throw new Exception(&lt;br /&gt;                          String.Format(&lt;br /&gt;                               &amp;quot;trouble evaluating {0}&amp;quot;, &lt;br /&gt;                               aexpr),&lt;br /&gt;                          e);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;By conflating null and Maybe's Nothing, we preserve the simple approach Identity monad can use for Star: just unwrap the computation from the input monad and wrap it in the output monad. This approach doesn't work in general, because there might be state associated with the monads that we'd need to migrate.&lt;br /&gt;&lt;br /&gt;Now, comparing the &lt;a href="http://seanfoy.blogspot.com/2009/06/andandnet-sans-monads.html"&gt;direct implementation&lt;/a&gt; to the monadic implementation, you might be ready to decide that monads might be theoretically interesting but are impractical. I think that on one hand, the more stateful monads benefit more fully from the paradigm than the Maybe monad; on the other hand, C# exaggerates the ceremony of monad implementation relative to Haskell. Microsoft hasn't highlighted their use of monads in LINQ or EF or PLINQ, and they haven't exposed any analog to my Monad&amp;lt;T, M&amp;gt; type, so I couldn't share that implementation for andand. In summary, I don't think the monadic implementation of andand carries its weight, but if I saw an opportunity to benefit from other kinds of monads in my program, the benefits might easily outweigh the amortized costs of monads.&lt;br /&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushRuby.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script type='text/javascript'&gt;&lt;br /&gt;    SyntaxHighlighter.config.bloggerMode = true;&lt;br /&gt;    SyntaxHighlighter.all();&lt;br /&gt;  &lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-8986665854239693499?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/8986665854239693499/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/06/andandnet-via-monads.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8986665854239693499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8986665854239693499'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/06/andandnet-via-monads.html' title='Andand.NET via monads'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-7719418722506494350</id><published>2009-06-04T20:25:00.015-05:00</published><updated>2011-01-01T21:03:45.167-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='monad'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Andand.NET sans monads</title><content type='html'>I motivated this material in my &lt;a href="http://seanfoy.blogspot.com/2009/05/motivation-for-andandnet.html"&gt;previous post&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;One way to view andand is as a syntactic transformation of the code: the effect we want is to write&lt;br /&gt;&lt;pre class="brush:csharp; toolbar:false; gutter:false"&gt;&lt;br /&gt;a.b.c.d&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and have that automatically translated to&lt;br /&gt;&lt;pre class="brush:csharp; toolbar:false; gutter:false"&gt;&lt;br /&gt;(a&amp;nbsp;!= null&amp;nbsp;&amp;amp;&amp;amp; b&amp;nbsp;!= null&amp;nbsp;&amp;amp;&amp;amp; c&amp;nbsp;!= null)&amp;nbsp;? a.b.c.d&amp;nbsp;: null&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;C# doesn't support syntactic abstraction in the style of Lisp macros, but later in this post we'll see that recent versions of C# give us much more flexibility than we had in 2.0 and earlier.&lt;br /&gt;&lt;br /&gt;Before we look at the techniques in detail, I want to acknowledge that &lt;a href="http://andand.rubyforge.org/"&gt;syntactic regularity&lt;/a&gt; is a key motivation for Ruby Object#andand. Ruby Object#andand uses infix "andand" in imitation of the built-in guarded assignment operator &amp;&amp;=. We have no such operator in C#, and I am a prefix sympathizer anyway, so neither of my andand implementations share this emphasis with the Ruby inspiration.&lt;br /&gt;&lt;br /&gt;So, my first implementation of andand for C# used LINQ expression tree transformation. The first step is to gain the ability to manipulate the program defined by the source code whose meaning I want to reinterpret (e.g., the compound member access expression a.b.c.d shown above):&lt;br /&gt;&lt;pre class="brush:csharp; toolbar:false"&gt;&lt;br /&gt;public static T Andand&amp;lt;T&amp;gt;(Expression&amp;lt;Func&amp;lt;T&amp;gt;&amp;gt; f) {&lt;br /&gt;    try {&lt;br /&gt;        f = (Expression&amp;lt;Func&amp;lt;T&amp;gt;&amp;gt;)aar(f);&lt;br /&gt;        return f.Compile()();&lt;br /&gt;    }&lt;br /&gt;    catch (Exception e) {&lt;br /&gt;        throw new Exception(String.Format(&amp;quot;trouble with {0}&amp;quot;, f), e);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;One of the &lt;a href="http://research.microsoft.com/en-us/um/people/emeijer/Papers/ICFP06.pdf"&gt;main ideas in LINQ&lt;/a&gt; is type-directed expression quoting. Here, I've written a function that takes an Expression tree as an argument. If I apply this function to a lambda expression, the compiler will translate the lambda to a value representing the abstract syntax tree of the lambda rather than generating an anonymous delegate. So, a typical call site for Andand looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:csharp; highlight:[14,15]"&gt;&lt;br /&gt;private class D {}&lt;br /&gt;private class C {&lt;br /&gt;    public D d;&lt;br /&gt;}&lt;br /&gt;private class B {&lt;br /&gt;    public C c;&lt;br /&gt;}&lt;br /&gt;private class A {&lt;br /&gt;    public B b;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;A ab = new A();&lt;br /&gt;ab.b = new B();&lt;br /&gt;Assert.AreEqual(ab.b, Andand(() =&amp;gt; ab.b));&lt;br /&gt;Assert.IsNull(Andand(() =&amp;gt; ab.b.c.d));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once I have my expression tree, I'm ready for a transformation. Clearly from my starting example, member access expressions constitute an important case. I'd like to be able to handle some other cases though, like method chaining. I'll give you a taste of tree inspection and construction here; for complete details see &lt;a href="http://andandnet.googlecode.com/files/andand-sans-monads.tar.bz2"&gt;andand-sans-monads at googlecode&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:csharp"&gt;&lt;br /&gt;private static Expression aar(Expression e) {&lt;br /&gt;    var me = e as MemberExpression;&lt;br /&gt;    if (me != null) {&lt;br /&gt;        return&lt;br /&gt;            guard(me.Expression, me);&lt;br /&gt;    }&lt;br /&gt;    var l = e as LambdaExpression;&lt;br /&gt;    if (l != null) {&lt;br /&gt;        return&lt;br /&gt;            Expression.Lambda(&lt;br /&gt;                guard(&lt;br /&gt;                    l.Body,&lt;br /&gt;                    l.Body),&lt;br /&gt;                Enumerable.Select(&lt;br /&gt;                    l.Parameters,&lt;br /&gt;                    p =&gt; p).ToArray());&lt;br /&gt;    }&lt;br /&gt;    var ie = e as InvocationExpression;&lt;br /&gt;    if (ie != null) {&lt;br /&gt;        var args =               &lt;br /&gt;            Enumerable.Select(&lt;br /&gt;                ie.Arguments,&lt;br /&gt;                (arg, i) =&gt; Expression.Parameter(arg.Type, String.Format(&amp;quot;arg{0}&amp;quot;, i))).ToArray();&lt;br /&gt;       return&lt;br /&gt;            Expression.Invoke(&lt;br /&gt;                guard(&lt;br /&gt;                    ie.Expression,&lt;br /&gt;                    Expression.Lambda(&lt;br /&gt;                        nullExpr(ie.Expression.Type),&lt;br /&gt;                        args),&lt;br /&gt;                    Expression.Lambda(&lt;br /&gt;                        ie.Expression,&lt;br /&gt;                        args)),&lt;br /&gt;                Enumerable.Select(&lt;br /&gt;                    ie.Arguments,&lt;br /&gt;                    arg =&gt; aar(arg)));&lt;br /&gt;    }&lt;br /&gt;    return e;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It sure is a lot of work to construct expression trees "by hand" as opposed to using the type-directed quoting facility. I could be a lot more concise with Lisp quasiquote. Maybe later we'll experiment with using type-directed quotation of expression tree transformations, but for now I was more interested in exploring the connection between andand and monads.&lt;br /&gt;&lt;br /&gt;But I have to work tomorrow morning and I'd like you to be able to read this now, so I'll save the &lt;a href="http://seanfoy.blogspot.com/2009/06/andandnet-via-monads.html"&gt;monad and GADT discussion&lt;/a&gt; for my next post in this series.&lt;br /&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shCore.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCSharp.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushXml.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushRuby.js' type='text/javascript'&gt;&lt;/script&gt;&lt;br /&gt;  &lt;script type='text/javascript'&gt;&lt;br /&gt;    SyntaxHighlighter.config.bloggerMode = true;&lt;br /&gt;    SyntaxHighlighter.all();&lt;br /&gt;  &lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-7719418722506494350?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/7719418722506494350/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/06/andandnet-sans-monads.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/7719418722506494350'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/7719418722506494350'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/06/andandnet-sans-monads.html' title='Andand.NET sans monads'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-6398719241310115501</id><published>2009-05-31T20:32:00.008-05:00</published><updated>2010-02-10T17:50:09.557-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='monad'/><category scheme='http://www.blogger.com/atom/ns#' term='story'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>motivation for Andand.NET</title><content type='html'>Some of my followers on twitter encouraged me to post about my andand implementation, so here is the first in a series.&lt;br /&gt;&lt;br /&gt;In brief: I wanted to replace&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;if (a != null &amp;&amp; a.b != null &amp;&amp; a.b.c != null) {&lt;br /&gt;    return a.b.c.d;&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;    return null;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;by&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;return andand(a.b.c.d);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and I manged to learn a little bit about monads, generalized algebraic data types, and .NET on the way to achieving&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;return andand(() =&gt; a.b.c.d)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;About a month ago I was working with an instance of a generic type:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var page = Potpourri.Page&lt;MissingItem&gt;.getPage(s, count.Value);&lt;br /&gt;ViewData["reverse-memento"] = page.reverseMemento.itemNumber;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This code obtains a range of rows from a database given a starting position and a window size. Simple integer offsets don't work for the general case of concurrent access by lay users to a mutable table. My approach requires getPage to know an equivalence relation on elements, but it doesn't require that getPage know about itemNumber specifically or even that there be just one field/property that's sufficient to determine equality.&lt;br /&gt;&lt;br /&gt;So, AFAICT, there's no good way to avoid violating &lt;a href="http://www.ccs.neu.edu/research/demeter/biblio/LoD.html"&gt;Demeter&lt;/a&gt; here.&lt;br /&gt;&lt;br /&gt;Certainly there remains a practical problem that the next and previous page references could be undefined for any given page. In this case, reverseMemento could be null.&lt;br /&gt;&lt;br /&gt;The awkward conditional needed to avoid NullReferenceException here brought to mind &lt;a href="http://andand.rubyforge.org/"&gt;Object#andand&lt;/a&gt;. Informally, andand provides short-circuit behavior for chained accesses. The Ruby andand relies on Ruby metaprogramming features. The closest thing we have to a macro facility in c# is Linq expression trees, and I hadn't had an excuse to use System.Linq.Expressions yet, so this seemed like a good opportunity.&lt;br /&gt;&lt;br /&gt;My first implementation directly transformed expression trees, replacing member accesses by conditional member accesses that return null whenever a is null in a.b.&lt;br /&gt;&lt;br /&gt;My second implementation was inspired by &lt;a href="http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html?showComment=1201469160000#c1273495864160958511"&gt;a question about the relation between andand and the Maybe monad&lt;/a&gt;. I thought it would be a good idea to (re)read some papers about monads and test my understanding by implementing a few, including Maybe Monad. I also thought it would be instructive to implement Maybe monad in terms of andand, and andand in terms of Maybe monad.&lt;br /&gt;&lt;br /&gt;I'll discuss the code in detail in my &lt;a href="http://seanfoy.blogspot.com/2009/06/andandnet-sans-monads.html"&gt;next post&lt;/a&gt;. For now, you can download a &lt;a href="http://andandnet.googlecode.com/files/andand.tar.bz2"&gt;snapshot&lt;/a&gt;. I guess I'll migrate my existing hg repo to googlecode in time for the next post to make browsing easier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-6398719241310115501?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/6398719241310115501/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/05/motivation-for-andandnet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/6398719241310115501'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/6398719241310115501'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/05/motivation-for-andandnet.html' title='motivation for Andand.NET'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-8808459274164214547</id><published>2009-05-18T10:06:00.004-05:00</published><updated>2009-05-19T07:07:38.588-05:00</updated><title type='text'>.emacs</title><content type='html'>; Some people I know have asked about my .emacs. Voila:&lt;br /&gt;&lt;br /&gt;(custom-set-faces&lt;br /&gt;  ;; custom-set-faces was added by Custom.&lt;br /&gt;  ;; If you edit it by hand, you could mess it up, so be careful.&lt;br /&gt;  ;; Your init file should contain only one such instance.&lt;br /&gt;  ;; If there is more than one, they won't work right.&lt;br /&gt; )&lt;br /&gt;&lt;br /&gt;(setq default-buffer-file-coding-system 'unix)&lt;br /&gt;&lt;br /&gt;(setenv "PATH" (concat "c:/cygwin-1.7/bin;" (getenv "PATH")))&lt;br /&gt;(setq exec-path (cons "c:/cygwin-1.7/bin/" exec-path))&lt;br /&gt;&lt;br /&gt;(require 'cygwin-mount)&lt;br /&gt;(cygwin-mount-activate)&lt;br /&gt;&lt;br /&gt;(add-to-list 'load-path "/cygdrive/c/program-files/emacs-22.3/site-lisp")&lt;br /&gt;&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.cs$" . java-mode))&lt;br /&gt;(add-hook 'java-mode-hook&lt;br /&gt;   (lambda ()&lt;br /&gt;     (setq indent-tabs-mode nil)))&lt;br /&gt;&lt;br /&gt;(autoload 'js2-mode "js2" nil t)&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))&lt;br /&gt;(add-hook 'js2-mode-hook&lt;br /&gt;   (lambda ()&lt;br /&gt;     (setq indent-tabs-mode nil)))&lt;br /&gt;&lt;br /&gt;(autoload 'python-mode "python-mode" "Python Mode." t)&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.py\\'" . python-mode))&lt;br /&gt;(add-to-list 'interpreter-mode-alist '("python" . python-mode))&lt;br /&gt;(add-hook 'python-mode-hook&lt;br /&gt;   (lambda ()&lt;br /&gt;     (set (make-variable-buffer-local 'beginning-of-defun-function)&lt;br /&gt;   'py-beginning-of-def-or-class)&lt;br /&gt;     (setq outline-regexp "def\\|class ")&lt;br /&gt;     (setq indent-tabs-mode nil)))&lt;br /&gt;&lt;br /&gt;(load (concat "/cygdrive/c/program-files/emacs-22.3/site-lisp/" "nxml-mode-20041004/rng-auto.el"))&lt;br /&gt;(add-to-list 'auto-mode-alist '("\\.\\(xml\\|xsl\\|rng\\|xhtml\\)\\'" . nxml-mode))&lt;br /&gt;(add-hook 'nxml-mode-hook&lt;br /&gt;   (lambda ()&lt;br /&gt;     (setq indent-tabs-mode nil)))&lt;br /&gt;(setq magic-mode-alist&lt;br /&gt;      (cons '("&lt;\\?xml" . nxml-mode)&lt;br /&gt;     magic-mode-alist))&lt;br /&gt;&lt;br /&gt;(setq binary-process-input t) &lt;br /&gt;(setq w32-quote-process-args ?\") &lt;br /&gt;(setq shell-file-name "bash") ;; or sh if you rename your bash executable to sh. &lt;br /&gt;(setenv "SHELL" shell-file-name) &lt;br /&gt;;; For subprocesses invoked via the shell&lt;br /&gt;;; (e.g., "shell -c command")&lt;br /&gt;(setq explicit-shell-file-name shell-file-name) &lt;br /&gt;(setq explicit-sh-args '("-login" "-i"))&lt;br /&gt;&lt;br /&gt;(put 'erase-buffer 'disabled nil)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-8808459274164214547?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/8808459274164214547/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/05/emacs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8808459274164214547'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/8808459274164214547'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/05/emacs.html' title='.emacs'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-481304444429029742</id><published>2009-04-23T17:05:00.004-05:00</published><updated>2009-12-04T16:32:45.800-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>API refs no longer fashionable</title><content type='html'>Recently it seems that API references have gone out of fashion. I'm guessing VS.NET IntelliSense might explain the lack of interest? Well, IntelliSense is no good for lengthy explanations. And so far nobody has cared enough about IntelliSense to implement it for C# and Emacs.&lt;br /&gt;&lt;br /&gt;So I'm going to keep on publishing API ref pages for the free software I write.&lt;br /&gt;&lt;br /&gt;BTW, I eventually found an &lt;a href="http://elliottjorgensen.com/nhibernate-api-ref/"&gt;NHibernate API ref&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-481304444429029742?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/481304444429029742/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/04/api-refs-no-longer-fashionable.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/481304444429029742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/481304444429029742'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/04/api-refs-no-longer-fashionable.html' title='API refs no longer fashionable'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-4921956668054068704</id><published>2009-04-05T22:30:00.005-05:00</published><updated>2009-04-07T17:33:34.179-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='vs.net'/><title type='text'>Oops.NET</title><content type='html'>I think the world needs another error-handling module for .NET applications.&lt;br /&gt;&lt;br /&gt;My experience working in the IT departments of big companies suggests that most .NET applications do not behave well under exceptional circumstances. Sometimes programmers ignore exceptions and allow their programs to corrupt data. More often, our apps lose valuable diagnostic information during crashes. Occasionally, while attempting to preserve diagnostic details, our programs cause cascading failures that overshadow the original!&lt;br /&gt;&lt;br /&gt;So, contrary to my first impression, even very simple error-handling code in thoroughly mundane programs is not too trivial share.&lt;br /&gt;&lt;br /&gt;Before writing yet-another error handling module, I searched for something to reuse.&lt;br /&gt;&lt;br /&gt;Microsoft's Enterprise Library has an exception handling module. In the old days, one reason to avoid it was because of its restrictive license; these days it is offered under the permissive MS-PL. But, it's still not right for most of the (small) apps I encounter, because its purpose is to provide high-level abstractions for conditions throughout an application.&lt;br /&gt;&lt;br /&gt;ELMAH is aimed at ASP.NET specifically, but it wants to do data access and logging on its own. I do recognize the irony in launching competition for ELMAH due to ELMAH's tendency to reinvent the wheel, but I strongly value small sharp tools.&lt;br /&gt;&lt;br /&gt;So I'm making something new.&lt;br /&gt;&lt;br /&gt;It's conceptually based on the "oops page" I wrote for Tyco Healthcare iDeal many years ago. iDeal was an ASP.NET app that helped salespersons develop contracts; the Oops page gave our users an easy way to file bugs so that we wouldn't have to decipher vague phone messages and partial screenshots of ASP.NET default backtraces any more.&lt;br /&gt;&lt;br /&gt;At Anheuser-Busch, our Enterprise Architecture group used to hand out sample code to illustrate our policies about how .NET apps (ASP.NET apps in particular) should crash. The sample could only be obtained by asking for it, which effectively encouraged teams to guess about its contents. The sample tried to show a few variations on its theme, but its presentation confused many teams, who either copied redundant code or omitted crucial details. We needed production-quality reusable code rather than sketches that wound up being reused by copy-and-paste. Because iDeal Oops was proprietary, and because it didn't address console apps, WinForms, Web Services, etc., I had to rewrite it for A-B.&lt;br /&gt;&lt;br /&gt;Now I find myself back at the company formerly known as Tyco Healthcare, and I see apps that would benefit from Oops. I could extract Oops from iDeal, package it as nicely as the code I wrote for A-B... But I'm afraid I might wind up working on .NET at some other company, and I never want to have to rewrite this code again. And, it's clearly worth sharing.&lt;br /&gt;&lt;br /&gt;So, I'm finally publishing &lt;a href="http://code.google.com/p/oopsnet/"&gt;Oops.NET&lt;/a&gt;. Coming soon: log4net integration demo, bugzilla integration demo, and support for WinForms, System.Web.Services, System.ServiceModel, and WPF.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-4921956668054068704?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/4921956668054068704/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/04/oopsnet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/4921956668054068704'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/4921956668054068704'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/04/oopsnet.html' title='Oops.NET'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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-190798191079317856.post-5274596590009812370</id><published>2009-02-13T12:09:00.001-06:00</published><updated>2009-02-13T14:19:38.640-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='vs.net'/><category scheme='http://www.blogger.com/atom/ns#' term='story'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>underrated: Class Library Projects</title><content type='html'>This is the preface for  &lt;a href="http://jottit.com/5t9p2/vsweb"&gt;vsweb&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;My first job after college was programming and sysadmin at BigCo, mostly working on the ASP.NET framework. Early on, I was basically working alone as an programmer although I had a mentor/partner for the sysadmin role. Later, I joined a team that was just beginning to function in fact, although in the tradition of BigCo it had been called a "team" for years before that. There was little development infrastructure in place, and what we had was disfunctional -- think Visual SourceSafe, with MS Office documents and SharePoint lists for defect tracking and planning. I suggested we switch to SVN, my teammate Mike pushed for draco.net (CI), I setup a Bugzilla instance, we held an NUnit tutorial, and a couple of years later we had some some experience with organizing a team software development effort and supporting it with various tools. Things eventually stabilized and the main challenge at work shifted from learning to explaining.&lt;br /&gt;&lt;br /&gt;My second job was coaching and mentoring for another BigCo. My new employer had not a dozen but hundreds of programmers, hence the ability specialize full-time on reflective work. In this role, I was exposed to a much larger and broader set of projects than I would have been as an ordinary programmer. I had the opportunity to revisit the lessons I'd learned before, see how they fit with new evidence, and articulate them for the benefit of others.&lt;br /&gt;&lt;br /&gt;So, that's how I came to write &lt;a href="http://jottit.com/5t9p2/vsweb"&gt;vsweb&lt;/a&gt;: why &lt;em&gt;web application developers should use Class Library Projects rather than project templates whose names include the word "web."&lt;/em&gt; Enjoy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/190798191079317856-5274596590009812370?l=seanfoy.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://seanfoy.blogspot.com/feeds/5274596590009812370/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://seanfoy.blogspot.com/2009/02/underrated-class-library-projects.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/5274596590009812370'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/190798191079317856/posts/default/5274596590009812370'/><link rel='alternate' type='text/html' href='http://seanfoy.blogspot.com/2009/02/underrated-class-library-projects.html' title='underrated: Class Library Projects'/><author><name>Sean Foy</name><uri>http://www.blogger.com/profile/04090757232925761915</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>
