<?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:blogger='http://schemas.google.com/blogger/2008' 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-4161371356646794331</id><updated>2026-01-23T17:09:14.539-06:00</updated><category term="groovy"/><category term="java"/><category term="problem solving"/><category term="code"/><category term="database"/><category term="google calendar"/><category term="youtube"/><category term="Extjs"/><category term="Oracle"/><category term="ant"/><category term="artificialintelligence"/><category term="blogger"/><category term="book"/><category term="calibre"/><category term="closures"/><category term="ebook"/><category term="eclipse"/><category term="facebook"/><category term="fantasyfootball"/><category term="geeks"/><category term="gmail"/><category term="google"/><category term="grails"/><category term="kindle"/><category term="lottery"/><category term="privacy"/><category term="swingbuilder"/><title type='text'>Geek Matt</title><subtitle type='html'>Geeky Stuff</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>25</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-8673035455783799291</id><published>2012-11-27T13:59:00.004-06:00</published><updated>2012-11-27T14:00:15.333-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="gmail"/><category scheme="http://www.blogger.com/atom/ns#" term="google"/><category scheme="http://www.blogger.com/atom/ns#" term="google calendar"/><title type='text'>Direct Links to Multiple Google Accounts</title><content type='html'>&lt;br /&gt;
I have a few different Google accounts that I use regularly. For Gmail, I mostly have it set up to &lt;a href=&quot;http://support.google.com/mail/bin/answer.py?hl=en&amp;amp;ctx=mail&amp;amp;answer=22370&quot;&gt;send email from one inbox&lt;/a&gt;. However, there are times when I want to open up a specific inbox without having to open up the main account, click my address, and then click my other account. The same thing applies for multiple calendars, drive/docs, etc.&lt;br /&gt;
&lt;br /&gt;
I set up some bookmarks to directly link to the accounts and here are the formats.&lt;br /&gt;
&lt;br /&gt;
In all cases, you need to be signed in to each of your accounts. And the order you signed in matters. The number (0, 1, 2) corresponds to the order in which you signed in (starting at 0)&lt;br /&gt;
&lt;br /&gt;
Here is the format of the URLs:&lt;br /&gt;
&lt;br /&gt;
Gmail:&lt;br /&gt;
&lt;a href=&quot;https://mail.google.com/mail/u/0&quot;&gt;https://mail.google.com/mail/u/&lt;b&gt;0&lt;/b&gt;&lt;/a&gt; (or just &lt;a href=&quot;https://mail.google.com/&quot;&gt;https://mail.google.com&lt;/a&gt;)&lt;br /&gt;
&lt;a href=&quot;https://mail.google.com/mail/u/1&quot;&gt;https://mail.google.com/mail/u/&lt;b&gt;1&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://mail.google.com/mail/u/2&quot;&gt;https://mail.google.com/mail/u/&lt;b&gt;2&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Google Calendar:&lt;br /&gt;
&lt;a href=&quot;https://www.google.com/calendar?tab=mc&amp;amp;authuser=0&quot;&gt;https://www.google.com/calendar?tab=mc&amp;amp;authuser=&lt;b&gt;0&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.google.com/calendar?tab=mc&amp;amp;authuser=1&quot;&gt;https://www.google.com/calendar?tab=mc&amp;amp;authuser=&lt;b&gt;1&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.google.com/calendar?tab=mc&amp;amp;authuser=2&quot;&gt;https://www.google.com/calendar?tab=mc&amp;amp;authuser=&lt;b&gt;2&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Google Drive:&lt;br /&gt;
&lt;a href=&quot;https://drive.google.com/?tab=mo&amp;amp;authuser=0&quot;&gt;https://drive.google.com/?tab=mo&amp;amp;authuser=&lt;b&gt;0&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://drive.google.com/?tab=mo&amp;amp;authuser=1&quot;&gt;https://drive.google.com/?tab=mo&amp;amp;authuser=&lt;b&gt;1&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://drive.google.com/?tab=mo&amp;amp;authuser=2&quot;&gt;https://drive.google.com/?tab=mo&amp;amp;authuser=&lt;b&gt;2&lt;/b&gt;&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/8673035455783799291/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2012/11/direct-links-to-multiple-google-accounts.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8673035455783799291'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8673035455783799291'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2012/11/direct-links-to-multiple-google-accounts.html' title='Direct Links to Multiple Google Accounts'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-3686217821986599116</id><published>2012-05-13T20:35:00.002-05:00</published><updated>2012-05-13T20:35:36.028-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="eclipse"/><category scheme="http://www.blogger.com/atom/ns#" term="grails"/><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><title type='text'>Other Blog Posts</title><content type='html'>Here are some recent posts I wrote on a different blog:&lt;br /&gt;
&lt;br /&gt;
Creating an in-memory cache in a Grails/Groovy application using Google&#39;s Guava cache library:&lt;br /&gt;
&lt;a href=&quot;http://refactr.com/blog/2012/05/grails-in-memory-cache-using-googles-guava-library/&quot;&gt;http://refactr.com/blog/2012/05/grails-in-memory-cache-using-googles-guava-library/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Using Google fusion tables and GPS data from my Garmin running watch to easily create a heat-map of most-visited locations:&lt;br /&gt;
&lt;a href=&quot;http://refactr.com/blog/2012/04/create-a-heat-map-using-google-docs/&quot;&gt;http://refactr.com/blog/2012/04/create-a-heat-map-using-google-docs/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Using Eclipse/STS for Grails development. Why you should switch and some common errors/remedies:&lt;br /&gt;
&lt;a href=&quot;http://refactr.com/blog/2012/04/switch-to-sts/&quot;&gt;http://refactr.com/blog/2012/04/switch-to-sts/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/3686217821986599116/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2012/05/other-blog-posts.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/3686217821986599116'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/3686217821986599116'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2012/05/other-blog-posts.html' title='Other Blog Posts'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-8728962987936957280</id><published>2011-09-20T08:55:00.002-05:00</published><updated>2011-09-20T09:01:29.152-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="youtube"/><title type='text'>Extract Audio from YouTube Videos</title><content type='html'>I wanted to get an mp3 of a video on YouTube. There are &lt;a href=&quot;http://lmgtfy.com/?q=extract+youtube+audio&quot;&gt;sites that will do it for you&lt;/a&gt;, but I wanted a little more control.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Turns out it was pretty easy with free software.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1) Download the flv video&lt;/div&gt;&lt;div&gt;I used the open-source &lt;a href=&quot;http://sourceforge.net/projects/ytd/&quot;&gt;YouTube Downloader from Sourceforge&lt;/a&gt;.  With that Java application I just needed to drag the URL from my browser onto the application and it would start downloading. I could even download multiple videos at once.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2) Extract the audio&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;http://ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt; was all I needed in this case.  The command I used was:&lt;/div&gt;&lt;div&gt;ffmpeg -i youtubevideo.flv output.mp3&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Simple as that!&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/8728962987936957280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2011/09/extract-audio-from-youtube-videos.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8728962987936957280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8728962987936957280'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2011/09/extract-audio-from-youtube-videos.html' title='Extract Audio from YouTube Videos'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-6103439352745014499</id><published>2011-08-04T19:48:00.003-05:00</published><updated>2011-08-04T20:33:10.469-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="calibre"/><category scheme="http://www.blogger.com/atom/ns#" term="kindle"/><title type='text'>Calibre and Kindle Collections</title><content type='html'>I recently bought a Kindle. Even though I didn&#39;t think I would like it, I really do.  Maybe it is the computer scientist in me, but I want to control how books are organized and displayed from my computer instead of from the Kindle.  Enter Calibre (&lt;a href=&quot;http://calibre-ebook.com/&quot;&gt;http://calibre-ebook.com/&lt;/a&gt;).  If you don&#39;t know, Calibre is to ebooks as iTunes is to mp3s.  It is great at organizing and converting books.  However, out-of-the-box it doesn&#39;t support collections on the Kindle.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It was pretty easy to set up:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Install the &quot;Kindle Collections&quot; plugin (&lt;a href=&quot;http://www.mobileread.com/forums/showthread.php?t=118635&quot;&gt;instructions here&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Create a custom column by going to Preferences &amp;gt; Change calibre behavior &amp;gt; Interface &amp;gt; Add your own columns. Click the &quot;Add custom column&quot; button&lt;/li&gt;&lt;li&gt;Give it a lookup name (kindlecollections), Column heading (Kindle Collections), and choose the &quot;Text, column shown in tag browser&quot;&lt;/li&gt;&lt;li&gt;Fill in that new column with whatever collections you want on your Kindle.  You can also go to Edit metadata &amp;gt; Custom metadata to add the name of the collections&lt;/li&gt;&lt;li&gt;Plug in your Kindle (if you haven&#39;t already) so you can use the Kindle Collections plugin&lt;/li&gt;&lt;li&gt;Go to Kindle Collections &amp;gt; Customize collections to create from Calibre...&lt;/li&gt;&lt;li&gt;Select &quot;Create&quot; in the Action dropdown for your new column&lt;/li&gt;&lt;li&gt;Go to Kindle Collections &amp;gt; Create collections on the Kindle from Calibre&lt;/li&gt;&lt;li&gt;Eject and Restart your Kindle&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;It sounds more complicated than it really is.  I know there are other tips out there for using existing columns like Series for Kindle Collections, but that didn&#39;t make sense to me as much as creating a new column.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;Also, I don&#39;t like that you have to restart your Kindle every time, but I think that is a Kindle limitation, not Calibre or the plugin.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let me know if you have any other Kindle/Calibre/Collections tips!&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/6103439352745014499/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2011/08/calibre-and-kindle-collections.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/6103439352745014499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/6103439352745014499'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2011/08/calibre-and-kindle-collections.html' title='Calibre and Kindle Collections'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-2556247220570976158</id><published>2011-06-30T11:06:00.000-05:00</published><updated>2011-06-30T11:07:41.327-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ebook"/><title type='text'>Convert 2-Column PDF to eBook</title><content type='html'>&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 16px; -webkit-border-horizontal-spacing: 1px; -webkit-border-vertical-spacing: 1px; &quot;&gt;I recently bought a Kindle and have started loading it with techy books/papers/etc. that I can find for free. I am using the free calibre (&lt;a href=&quot;http://calibre-ebook.com/&quot; target=&quot;_blank&quot; style=&quot;font-family: Verdana, Arial, Helvetica, sans-serif; color: rgb(0, 0, 255); font-weight: normal; font-size: 12px; line-height: 1.4; text-decoration: none; &quot;&gt;http://calibre-ebook.com&lt;/a&gt;) software to do the conversion since pretty much everything I find is in PDF format.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It works OK for 1-column PDFs, but for 2-column the conversion does not work at all.  I can read PDFs on my Kindle, but it is not as nice as reading an actual ebook.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I found one way to convert them in case anyone else is interested:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1) Upload the PDF to Google Docs&lt;/div&gt;&lt;div&gt;2) Choose to &quot;Make a Google Docs Copy&quot; which will convert it into the Google Docs document format that you can edit&lt;/div&gt;&lt;div&gt;3) Tweak as needed&lt;/div&gt;&lt;div&gt;4) Download as HTML&lt;/div&gt;&lt;div&gt;5) Load into Calibre and convert to your book type (mobi, epub, etc)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This will only convert the first 10 pages though, so you might need to split the original PDF (with a site like &lt;a href=&quot;http://foxyutils.com/splitpdf/&quot; target=&quot;_blank&quot; style=&quot;font-family: Verdana, Arial, Helvetica, sans-serif; color: rgb(0, 0, 255); font-weight: normal; font-size: 12px; line-height: 1.4; text-decoration: none; &quot;&gt;http://foxyutils.com/splitpdf&lt;/a&gt;). However, if it gets too long this method will get pretty tedious.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you have any questions or thoughts, let me know.&lt;/div&gt;&lt;/span&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/2556247220570976158/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2011/06/convert-2-column-pdf-to-ebook.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/2556247220570976158'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/2556247220570976158'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2011/06/convert-2-column-pdf-to-ebook.html' title='Convert 2-Column PDF to eBook'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-8184849906268882771</id><published>2011-06-08T19:41:00.008-05:00</published><updated>2011-06-09T08:08:02.467-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Extjs"/><title type='text'>Ext 3.3 MessageBox Issues Resolved</title><content type='html'>I was doing some work using a MessageBox in Ext and ran into a couple of issues.  Mainly the problem was because I am working in an infrastructure that is on Ext 3.3 and not Ext 4 (yet).  Because of that I had to do some new things to add an ID to the MessageBox and also to bring the MessageBox above other elements on the page, specifically the loading mask.&lt;br /&gt;&lt;br /&gt;Here is the code:&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: plain&quot;&gt;&lt;br /&gt;Ext.MessageBox.showWithId = function(dialogId, config){&lt;br /&gt; var dialog = this.getDialog();&lt;br /&gt; dialog.el.dom.id=dialogId;&lt;br /&gt; Ext.MessageBox.show(config);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;Ext.MessageBox.showWithId(&#39;myID&#39;, {&lt;br /&gt; title : &#39;Progress&#39;,&lt;br /&gt; msg : &#39;Processing...&#39;&lt;br /&gt; progressText : &#39;Initializing&#39;,&lt;br /&gt; width : 270,&lt;br /&gt; progress : true,&lt;br /&gt; closable : false,&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;//bring it above the loading mask&lt;br /&gt;Ext.MessageBox.getDialog().getEl().setStyle(&#39;z-index&#39;, &#39;50000&#39;);&lt;br /&gt;&lt;br /&gt;//remove the showWithId function to clean up after myself&lt;br /&gt;Ext.MessageBox.showWithId = undefined;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;First, the ID.  I needed an ID to do automated testing with Selenium, and because of that I needed a way to locate the MessageBox by an ID.  In Ext 4 you can just add an ID in the show() initialization, but that is not in Ext 3.3.  So basically I had to create a new function called showWithId().  This just sets an ID in the dialog and then turn around and call show().&lt;br /&gt;&lt;br /&gt;Next, the z-index.  This just brings the MessageBox to the front.  50000 might be higher than I needed, but it worked for this example.&lt;br /&gt;&lt;br /&gt;Let me know if you have any questions</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/8184849906268882771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2011/06/ext-33-messagebox-issues-resolved.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8184849906268882771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8184849906268882771'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2011/06/ext-33-messagebox-issues-resolved.html' title='Ext 3.3 MessageBox Issues Resolved'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-1354876044082450517</id><published>2011-03-16T08:58:00.001-05:00</published><updated>2011-03-16T09:00:08.523-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="database"/><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><title type='text'>Use Groovy to Find Database Size</title><content type='html'>Last week I found a &lt;a href=&quot;http://geekmatt.blogspot.com/2011/03/getting-size-of-database.html&quot;&gt;SQL statement to find the size of rows in a database&lt;/a&gt;.  That was helpful but I wanted to be able to run this from the command line.  Sounds like a good job for the &lt;a href=&quot;http://groovy.codehaus.org/Database+features&quot;&gt;Groovy database features&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;import groovy.sql.Sql&lt;br /&gt;&lt;br /&gt;//connect to the database&lt;br /&gt;def sql = Sql.newInstance(&#39;jdbc:oracle:thin:user/pass@host:1521:database&#39;)&lt;br /&gt;&lt;br /&gt;//define the statement to run&lt;br /&gt;def sizeStatement = &quot;&quot;&quot;select table_name, num_rows, avg_row_len,&lt;br /&gt;ROUND((num_rows * avg_row_len / 1048576),2) &quot;mb_used&quot;&lt;br /&gt;from user_tables&lt;br /&gt;where num_rows &gt; 0&quot;&quot;&quot;&lt;br /&gt;&lt;br /&gt;//create a date to output (not needed for SQL, just for reporting purposes&lt;br /&gt;def formattedDate = String.format(&#39;%tD %&lt;tT&#39;, new Date())&lt;br /&gt;&lt;br /&gt;//output the results&lt;br /&gt;sql.eachRow(sizeStatement) { row -&gt;&lt;br /&gt; println &quot;${formattedDate},${row.table_name},${row.mb_used}&quot;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A nice little script.  The only catch is that my database is Oracle so I need to pass in the driver jar when I run the script from the command line like this&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: plain&quot;&gt;groovy -classpath OracleThinDrivers.jar CheckSize.groovy&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/1354876044082450517/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2011/03/use-groovy-to-find-database-size.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/1354876044082450517'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/1354876044082450517'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2011/03/use-groovy-to-find-database-size.html' title='Use Groovy to Find Database Size'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-4313061945877435686</id><published>2011-03-09T14:44:00.002-06:00</published><updated>2011-03-16T09:00:53.040-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="database"/><category scheme="http://www.blogger.com/atom/ns#" term="Oracle"/><title type='text'>Getting the Size of a Database</title><content type='html'>I recently needed to get an estimated size of an Oracle database.  It turns out to be not too hard.&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: plain&quot;&gt;--by table&lt;br /&gt;select table_name, num_rows, avg_row_len,&lt;br /&gt; ROUND((num_rows * avg_row_len / 1048576),2) &quot;MB Used&quot;&lt;br /&gt;from user_tables&lt;br /&gt;where num_rows &gt; 0&lt;br /&gt;&lt;br /&gt;--total&lt;br /&gt;select SUM(ROUND((num_rows * avg_row_len / 1048576),2)) &quot;Total MB Used&quot;&lt;br /&gt;from user_tables&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/4313061945877435686/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2011/03/getting-size-of-database.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/4313061945877435686'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/4313061945877435686'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2011/03/getting-size-of-database.html' title='Getting the Size of a Database'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-7782595304499107640</id><published>2010-12-21T11:37:00.000-06:00</published><updated>2010-12-21T11:38:33.778-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="closures"/><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><title type='text'>Return Statements in Closures</title><content type='html'>I found an interesting little &quot;quirk&quot; that I was not expecting with Groovy today having to do with return statements in a closure.&lt;br /&gt;&lt;br /&gt;Let&#39;s say I have some code like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;String someMethod(){&lt;br /&gt; def myList = [&quot;1&quot;, &quot;a&quot;, &quot;test&quot;, &quot;z&quot;]&lt;br /&gt; myList.each{&lt;br /&gt;  println &quot;in each loop for: &quot; + it&lt;br /&gt;  if(it == &quot;test&quot;){&lt;br /&gt;   println &quot;\tfound&quot;&lt;br /&gt;   return it&lt;br /&gt;  }&lt;br /&gt;  println &quot;\tafter if&quot;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def value = someMethod()&lt;br /&gt;println value;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The output is:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;in each loop for: 1&lt;br /&gt; after if&lt;br /&gt;in each loop for: a&lt;br /&gt; after if&lt;br /&gt;in each loop for: test&lt;br /&gt; found&lt;br /&gt;in each loop for: z&lt;br /&gt; after if&lt;br /&gt;[1, a, test, z]&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I was hoping for this:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;in each loop for: 1&lt;br /&gt; after if&lt;br /&gt;in each loop for: a&lt;br /&gt; after if&lt;br /&gt;in each loop for: test&lt;br /&gt; found&lt;br /&gt;test&lt;/pre&gt;&lt;br /&gt;Where it would return from the method once the value is found.  It turns out each{} doesn&#39;t work this way.  I am sure there are good (complicated) reasons for this, but for now I&#39;m just going to change my method to use a good old for loop.&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;String someMethod2(){&lt;br /&gt; def myList = [&quot;1&quot;, &quot;a&quot;, &quot;test&quot;, &quot;z&quot;]&lt;br /&gt; for(int i = 0; i &lt; myList.size; i++){&lt;br /&gt;  println &quot;in each loop for: &quot; + myList[i]&lt;br /&gt;  if(myList[i] == &quot;test&quot;){&lt;br /&gt;   println &quot;\tfound&quot;&lt;br /&gt;   return myList[i]&lt;br /&gt;  }&lt;br /&gt;  println &quot;\tafter if&quot;&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/7782595304499107640/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/12/return-statements-in-closures.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7782595304499107640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7782595304499107640'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/12/return-statements-in-closures.html' title='Return Statements in Closures'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-5450475928946869830</id><published>2010-12-17T09:01:00.002-06:00</published><updated>2010-12-17T09:11:50.413-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><category scheme="http://www.blogger.com/atom/ns#" term="swingbuilder"/><title type='text'>Groovy Stopwatch</title><content type='html'>Groovy Stopwatch&lt;br /&gt;&lt;br /&gt;Before I started using Groovy, I had created a simple little stopwatch Swing application that basically had one button (Start/Stop) and displayed a time.  If you are interested, that is &lt;a href=&quot;http://www.box.net/shared/vu73qi3f4p&quot;&gt;here&lt;/a&gt;.  I wanted to improve it a bit so I could put a countdown so it would give me a few seconds before it started to time.  As a little background, I needed that &quot;feature&quot; so when I was timing this UI I was working on it would give me some time to click &quot;start&quot; on my stopwatch and then switch to the UI I was testing.  Not the most advanced tool, but it works fine for my needs.&lt;br /&gt;&lt;br /&gt;Instead of updating my Java class, which was already fairly complicated due to the nature of Swing programming, I decided to use Groovy&#39;s &lt;a href=&quot;http://groovy.codehaus.org/Swing+Builder&quot;&gt;SwingBuilder&lt;/a&gt;.  &lt;br /&gt;&lt;br /&gt;Here is the final result &lt;br /&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;width: 200px; height: 130px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZ_53re31bMheybN1aFgNTJgDUv0kH9R2SgJbO0XdzglY9OgJHUA5uyzTxJ42RdGR5QsanUJss65mTsnWo1_QJ8gc15QkCBB598XTJdNTdR22hwcGYzE6o8WVCv8ZegBan9QLweSLEqlc/s320/stopwatch.png&quot; border=&quot;0&quot; alt=&quot;Stopwatch&quot;id=&quot;BLOGGER_PHOTO_ID_5551666723278613186&quot; /&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;width: 200px; height: 130px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikHvyzo_FTA77au23IUxepUQG23hNgKgpIftPp7pe2o5ISznbMAFeRLRYJxi9nFt9BPIr-e2BUe2-DMRPTfokfN6i1uZBAk7edQh2wxdqPsnILKtCR16MSH2Phzpol5_mC6NgdWSXXo4Q/s320/stopwatch2.png&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5551668969738175346&quot; /&gt;&lt;br /&gt;&lt;a href=&quot;http://www.box.net/shared/vu73qi3f4p&quot;&gt;Download the Groovy code here&lt;/a&gt;.  &lt;br /&gt;&lt;br /&gt;It was interesting to find that my original Java class for this simple app was 80 lines and my enhanced application in Groovy was only 61, even though I added new features.  I probably could have made each of them a little shorter if I really cared about &lt;a href=&quot;http://en.wikipedia.org/wiki/Lines_of_code&quot;&gt;lines of code&lt;/a&gt;, but for comparison sake I thought it was interesting how much shorter Groovy was.&lt;br /&gt;&lt;br /&gt;For the application itself, here is the code that sets up and displays the Swing UI:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;import groovy.swing.SwingBuilder;&lt;br /&gt;import java.awt.BorderLayout as BL&lt;br /&gt;import javax.swing.JFrame&lt;br /&gt;&lt;br /&gt;startTime = 0&lt;br /&gt;started = false&lt;br /&gt;&lt;br /&gt;swing = new SwingBuilder()&lt;br /&gt;myFrame = swing.frame(title:&#39;Stopwatch&#39;, show: true, size:[200,130], defaultCloseOperation: JFrame.EXIT_ON_CLOSE){&lt;br /&gt; panel (layout: new BL()){&lt;br /&gt;  panel(constraints: BL.NORTH){&lt;br /&gt;   label &#39;Countdown time&#39;&lt;br /&gt;   countdownField = textField (text:&#39;5&#39;, columns:4)&lt;br /&gt;  }&lt;br /&gt;  panel(constraints: BL.CENTER){&lt;br /&gt;   startStopButton = button (text: &#39;Start/Stop&#39;, actionPerformed: {&lt;br /&gt;    if(!started){&lt;br /&gt;     countdownThenStart()&lt;br /&gt;    } else {&lt;br /&gt;     stopTimer()&lt;br /&gt;    }&lt;br /&gt;   })&lt;br /&gt;  }&lt;br /&gt;  panel(constraints: BL.SOUTH){&lt;br /&gt;   messages = label (text:&#39;Click Start&#39;)&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The countdownThenStart() and stopTimer() methods are explained below.  One interesting thing I found out was that if I wanted to reference a button (like &quot;startStopButton&quot;), label (like &quot;messages&quot;), or text field (like &quot;countdownField&quot;) again, all I had to do was assign it to a variable.  If I didn&#39;t need it, then I didn&#39;t assign it.&lt;br /&gt;&lt;br /&gt;I could have made this even simplier if I didn&#39;t use the BorderLayout (remove the &quot;constraints: BL.NORTH&quot;) or explicitly set the size.  I could have just used &quot;pack: true&quot; in place of size.  However, since I was learning how to do this, I added them in.&lt;br /&gt;&lt;br /&gt;Also, be sure to put the &quot;defaultCloseOperation: JFrame.EXIT_ON_CLOSE&quot; in the frame.  I forgot and ended up with dozens of instances of Java running.&lt;br /&gt;&lt;br /&gt;To do the countdown, here is the method I used:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;def countdownThenStart(){&lt;br /&gt; int secondsInt = Integer.parseInt(countdownField.text)&lt;br /&gt; enableFields(false)&lt;br /&gt; &lt;br /&gt; def th = Thread.start{&lt;br /&gt;  for(int i = secondsInt; i &gt; 0; i--){   &lt;br /&gt;   messages.text = i&lt;br /&gt;   sleep(1000);&lt;br /&gt;  }&lt;br /&gt;  messages.text = &quot;Go...&quot;&lt;br /&gt;  startTimer()&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This was a little tricky.  I had a problem displaying the countdown.  It would just display &quot;Go...&quot; and no digits.  It turns out I had to wrap those messages in a separate thread and then it worked like a charm.&lt;br /&gt;&lt;br /&gt;Finally, the rest of the helper methods look like this:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;def startTimer() {&lt;br /&gt; started = true&lt;br /&gt; startTime = new Date().getTime()&lt;br /&gt; enableFields(true)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def stopTimer() {&lt;br /&gt; stopTime = new Date().getTime()&lt;br /&gt; started = false&lt;br /&gt; messages.text = ((stopTime - startTime) / ((double)1000)) + &quot; seconds&quot;  &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def enableFields(def enable){&lt;br /&gt; countdownField.enabled = enable&lt;br /&gt; startStopButton.enabled = enable&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Nothing surprising or confusing here I hope.&lt;br /&gt;&lt;br /&gt;Overall, the SwingBuilder was very easy to use.  I&#39;ve written a number of little Swing apps in the past, and I doubt I will ever need to use anything other than Groovy&#39;s SwingBuilder for them again.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/5450475928946869830/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/12/groovy-stopwatch.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/5450475928946869830'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/5450475928946869830'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/12/groovy-stopwatch.html' title='Groovy Stopwatch'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZ_53re31bMheybN1aFgNTJgDUv0kH9R2SgJbO0XdzglY9OgJHUA5uyzTxJ42RdGR5QsanUJss65mTsnWo1_QJ8gc15QkCBB598XTJdNTdR22hwcGYzE6o8WVCv8ZegBan9QLweSLEqlc/s72-c/stopwatch.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-4902110215171698597</id><published>2010-11-17T10:06:00.010-06:00</published><updated>2010-11-17T10:20:15.463-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><category scheme="http://www.blogger.com/atom/ns#" term="youtube"/><title type='text'>Word Frequency for YouTube Videos</title><content type='html'>YouTube has a feature where you can browse the top viewed videos over a specific time-frame (today, this week, this month, or all time). I thought it would be interesting to see which words (if any) pop up more than others.  By just glancing at the list I guessed that &quot;justin&quot; and &quot;beiber&quot; would top the list. I thought I&#39;d write some quick Groovy code to see if I was right.&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;The Stats: &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is what I found when I looked at the top 160 most viewed videos of all time (as of today):&lt;br /&gt;&lt;br /&gt;Top 25 Words: &lt;br /&gt;&lt;table border=&quot;1&quot; cellpadding=&quot;3&quot;&gt;&lt;tr&gt;&lt;th width=&quot;100&quot;&gt;Word&lt;/th&gt;&lt;th width=&quot;50&quot;&gt;Count&lt;/th&gt;&lt;th width=&quot;50&quot;&gt;Freq&lt;/th&gt;&lt;tr&gt;&lt;td&gt;official&lt;/td&gt;&lt;td&gt;19&lt;/td&gt;&lt;td&gt;3%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;music&lt;/td&gt;&lt;td&gt;12&lt;/td&gt;&lt;td&gt;1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;song&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;cyrus&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;miley&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;1%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;gaga&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;lady&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;bieber&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;justin&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;jason&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;feat&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;dance&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;love&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;baby&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;david&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;best&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;this&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;sean&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nuki&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;iglesias&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;enrique&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;goes&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;like&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;Other Stats:&lt;br /&gt;Total words: 619&lt;br /&gt;Total unique words: 424&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;&lt;b&gt;The Code:&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;All the source code is located &lt;a href=&quot;http://www.box.net/shared/pp9xoohxka&quot;&gt;here&lt;/a&gt; (box.net)&lt;br /&gt;&lt;br /&gt;Here are the guts of the program:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;def html = new XmlSlurper(new SAXParser()).parse(urlString)&lt;br /&gt;html.&#39;**&#39;.findAll{ it.@class == &#39;video-title&#39;}.each {nextVideo -&gt;&lt;br /&gt; //split the title using regex on non-word characters&lt;br /&gt; nextVideo.text().split(/\W/).each{nextWord -&gt;&lt;br /&gt;  def lowerCase = nextWord.toLowerCase()&lt;br /&gt;  //limit the results to &quot;interesting&quot; words&lt;br /&gt;  if(lowerCase.length() &gt;= minWordLength &amp;&amp; !(lowerCase in ignoreList)){&lt;br /&gt;   wordFreq[lowerCase] = wordFreq[lowerCase] == null ? 1 : wordFreq[lowerCase] + 1&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is what it does:&lt;br /&gt;1. Parse the YouTube URL using XmlSlurper&lt;br /&gt;2. Find all the titles on the page&lt;br /&gt;4. Split the title to get individual words&lt;br /&gt;5. Convert it to lower case to make comparisons easier&lt;br /&gt;7. Limit the words by a minimum length (to get rid of stuff like &quot;of&quot;, &quot;and&quot;, &quot;the&quot;) and ignore other words (like &quot;video&quot;)&lt;br /&gt;8. Update the wordFreq map&lt;br /&gt;&lt;br /&gt;I am not very comfortable with minimum length and ignoring words, but without that the top 10 words were: video, the, official, ft, music, i, t, you, in, on.  That is much less interesting that the filtered list, in my opinion.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/4902110215171698597/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/11/word-frequency-for-youtube-videos.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/4902110215171698597'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/4902110215171698597'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/11/word-frequency-for-youtube-videos.html' title='Word Frequency for YouTube Videos'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-7444299268781718659</id><published>2010-10-21T10:39:00.003-05:00</published><updated>2010-10-21T10:57:30.474-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="google calendar"/><title type='text'>Google Calendar - When Was That Event Created?</title><content type='html'>Usually I don&#39;t care, but occasionally I need to know when I created an event on my &lt;a href=&quot;https://www.google.com/calendar/&quot;&gt;Google Calendar&lt;/a&gt;.  It is not the easiest piece of information to find, but it is out there and here is how you find it.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Go to Settings (Calendar Settings) &gt; Calendars tab&lt;/li&gt;&lt;li&gt;Select (click on) the calendar with the event you want&lt;/li&gt;&lt;li&gt;Under the Private Address, click on the ICAL icon&lt;/li&gt;&lt;li&gt;The address will pop up, just save and open that file&lt;/li&gt;&lt;li&gt;In your editor, just search for the name of the event.&lt;/li&gt;&lt;li&gt;Look for the CREATED attribute.  There is also a LAST-MODIFIED attribute if you are interested in that.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/7444299268781718659/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/10/google-calendar-when-was-that-event.html#comment-form' title='29 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7444299268781718659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7444299268781718659'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/10/google-calendar-when-was-that-event.html' title='Google Calendar - When Was That Event Created?'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>29</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-8426108656891031815</id><published>2010-09-09T09:19:00.001-05:00</published><updated>2010-09-09T09:22:01.637-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><title type='text'>Use ConfigSluper Instead Of Properites Files</title><content type='html'>I often use Java property files as configuration for prototypes, proofs of concepts, utility applications, etc.  Groovy has a more powerful way of doing this called a &lt;a href=&quot;http://groovy.codehaus.org/ConfigSlurper&quot;&gt;ConfigSlurper&lt;/a&gt; (&lt;a href=&quot;http://groovy.codehaus.org/gapi/groovy/util/ConfigSlurper.html&quot;&gt;JavaDoc&lt;/a&gt;).  &lt;br /&gt;&lt;br /&gt;A ConfigSlurper just uses a Groovy script as input instead of a .properties file.  This lets you do better (in my opinion) configuration like having typed properties (instead of them all being Strings you have to convert) including lists and maps, tree-structures of properties, even deriving a property value based on some calculation or closure.  &lt;br /&gt;&lt;br /&gt;Here are some examples.  All the code can be &lt;a href=&quot;http://www.box.net/shared/p5pxcv6r2b&quot;&gt;downloaded here&lt;/a&gt; (box.net)&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;//Config1.groovy&lt;br /&gt;stringProperty=&quot;Some string&quot;&lt;br /&gt;numberProperty=42&lt;br /&gt;booleanProperty=false&lt;br /&gt;listProperty=[&quot;Monday&quot;, &quot;Tuesday&quot;, &quot;Wednesday&quot;]&lt;/pre&gt;&lt;br /&gt;Here is a basic configuration file using some different types.  Here is how to read and use it:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;def config = new ConfigSlurper().parse(new File(&#39;src/Config1.groovy&#39;).toURL())&lt;br /&gt;&lt;br /&gt;assert &quot;Some string&quot; == config.stringProperty&lt;br /&gt;assert config.stringProperty.class == String&lt;br /&gt;&lt;br /&gt;assert 42 == config.numberProperty&lt;br /&gt;assert config.numberProperty.class == Integer&lt;br /&gt;&lt;br /&gt;assert false == config.booleanProperty&lt;br /&gt;assert config.booleanProperty.class == Boolean&lt;br /&gt;&lt;br /&gt;assert [&quot;Monday&quot;, &quot;Tuesday&quot;, &quot;Wednesday&quot;] == config.listProperty&lt;br /&gt;assert 3 == config.listProperty.size()&lt;br /&gt;assert config.listProperty.class == ArrayList&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Another powerful feature is being able to use trees of data like this:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;//Config2.groovy&lt;br /&gt;teams {&lt;br /&gt; packers {&lt;br /&gt;  quarterback=&quot;Aaron Rodgers&quot;&lt;br /&gt;  recievers=[&quot;Greg Jennings&quot;, &quot;Donald Driver&quot;]&lt;br /&gt; }&lt;br /&gt; vikings { &lt;br /&gt;  quarterback=&quot;Brett Favre&quot; &lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And then reading them like this:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;def config = new ConfigSlurper().parse(new File(&#39;src/Config2.groovy&#39;).toURL())&lt;br /&gt;&lt;br /&gt;assert &quot;Aaron Rodgers&quot; == config.teams.packers.quarterback&lt;br /&gt;assert [&quot;Greg Jennings&quot;, &quot;Donald Driver&quot;] == config.teams.packers.recievers&lt;br /&gt;assert &quot;Brett Favre&quot; == config.teams.vikings.quarterback&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Along those lines, there is a special configuration called &quot;environment.&quot;  This allows you to use different configurations based on what &quot;environment&quot; you are in.&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;//Config3.groovy&lt;br /&gt;website {&lt;br /&gt; //default values&lt;br /&gt; url = &quot;http://default.mycompany.com&quot;&lt;br /&gt; port = 80&lt;br /&gt; user = &quot;test&quot;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;environments {&lt;br /&gt; development {&lt;br /&gt;  website {&lt;br /&gt;   url = &quot;http://dev.mycompany.com&quot;&lt;br /&gt;   port = 8080&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; production {&lt;br /&gt;  website {&lt;br /&gt;   url = &quot;http://www.mycompany.com&quot;&lt;br /&gt;   user = &quot;prodUser&quot;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;In this example, there are some default values for the website properties.  Then, the different environments overwrite some of the defaults.  Here is how you call the ConfigSlurper to use the different environments.&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;//defaults&lt;br /&gt;def config = new ConfigSlurper().parse(new File(&#39;src/Config3.groovy&#39;).toURL())&lt;br /&gt;&lt;br /&gt;assert config.website.url==&quot;http://default.mycompany.com&quot;&lt;br /&gt;assert config.website.port==80&lt;br /&gt;assert config.website.user==&quot;test&quot;&lt;br /&gt;&lt;br /&gt;//development environment&lt;br /&gt;config = new ConfigSlurper(&quot;development&quot;).parse(new File(&#39;src/Config3.groovy&#39;).toURL())&lt;br /&gt;&lt;br /&gt;assert config.website.url==&quot;http://dev.mycompany.com&quot;&lt;br /&gt;assert config.website.port==8080&lt;br /&gt;assert config.website.user==&quot;test&quot;&lt;br /&gt;&lt;br /&gt;//production environment&lt;br /&gt;config = new ConfigSlurper(&quot;production&quot;).parse(new File(&#39;src/Config3.groovy&#39;).toURL())&lt;br /&gt;&lt;br /&gt;assert config.website.url==&quot;http://www.mycompany.com&quot;&lt;br /&gt;assert config.website.port==80&lt;br /&gt;assert config.website.user==&quot;prodUser&quot;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally (for this article), here are a few fun things you can do with a ConfigSlurper including calculated values and merging configs.&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;//Config4.groovy&lt;br /&gt;&lt;br /&gt;//set the value as some calculation&lt;br /&gt;calculation = 2+2&lt;br /&gt;&lt;br /&gt;//base the value off of some other property&lt;br /&gt;calc2 = calculation * 2&lt;br /&gt;&lt;br /&gt;//set the value using a closure&lt;br /&gt;doubling = 1&lt;br /&gt;5.times{ doubling = doubling * 2 }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And using those values:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;def config = new ConfigSlurper().parse(new File(&#39;src/Config4.groovy&#39;).toURL())&lt;br /&gt;&lt;br /&gt;assert 4 == config.calculation&lt;br /&gt;assert 8 == config.calc2&lt;br /&gt;&lt;br /&gt;assert 32 == config.doubling&lt;br /&gt;&lt;br /&gt;//merge config files&lt;br /&gt;def config1 = new ConfigSlurper().parse(new File(&#39;src/Config1.groovy&#39;).toURL())&lt;br /&gt;def config2 = new ConfigSlurper().parse(new File(&#39;src/Config2.groovy&#39;).toURL())&lt;br /&gt;config1 = config1.merge(config2)&lt;br /&gt;assert 42 == config1.numberProperty&lt;br /&gt;assert &quot;Aaron Rodgers&quot; == config1.teams.packers.quarterback&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Leave a comment if you have any questions or suggestions!</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/8426108656891031815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/09/use-configsluper-instead-of-properites.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8426108656891031815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8426108656891031815'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/09/use-configsluper-instead-of-properites.html' title='Use ConfigSluper Instead Of Properites Files'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-7577341729212994791</id><published>2010-09-01T12:18:00.002-05:00</published><updated>2010-09-01T12:22:09.877-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ant"/><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><title type='text'>Running an Ant Script in Groovy</title><content type='html'>I recently wanted to test the execution of an Ant script and I wanted to use Groovy.  There are a lot of blogs about using AntBuilder to run Ant tasks in Groovy, but that isn&#39;t what I needed.  I actually needed to test the Ant script itself.&lt;br /&gt;&lt;br /&gt;This was not to hard, but the concept is tricky.  I used AntBuilder to execute an external process (using the &lt;a href=&quot;http://ant.apache.org/manual/Tasks/exec.html&quot;&gt;exec task&lt;/a&gt;).  In this case, the external process is ant.  Lost yet?  Here is the code, maybe that will make it easier:&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;File antExecutable = new File(&quot;C:/Ant/bin/ant.bat&quot;)&lt;br /&gt;assert antExecutable.exists()&lt;br /&gt;&lt;br /&gt;File antScript = new File(&quot;C:/antscripts/build.xml&quot;)&lt;br /&gt;assert antScript.exists()&lt;br /&gt;&lt;br /&gt;def ant = new AntBuilder()&lt;br /&gt;ant.exec(executable: antExecutable.getAbsolutePath(),&lt;br /&gt; outputproperty:&quot;cmdOutput&quot;,&lt;br /&gt; errorproperty:&quot;cmdError&quot;){&lt;br /&gt;  arg(value: &quot;-f&quot;)&lt;br /&gt;  arg(path: antScript.getAbsolutePath())&lt;br /&gt;}&lt;br /&gt;assert ant.project.properties.cmdOutput.contains(&quot;BUILD SUCCESSFUL&quot;)&lt;br /&gt;assert ant.project.properties.cmdError == &quot;&quot;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Hopefully this is pretty straight-forward.  Basically this is equivalent to calling &quot;ant -f C:\antscripts\build.xml&quot; from a command line. If you want to know more about how to use AntBuilder, &lt;a href=&quot;http://groovy.codehaus.org/Using+Ant+from+Groovy&quot;&gt;go here&lt;/a&gt;.  &lt;br /&gt;&lt;br /&gt;The output gets stored in a property called &quot;cmdOutput&quot; which can be tested using the &quot;ant.project.properties.cmdOutput&quot; variable.  In a similiar way, the standard error gets stored in &quot;cmdError.&quot;  If you just want to display the output, remove the outputproperty and errorproperty from the exec task.&lt;br /&gt;&lt;br /&gt;There are a couple other things you might need to do.  &lt;br /&gt;&lt;br /&gt;First, if you want to pass additional arguments to the script (for example, setting some ant properties), you can just add additional &quot;arg&quot; lines like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;ant.exec(executable: antExecutable.getAbsolutePath(),&lt;br /&gt; outputproperty:&quot;cmdOutput&quot;,&lt;br /&gt; errorproperty:&quot;cmdError&quot;){&lt;br /&gt;  arg(value: &quot;-f&quot;)&lt;br /&gt;  arg(path: antScript.getAbsolutePath())&lt;br /&gt;  arg(value: &quot;-DsomeProperty=someValue)  &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Second, if the ant script needs to know about some classpath variables, just add an &quot;env&quot; line like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;ant.exec(executable: antExecutable.getAbsolutePath(),&lt;br /&gt; outputproperty:&quot;cmdOutput&quot;,&lt;br /&gt; errorproperty:&quot;cmdError&quot;){&lt;br /&gt;  arg(value: &quot;-f&quot;)&lt;br /&gt;  arg(path: antScript.getAbsolutePath())&lt;br /&gt;  env(key: &quot;CLASSPATH&quot;, path: &quot;C:/jars/;C:/project/files&quot;)  &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Leave a comment if you have any questions or suggestions</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/7577341729212994791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/09/running-ant-script-in-groovy.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7577341729212994791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7577341729212994791'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/09/running-ant-script-in-groovy.html' title='Running an Ant Script in Groovy'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-1413011777628620966</id><published>2010-08-30T13:08:00.003-05:00</published><updated>2010-08-30T13:28:04.271-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><category scheme="http://www.blogger.com/atom/ns#" term="lottery"/><title type='text'>Scrape Powerball Number Frequency</title><content type='html'>I know that buying lottery tickets is a &lt;a href=&quot;http://articles.moneycentral.msn.com/RetirementandWills/RetireEarly/WhyPoorPeopleWinTheLottery.aspx&quot;&gt;total waste of money&lt;/a&gt;.  I also know that there is nothing you can do to increase the odds of hitting a Powerball jackpot (1 in 195,249,054).  However, just for fun, I thought I&#39;d fire off a quick groovy script that will read the frequencies from the &lt;a href=&quot;http://powerball.com/powerball/pb_frequency.asp&quot;&gt;Powerball website&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Once I have all the numbers I could sort the lists to find out which numbers are most frequently picked (or least frequently picked).  I&#39;m sure there is other statistical analysis that you could do too if you wanted.  You could also watch the frequency trends over time (though &lt;a href=&quot;http://powerball.com/powerball/pb_nbr_history.asp&quot;&gt;this historic list of numbers&lt;/a&gt; may be better at that)&lt;br /&gt;&lt;br /&gt;Here&#39;s the simple script:&lt;br /&gt;&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;import org.cyberneko.html.parsers.SAXParser;&lt;br /&gt;&lt;br /&gt;//initialize&lt;br /&gt;def maxPowerBall = 39&lt;br /&gt;def whiteBallFreq = [:]&lt;br /&gt;def pbFreq = [:]&lt;br /&gt;&lt;br /&gt;//read the numbers&lt;br /&gt;def url = &#39;http://powerball.com/powerball/pb_frequency.asp&#39;&lt;br /&gt;def html = new XmlSlurper(new SAXParser()).parse(url)&lt;br /&gt;&lt;br /&gt;//create the lists from the HTML&lt;br /&gt;html.BODY.TABLE.TBODY.TR[3].TD[1].TABLE.TBODY.TR[4].TD[1].TABLE.TBODY.TR[2..-2].each{nextRow -&gt;&lt;br /&gt; def cols = nextRow.children()&lt;br /&gt; def ball = Integer.parseInt(cols[0].text())&lt;br /&gt; &lt;br /&gt; whiteBallFreq[ball] = parsePossiblyBlankNumber(cols[1].text())&lt;br /&gt; if(ball &lt;= maxPowerBall){&lt;br /&gt;  pbFreq[ball] = parsePossiblyBlankNumber(cols[2].text())&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def int parsePossiblyBlankNumber(String value) {&lt;br /&gt; if(value.trim().length() == 0){&lt;br /&gt;  return 0;&lt;br /&gt; } else {&lt;br /&gt;  return Integer.parseInt(value)&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The import is &lt;a href=&quot;http://people.apache.org/%7Eandyc/neko/doc/index.html&quot;&gt;found here&lt;/a&gt;. The long &quot;html.BODY.TABLE...&quot; line is needed because of how the Powerball website is set up.&lt;br /&gt;&lt;br /&gt;I put in the parsePossiblyBlankNumber method since the website uses blanks instead of 0s when there are no frequencies.&lt;br /&gt;&lt;br /&gt;Leave a comment if you have any questions or suggestions!</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/1413011777628620966/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/08/scrape-powerball-number-frequency.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/1413011777628620966'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/1413011777628620966'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/08/scrape-powerball-number-frequency.html' title='Scrape Powerball Number Frequency'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-4722425355592248038</id><published>2010-08-20T08:27:00.006-05:00</published><updated>2010-08-20T08:47:26.804-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="blogger"/><title type='text'>Code Formatting in Blogger Using SyntaxHighlighter</title><content type='html'>I wanted to use SyntaxHighlighter, but it turns out it takes a little tweaking on Blogger.  I found a number of &lt;a href=&quot;http://lmgtfy.com/?q=SyntaxHighlighter+with+blogger&quot;&gt;blogs that had instructions&lt;/a&gt;, but none of them seemed to work.  Here is what I did to get the code formatting.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;1. Upload files to &lt;/span&gt;&lt;a style=&quot;font-weight: bold;&quot; href=&quot;https://sites.google.com/&quot;&gt;Google Sites&lt;/a&gt; (Optional)&lt;br /&gt;You need to link to a few javascript and css files.  It looks like SyntaxHighlighter has a &lt;a href=&quot;http://alexgorbatchev.com/SyntaxHighlighter/hosting.html&quot;&gt;hosted version&lt;/a&gt;, but I decided to host the specific files I wanted.  Here are the files you need:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;shCore.js&lt;/li&gt;&lt;li&gt;shCore.css&lt;/li&gt;&lt;li&gt;shCoreDefault.css (or your theme, I used shCoreEclipse.css)&lt;/li&gt;&lt;li&gt;Brushes (for type of code).  I used shBrushPlain.css, shBrushJava.css, and shBrushGroovy.css&lt;/li&gt;&lt;/ol&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;2. Edit your Blogger template&lt;/span&gt;&lt;br /&gt;Go to Design &gt; Edit HTML.  Add this code towards the bottom:&lt;br /&gt;&lt;pre class=&quot;brush: plain&quot;&gt;&lt;br /&gt;&lt;!-- end outer-wrapper --&gt;&lt;br /&gt;&lt;br /&gt;&lt;script src=&#39;http://sites.google.com/site/[SITE]/shCore.js&#39; type=&#39;text/javascript&#39;/&gt;  &lt;br /&gt;&lt;script src=&#39;http://sites.google.com/site/[SITE]/shBrushPlain.js&#39; type=&#39;text/javascript&#39;/&gt;  &lt;br /&gt;&lt;script src=&#39;http://sites.google.com/site/[SITE]/shBrushJava.js&#39; type=&#39;text/javascript&#39;/&gt;  &lt;br /&gt;&lt;script src=&#39;http://sites.google.com/site/[SITE]/shBrushGroovy.js&#39; type=&#39;text/javascript&#39;/&gt; &lt;br /&gt;&lt;br /&gt;&lt;link href=&#39;http://sites.google.com/site/[SITE]/shCore.css&#39; rel=&#39;stylesheet&#39; type=&#39;text/css&#39;/&gt;  &lt;br /&gt;&lt;link href=&#39;http://sites.google.com/site/[SITE]/shCoreEclipse.css&#39; rel=&#39;stylesheet&#39; type=&#39;text/css&#39;/&gt;  &lt;br /&gt;&lt;br /&gt;&lt;script type=&#39;text/javascript&#39;&gt;  &lt;br /&gt;SyntaxHighlighter.config.bloggerMode = true;&lt;br /&gt;SyntaxHighlighter.all();  &lt;br /&gt;&lt;/script&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The &quot;trick&quot; for blogger is to set the &quot;&lt;a href=&quot;http://alexgorbatchev.com/SyntaxHighlighter/manual/configuration/&quot;&gt;bloggerMode&quot; to true&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;3. Wrap your code in &quot;pre&quot;&lt;/span&gt;&lt;br /&gt;For example, if I wanted to use blog about this code in Java, I would use class=&quot;brush: java&quot; like this:&lt;br /&gt;&lt;pre class=&quot;brush: plain&quot;&gt;&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;public static void main(String[] args){&lt;br /&gt; System.out.println(&quot;Hello World!&quot;);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And it would look like this:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;public static void main(String[] args){&lt;br /&gt; System.out.println(&quot;Hello World!&quot;);&lt;br /&gt;}&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/4722425355592248038/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/08/code-formatting-in-blogger-using.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/4722425355592248038'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/4722425355592248038'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/08/code-formatting-in-blogger-using.html' title='Code Formatting in Blogger Using SyntaxHighlighter'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-7559997064740822147</id><published>2010-08-18T07:55:00.006-05:00</published><updated>2010-08-20T07:47:31.614-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="fantasyfootball"/><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><title type='text'>Fantasy Football Cheat Sheet Scraper</title><content type='html'>With my fantasy football draft coming up, I thought I would create a quick Groovy script to scrape a couple Fantasy Football cheat sheets.  If you don&#39;t know, a cheat sheet has a ranking of players that you can use to make your draft picks.  There are a bunch available online, and they are updated frequently (like now that Brett Favre is back with the Vikings I assume he will move up and Tarvaris Jackson will move down in rank).&lt;br /&gt;&lt;br /&gt;Since I wanted the most up-to-date ones on the day of my draft, I wrote a couple classes in Groovy to read the webpages and print out the results in a common format.  This turned out the be pretty easy to do.&lt;br /&gt;&lt;br /&gt;For example, here is one from &lt;a href=&quot;http://www.fftoolbox.com&quot;&gt;http://www.fftoolbox.com&lt;/a&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;import org.cyberneko.html.parsers.SAXParser;&lt;br /&gt;&lt;br /&gt;class FFToolbox {&lt;br /&gt;    public List getPlayerList(){&lt;br /&gt;        def playerList = []&lt;br /&gt;        &lt;br /&gt;        //read the XML&lt;br /&gt;        [&#39;http://www.fftoolbox.com/football/2010/overall.cfm?page=1&#39;,&lt;br /&gt;                &#39;http://www.fftoolbox.com/football/2010/overall.cfm?page=2&#39;,&lt;br /&gt;                &#39;http://www.fftoolbox.com/football/2010/overall.cfm?page=3&#39;,&lt;br /&gt;                &#39;http://www.fftoolbox.com/football/2010/overall.cfm?page=4&#39;].each{url -&amp;gt;&lt;br /&gt;                    def html = new XmlSlurper(new SAXParser()).parse(url)&lt;br /&gt;                    def table = html.BODY.&#39;**&#39;.findAll{ it.name() == &#39;TABLE&#39;}[0]&lt;br /&gt;                    def rows = table.TBODY.children()[1..-2]&lt;br /&gt;                    &lt;br /&gt;                    //convert to my BO&lt;br /&gt;                    rows.each{ nextRow -&amp;gt;&lt;br /&gt;                        def columns = nextRow.children()&lt;br /&gt;                        playerList.add(new CheatSheetEntry(&lt;br /&gt;                                rank: columns[0],&lt;br /&gt;                                name: columns[1],&lt;br /&gt;                                position: columns[2],&lt;br /&gt;                                team: columns[3],&lt;br /&gt;                                byeWeek: columns[4])&lt;br /&gt;                                )&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;        return playerList&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that the import is &lt;a href=&quot;http://people.apache.org/~andyc/neko/doc/index.html&quot;&gt;from here&lt;/a&gt; and provides an HTML parser&lt;br /&gt;&lt;br /&gt;Here is another example from ESPN.com:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;class ESPN {&lt;br /&gt;    public List getPlayerList(){&lt;br /&gt;        def playerList = []&lt;br /&gt;        &lt;br /&gt;        def url = &#39;http://sports.espn.go.com/fantasy/football/ffl/story?page=NFLDK2K10rankstop200&#39;&lt;br /&gt;        def html = new XmlSlurper(new SAXParser()).parse(url)&lt;br /&gt;        &lt;br /&gt;        def table = html.BODY.&#39;**&#39;.findAll{ it.name() == &#39;TABLE&#39;}[0]&lt;br /&gt;        def rows = table.TBODY.children()&lt;br /&gt;        &lt;br /&gt;        rows.each{nextRow -&amp;gt;&lt;br /&gt;            def columns = nextRow.children()&lt;br /&gt;            def positionMatcher = columns[3].text() =~ /\D+/&lt;br /&gt;            playerList.add(new CheatSheetEntry(&lt;br /&gt;                    rank: columns[0],&lt;br /&gt;                    name: columns[1].text().substring(0, columns[1].text().indexOf(&amp;quot;,&amp;quot;)),&lt;br /&gt;                    position: positionMatcher[0],&lt;br /&gt;                    team: columns[1].text().substring(columns[1].text().indexOf(&amp;quot;,&amp;quot;) + 2),&lt;br /&gt;                    byeWeek: columns[2])&lt;br /&gt;                    )&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        return playerList&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Basically they do the same things: Connect to and parse the url(s), find the table with the cheat sheet, and then go through the rows and columns assigning the correct values to a GroovyBean called CheatSheetEntry.&lt;br /&gt;&lt;br /&gt;Now I just need to combine the lists somehow to get an average ranking of the players.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/7559997064740822147/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/08/fantasy-football-cheat-sheet-scraper.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7559997064740822147'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7559997064740822147'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/08/fantasy-football-cheat-sheet-scraper.html' title='Fantasy Football Cheat Sheet Scraper'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-1077385551274140559</id><published>2010-07-27T08:58:00.003-05:00</published><updated>2010-07-27T10:52:20.190-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="facebook"/><category scheme="http://www.blogger.com/atom/ns#" term="privacy"/><title type='text'>My Facebook Privacy Settings</title><content type='html'>There has been a lot of talk in the news about Facebook and their privacy/security settings.  Here is how I have set up my account to make sure only certain people can see certain parts of my Facebook profile.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Step 1 - Create Friend Lists&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;All of the privacy settings are based on which lists of people can see which parts of your profile, so the first step is to create those lists. The two lists we want to create now are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Full Access: used for your friends that can see all of your profile.&lt;/li&gt;&lt;li&gt;No Access: used for the &quot;friends&quot; that you don&#39;t want to see anything on your page&lt;/li&gt;&lt;/ul&gt;Everyone who falls between these two lists will just get &quot;default&quot; access, and we don&#39;t need to create a list for them since they are part of your &quot;all friends&quot; list.&lt;br /&gt;&lt;br /&gt;To create a list, click on &quot;Friends&quot; on the right side&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_Tfdzb-dOMCCBIy0Gh1o_aHyd-AI8ri8zn1NmJ8jImR6WyflyVJGHzxkK4cppodfqFp1wc4NKIrIsip2vkuPG694gA-lyRD4A0ns9uhoDPW7y8n7bleawUtTYb-04LJezsJYcQ3zx6V0/s1600/screenshot1.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 193px; height: 60px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_Tfdzb-dOMCCBIy0Gh1o_aHyd-AI8ri8zn1NmJ8jImR6WyflyVJGHzxkK4cppodfqFp1wc4NKIrIsip2vkuPG694gA-lyRD4A0ns9uhoDPW7y8n7bleawUtTYb-04LJezsJYcQ3zx6V0/s320/screenshot1.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613598065597890&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Then click on &quot;+ Create a List,&quot; give it a name and pick which people you want in the list.&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs79EX_QtRHRd9pZ-bNnZ4lNiD5pEHR_dpQMepdYgc2rgvFeBIzVXks4WB1diTl7NX3nJFcyjymLSQfnUR48QN3usj_LvBeel16adftjRYcmL-WobmCcipNQ8k7HfGG6xzzeY-a3LFoPU/s1600/screenshot2.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 80px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjs79EX_QtRHRd9pZ-bNnZ4lNiD5pEHR_dpQMepdYgc2rgvFeBIzVXks4WB1diTl7NX3nJFcyjymLSQfnUR48QN3usj_LvBeel16adftjRYcmL-WobmCcipNQ8k7HfGG6xzzeY-a3LFoPU/s320/screenshot2.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613603974006962&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Step 2 - Set Up Privacy&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Go to the &lt;a href=&quot;http://www.facebook.com/#%21/settings/?tab=privacy&amp;amp;ref=mb&quot;&gt;privacy settings&lt;/a&gt;.  Then choose &quot;Custom&quot; and then &quot;Customize Settings&quot;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjds0MjP5UHR00ubhg3dG4J8uiVkUcxUpyldRal0CVFkaF8rdDANC7FDk0CQUfXOdRE7qaUGKlpf0vX_52raGoEGGbS2L1l3QV0uaF3cA5HfaoKUXNoDRD9mUfLp6n6Q4pW0PrXLMCPcNU/s1600/screenshot3.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 278px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjds0MjP5UHR00ubhg3dG4J8uiVkUcxUpyldRal0CVFkaF8rdDANC7FDk0CQUfXOdRE7qaUGKlpf0vX_52raGoEGGbS2L1l3QV0uaF3cA5HfaoKUXNoDRD9mUfLp6n6Q4pW0PrXLMCPcNU/s320/screenshot3.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613275859530962&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;For each item that you want to restrict (I do all of them), select the drop down, and choose &quot;Custom&quot;&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghbalNBw739IJKVi6d5HbDiMKXVXCf0ILwBzV-tg-WC0ZHor37E7k8EK5Z_K_UsiyxCSxoisXDcjMMSF6vfbRFNOTxrxtDDwW8ao-E7YwZ0vfcvo0d9oHn-ITmrDEwJQ0iibXXMUMaWLE/s1600/screenshot4.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 228px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghbalNBw739IJKVi6d5HbDiMKXVXCf0ILwBzV-tg-WC0ZHor37E7k8EK5Z_K_UsiyxCSxoisXDcjMMSF6vfbRFNOTxrxtDDwW8ao-E7YwZ0vfcvo0d9oHn-ITmrDEwJQ0iibXXMUMaWLE/s320/screenshot4.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613271777513314&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Then say &quot;Specific People&quot; and choose the &quot;Full Access&quot; list (or whatever you called it)&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitzEjmwG5xxbxJQ5VE-35dnhHG5dIL1ygoWrh50WmZrJ4crpvAlLt5ygnn7ECFs6k44OEZpqO7mcomVR2LhEDmxGeoxqkQMZjt-obkalty7ZxD-510GM7g_N02ShOx0_46BheS5Ce6dXg/s1600/screenshot5.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 196px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitzEjmwG5xxbxJQ5VE-35dnhHG5dIL1ygoWrh50WmZrJ4crpvAlLt5ygnn7ECFs6k44OEZpqO7mcomVR2LhEDmxGeoxqkQMZjt-obkalty7ZxD-510GM7g_N02ShOx0_46BheS5Ce6dXg/s320/screenshot5.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613263239151938&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;You can choose what you want each group to see.  If you only want the &quot;Full Access&quot; people to see something, do what I mentioned above. &lt;br /&gt;&lt;br /&gt;If you want all of your friends except the &quot;No Access&quot; group, do this:&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_IV4_Tv-zE1bXcKNI9Lh4yJUagfrZoufG-T9yWnmouNZP2Mf1DdkDWb8ckkNFQH_z8CBtkMtjE8NV-rNnSS3jnifTyuPHlCoQGQOg4yEbFj_jgMbB8SXRy_7ic5MQ9HMFw2qpAhOytDs/s1600/screenshot6.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 220px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_IV4_Tv-zE1bXcKNI9Lh4yJUagfrZoufG-T9yWnmouNZP2Mf1DdkDWb8ckkNFQH_z8CBtkMtjE8NV-rNnSS3jnifTyuPHlCoQGQOg4yEbFj_jgMbB8SXRy_7ic5MQ9HMFw2qpAhOytDs/s320/screenshot6.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613260591102914&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;Note: if you say &quot;Everyone&quot; that is everyone in the world, not just your friends.&lt;br /&gt;&lt;br /&gt;Repeat this for all the items under &quot;Things I share,&quot; &quot;Things others share,&quot; and &quot;Contact information.&quot;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Step 3 - Protect your pictures&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Privacy for your photos is set up per album.  Go to &lt;a href=&quot;http://www.facebook.com/privacy/?view=photos&quot;&gt;this link&lt;/a&gt; to change the privacy settings for each album.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Step 4 - Preview your changes&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;One useful tool Facebook provides is allowing you to see your profile as your friends see it.  Go back to the privacy setting in step 2 and click the button at the top labeled &quot;Preview My Profile.&quot;  Click that and you will be able to type in a friend&#39;s name and see what they will see of your profile.  Try it with people in your new lists.&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR2EYXUU2gTjYihKp56aafBmhofIjhzm_BNroFq7bXuLmbk5X7jJyKO_aCg0Itr5tw3g8wZFIdsM4fBRRKBn_xHbSaYP3xMRxDnL-SZ_J4wxakR-8Av-cpSHZJNHElczcRUZzWs1aXbDw/s1600/screenshot7.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 42px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR2EYXUU2gTjYihKp56aafBmhofIjhzm_BNroFq7bXuLmbk5X7jJyKO_aCg0Itr5tw3g8wZFIdsM4fBRRKBn_xHbSaYP3xMRxDnL-SZ_J4wxakR-8Av-cpSHZJNHElczcRUZzWs1aXbDw/s320/screenshot7.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5498613252407861154&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you see something that doesn&#39;t look right, go back and update your privacy settings.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Summary&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This should be enough to get you started.  If you want to be more specific, just create more than two friend lists.  For example, I have a &quot;Work People&quot; list that can see my employers and education, but most of those people aren&#39;t in the &quot;Full Access&quot; list so they can&#39;t see all my pictures.&lt;br /&gt;&lt;br /&gt;If you have any questions, let me know.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/1077385551274140559/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/my-facebook-privacy-settings.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/1077385551274140559'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/1077385551274140559'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/my-facebook-privacy-settings.html' title='My Facebook Privacy Settings'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_Tfdzb-dOMCCBIy0Gh1o_aHyd-AI8ri8zn1NmJ8jImR6WyflyVJGHzxkK4cppodfqFp1wc4NKIrIsip2vkuPG694gA-lyRD4A0ns9uhoDPW7y8n7bleawUtTYb-04LJezsJYcQ3zx6V0/s72-c/screenshot1.jpg" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-7299670242115478030</id><published>2010-07-27T08:54:00.002-05:00</published><updated>2010-07-27T08:56:48.604-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="code"/><title type='text'>Route Finding Code</title><content type='html'>I uploaded the full source-code&lt;a href=&quot;http://www.box.net/shared/7zk5qqqxjv&quot;&gt;&lt;/a&gt; for the &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/groovyjava-problem-solving-agent.html&quot;&gt;Route Finding Problem&lt;/a&gt; and the &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/extending-to-traveling-salesman-problem.html&quot;&gt;Touring Problem&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://www.box.net/shared/7zk5qqqxjv&quot;&gt;Source Code&lt;/a&gt; (box.net)</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/7299670242115478030/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/route-finding-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7299670242115478030'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/7299670242115478030'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/route-finding-code.html' title='Route Finding Code'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-3758670475750785401</id><published>2010-07-19T14:27:00.004-05:00</published><updated>2010-08-20T07:49:58.185-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><category scheme="http://www.blogger.com/atom/ns#" term="java"/><category scheme="http://www.blogger.com/atom/ns#" term="problem solving"/><title type='text'>Extending to Traveling Salesman Problem</title><content type='html'>My next step in my A.I. investigation was to extend the &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/groovyjava-problem-solving-agent.html&quot;&gt;route-finding problem&lt;/a&gt; to do a touring problem.  This is basically a simplified &lt;a href=&quot;http://en.wikipedia.org/wiki/Travelling_salesman_problem&quot;&gt;traveling salesman problem&lt;/a&gt; where you pick a starting node and have to visit each node one time.&lt;br /&gt;&lt;br /&gt;This turned out to be pretty easy with the code I already had.  I first created a new interface called Goal with just one method:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;public interface GoalInterface {&lt;br /&gt;  public boolean goalMet(State currentState);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;For the existing route finding problem, I moved the goalTest method to an implementation of this interface:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;@Override&lt;br /&gt;public boolean goalMet(State currentState) {&lt;br /&gt;  return currentState.getCurrentNode().getNodeName()&lt;br /&gt;          .equalsIgnoreCase(goalNode.getNodeName());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Note that the constructor for this goal takes in the goalNode&lt;br /&gt;&lt;br /&gt;Then I just updated the RouteFindingProblem to take a Goal (described below) instead of an goalNode.&lt;br /&gt;&lt;br /&gt;The new signature for solve:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;public Path solve(final Node initialNode, final GoalInterface goal) { }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And instead of calling the goalTest method, I just call goal.goalMet(). At this point the initial route-finiding problem produces the same results and all the tests still pass.&lt;br /&gt;&lt;br /&gt;For the touring problem, I just had to create a new goal.  The goalMet() method just checks if every node has been visited by calling this method:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;private boolean isEveryNodeVisited(final State currentState) {&lt;br /&gt;  boolean tourComplete = true;&lt;br /&gt;  final List&amp;lt;Action&amp;gt; currentPathActions = currentState.getCurrentPath()&lt;br /&gt;          .getActions();&lt;br /&gt;  final Set&amp;lt;Node&amp;gt; currentlyVisitedNodes = new HashSet&amp;lt;Node&amp;gt;();&lt;br /&gt;  for (Action nextPathAction : currentPathActions) {&lt;br /&gt;      currentlyVisitedNodes.add(nextPathAction.resultOfAction(allNodes));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  // make sure all nodes were visited&lt;br /&gt;  for (Node nextNode : allNodes) {&lt;br /&gt;      if (!currentlyVisitedNodes.contains(nextNode)&lt;br /&gt;              &amp;amp;&amp;amp; !nextNode.getNodeName().equalsIgnoreCase(&lt;br /&gt;                      initialNode.getNodeName())) {&lt;br /&gt;          tourComplete = false;&lt;br /&gt;          break;&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;  return tourComplete;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A couple things about this.  First, it is not very efficient, but for my testing that is not a big deal.  Next, I had to add an extra check for the initialNode since the path that is stored in currentState has a list of actions after the initialNode, but does not include the initialNode.&lt;br /&gt;&lt;br /&gt;The last thing I had to do was simplify the map.  Since my &lt;a href=&quot;http://books.google.com/books?id=8jZBksh-bUMC&amp;amp;lpg=PP1&amp;amp;dq=artificial%20intelligence&amp;amp;pg=PA68#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;map of Romania&lt;/a&gt; had a few branches that had only one route, there was no way to visit each city only one time.  I just &quot;trimmed&quot; the branches and ended up with these &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/adding-segments.html&quot;&gt;segments&lt;/a&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;segments = [&lt;br /&gt;      new Segment(start:&quot;Arad&quot;, end:&quot;Zerind&quot;, cost:75),&lt;br /&gt;      new Segment(start:&quot;Arad&quot;, end:&quot;Sibiu&quot;, cost:140),&lt;br /&gt;      new Segment(start:&quot;Arad&quot;, end:&quot;Timisoara&quot;, cost:118),&lt;br /&gt;      new Segment(start:&quot;Bucharest&quot;, end:&quot;Fagaras&quot;, cost:211),&lt;br /&gt;      new Segment(start:&quot;Bucharest&quot;, end:&quot;Pitesti&quot;, cost:101),&lt;br /&gt;      new Segment(start:&quot;Craiova&quot;, end:&quot;Drobeta&quot;, cost:120),&lt;br /&gt;      new Segment(start:&quot;Craiova&quot;, end:&quot;Pitesti&quot;, cost:138),&lt;br /&gt;      new Segment(start:&quot;Craiova&quot;, end:&quot;Rimnieu Vilcea&quot;, cost:146),&lt;br /&gt;      new Segment(start:&quot;Drobeta&quot;, end:&quot;Mehadia&quot;, cost:75),&lt;br /&gt;      new Segment(start:&quot;Fagaras&quot;, end:&quot;Sibiu&quot;, cost:99),&lt;br /&gt;      new Segment(start:&quot;Lugoj&quot;, end:&quot;Mehadia&quot;, cost:70),&lt;br /&gt;      new Segment(start:&quot;Lugoj&quot;, end:&quot;Timisoara&quot;, cost:111),&lt;br /&gt;      new Segment(start:&quot;Oradea&quot;, end:&quot;Sibiu&quot;, cost:151),&lt;br /&gt;      new Segment(start:&quot;Oradea&quot;, end:&quot;Zerind&quot;, cost:71),&lt;br /&gt;      new Segment(start:&quot;Pitesti&quot;, end:&quot;Rimnieu Vilcea&quot;, cost:97),&lt;br /&gt;      new Segment(start:&quot;Rimnieu Vilcea&quot;, end:&quot;Sibiu&quot;, cost:80)&lt;br /&gt;  ];&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then I ran these tests in Groovy:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;//make sure each node results in a path&lt;br /&gt;nodes.each {assert problem.solve(it, new TouringGoal(it, nodes)) != null}&lt;br /&gt;//check a couple paths&lt;br /&gt;assert 1327 == (problem.solve(findNode(nodes, &quot;Arad&quot;), new TouringGoal(findNode(nodes, &quot;Arad&quot;), nodes))).getPathCost();&lt;br /&gt;assert 1294 == (problem.solve(findNode(nodes, &quot;Sibiu&quot;), new TouringGoal(findNode(nodes, &quot;Sibiu&quot;), nodes))).getPathCost();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And finally I did a test to display the results in Groovy:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;Node testNode = findNode(nodes, &quot;Bucharest&quot;);&lt;br /&gt;Path resultPath = problem.solve(testNode, new TouringGoal(testNode, nodes));&lt;br /&gt;//display the results&lt;br /&gt;println(&quot;Shortest tour from &quot; + testNode.toString());&lt;br /&gt;if(resultPath == null){&lt;br /&gt;  println(&quot;No route found&quot;);&lt;br /&gt;} else {&lt;br /&gt;  for (Action nextAction : resultPath.getActions()) {&lt;br /&gt;      println(nextAction.toString());&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With the results:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;Shortest tour from In(Bucharest)&lt;br /&gt;Go(Pitesti)(101)&lt;br /&gt;Go(Rimnieu Vilcea)(97)&lt;br /&gt;Go(Craiova)(146)&lt;br /&gt;Go(Drobeta)(120)&lt;br /&gt;Go(Mehadia)(75)&lt;br /&gt;Go(Lugoj)(70)&lt;br /&gt;Go(Timisoara)(111)&lt;br /&gt;Go(Arad)(118)&lt;br /&gt;Go(Zerind)(75)&lt;br /&gt;Go(Oradea)(71)&lt;br /&gt;Go(Sibiu)(151)&lt;br /&gt;Go(Fagaras)(99)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let me know if you have any questions or suggestions on improvements!&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Update 7/27/10: &lt;/span&gt;Uploaded the &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/route-finding-code.html&quot;&gt;source code&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/3758670475750785401/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/extending-to-traveling-salesman-problem.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/3758670475750785401'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/3758670475750785401'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/extending-to-traveling-salesman-problem.html' title='Extending to Traveling Salesman Problem'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-898401758887658927</id><published>2010-07-17T08:47:00.001-05:00</published><updated>2010-07-25T20:48:26.219-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="problem solving"/><title type='text'>Route Finding Applied</title><content type='html'>Now that I implemented a &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/groovyjava-problem-solving-agent.html&quot;&gt;simple route-finding agent&lt;/a&gt;, I thought I would try to apply it to a real-world situation: the shortest route to my office.&lt;br /&gt;&lt;br /&gt;I created a new map that looks like this:&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglx7BIEzuL-iouavLFj_iSneynsX-XB5N9zwpgvY-tjNmBJbYjvvcQ48s27AqWRtMfKNLSRZlM6ShgEk6FSqEDMbMy4d3vNhjmpMkoCmG48KD2QWKI5CEYp6tEKEsguZWk9tSuqQpx4qs/s1600/home_work_map.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 263px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglx7BIEzuL-iouavLFj_iSneynsX-XB5N9zwpgvY-tjNmBJbYjvvcQ48s27AqWRtMfKNLSRZlM6ShgEk6FSqEDMbMy4d3vNhjmpMkoCmG48KD2QWKI5CEYp6tEKEsguZWk9tSuqQpx4qs/s320/home_work_map.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5494531881333786498&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;My house is at node &quot;P&quot; and work is node &quot;A.&quot;  The weights are based on the distance and speed limits and are basically the amount of time in minutes * 10 (just to get some rounded numbers).&lt;br /&gt;&lt;br /&gt;To my surprise, the shortest route is not the route I typically take to work.  The path ended up being:&lt;br /&gt;&lt;pre style=&quot;font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;&quot;&gt;&lt;code&gt;P -&amp;gt; O -&amp;gt; N -&amp;gt; M -&amp;gt; L -&amp;gt; K -&amp;gt; J -&amp;gt; I -&amp;gt; D -&amp;gt; C -&amp;gt; B -&amp;gt; A&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;I will have to try this new route on the way to work on Monday!&lt;br /&gt;&lt;br /&gt;This does not take into account things like stoplights, which I&#39;m sure will have an effect on the overall time.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/898401758887658927/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/route-finding-applied.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/898401758887658927'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/898401758887658927'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/route-finding-applied.html' title='Route Finding Applied'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglx7BIEzuL-iouavLFj_iSneynsX-XB5N9zwpgvY-tjNmBJbYjvvcQ48s27AqWRtMfKNLSRZlM6ShgEk6FSqEDMbMy4d3vNhjmpMkoCmG48KD2QWKI5CEYp6tEKEsguZWk9tSuqQpx4qs/s72-c/home_work_map.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-2381025752291028144</id><published>2010-07-16T08:16:00.003-05:00</published><updated>2010-08-20T07:52:26.388-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><category scheme="http://www.blogger.com/atom/ns#" term="java"/><title type='text'>Adding Segments</title><content type='html'>I made a small update to my &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/groovyjava-problem-solving-agent.html&quot;&gt;previous solution&lt;/a&gt; to the problem-solving agent.  The developer in me hated the duplicate &quot;code&quot; of when I defined the nodes.  For example, when I defined that Arad was 75 units from Zerind, I also had to define that Zerind was 75 units from Arad.  For this example that was not huge, but it could lead to errors.&lt;br /&gt;&lt;br /&gt;Here is what it was:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;nodes = [&lt;br /&gt;   createNode(&quot;Arad&quot;, [&quot;Zerind&quot;:75, &quot;Sibiu&quot;:140, &quot;Timisoara&quot;:118]),&lt;br /&gt;   ...&lt;br /&gt;   createNode(&quot;Zerind&quot;, [&quot;Oradea&quot;:71, &quot;Arad&quot;:75])&lt;br /&gt;];&lt;/pre&gt;&lt;br /&gt;To make it a little less error-prone, I added the idea of a &quot;segment.&quot;  Now I just have to define each segment one time instead of twice.  It looks like this:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;segments = [&lt;br /&gt;   new Segment(start:&quot;Arad&quot;, end:&quot;Zerind&quot;, cost:75),&lt;br /&gt;   new Segment(start:&quot;Arad&quot;, end:&quot;Sibiu&quot;, cost:140),&lt;br /&gt;   new Segment(start:&quot;Arad&quot;, end:&quot;Timisoara&quot;, cost:118),&lt;br /&gt;   new Segment(start:&quot;Bucharest&quot;, end:&quot;Fagaras&quot;, cost:211),&lt;br /&gt;   new Segment(start:&quot;Bucharest&quot;, end:&quot;Giurgiu&quot;, cost:90),&lt;br /&gt;   new Segment(start:&quot;Bucharest&quot;, end:&quot;Pitesti&quot;, cost:101),&lt;br /&gt;   new Segment(start:&quot;Bucharest&quot;, end:&quot;Urziemi&quot;, cost:85),&lt;br /&gt;   new Segment(start:&quot;Craiova&quot;, end:&quot;Drobeta&quot;, cost:120),&lt;br /&gt;   new Segment(start:&quot;Craiova&quot;, end:&quot;Pitesti&quot;, cost:138),&lt;br /&gt;   new Segment(start:&quot;Craiova&quot;, end:&quot;Rimnieu Vilcea&quot;, cost:146),&lt;br /&gt;   new Segment(start:&quot;Drobeta&quot;, end:&quot;Mehadia&quot;, cost:75),&lt;br /&gt;   new Segment(start:&quot;Eforie&quot;, end:&quot;Hirsova&quot;, cost:86),&lt;br /&gt;   new Segment(start:&quot;Fagaras&quot;, end:&quot;Sibiu&quot;, cost:99),&lt;br /&gt;   new Segment(start:&quot;Hirsova&quot;, end:&quot;Urziemi&quot;, cost:98),&lt;br /&gt;   new Segment(start:&quot;Iasi&quot;, end:&quot;Neamt&quot;, cost:87),&lt;br /&gt;   new Segment(start:&quot;Iasi&quot;, end:&quot;Vaslui&quot;, cost:92),&lt;br /&gt;   new Segment(start:&quot;Lugoj&quot;, end:&quot;Mehadia&quot;, cost:70),&lt;br /&gt;   new Segment(start:&quot;Lugoj&quot;, end:&quot;Timisoara&quot;, cost:111),&lt;br /&gt;   new Segment(start:&quot;Oradea&quot;, end:&quot;Sibiu&quot;, cost:151),&lt;br /&gt;   new Segment(start:&quot;Oradea&quot;, end:&quot;Zerind&quot;, cost:71),&lt;br /&gt;   new Segment(start:&quot;Pitesti&quot;, end:&quot;Rimnieu Vilcea&quot;, cost:97),&lt;br /&gt;   new Segment(start:&quot;Rimnieu Vilcea&quot;, end:&quot;Sibiu&quot;, cost:80),&lt;br /&gt;   new Segment(start:&quot;Urziemi&quot;, end:&quot;Vaslui&quot;, cost:142),&lt;br /&gt;];&lt;br /&gt;&lt;/pre&gt;Then in the Segment class I created a method to convert a list of Segments into a list of Nodes so I just needed to do this in my groovy script to have the same information I did before:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;nodes = Segment.convertSegmentsToNodes(segments);&lt;/pre&gt;&lt;br /&gt;I also had to move the createActionList and createNode methods into the Segment class, which helped clean up my groovy script.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/2381025752291028144/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/adding-segments.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/2381025752291028144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/2381025752291028144'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/adding-segments.html' title='Adding Segments'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-5614352455964059195</id><published>2010-07-14T12:54:00.013-05:00</published><updated>2010-08-20T07:55:46.685-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="code"/><category scheme="http://www.blogger.com/atom/ns#" term="groovy"/><category scheme="http://www.blogger.com/atom/ns#" term="java"/><category scheme="http://www.blogger.com/atom/ns#" term="problem solving"/><title type='text'>Groovy/Java Problem Solving Agent</title><content type='html'>Today I&#39;m going to look at implementing a simple problem-solving agent using Java and Groovy. This is based on chapter 3 in my old &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/ai-reference-book.html&quot;&gt;A.I. textbook&lt;/a&gt;, &quot;Solving Problems By Searching.&quot;  I am going to tackle the route-finding problem which takes a number of cities in Romania (&lt;a href=&quot;http://books.google.com/books?id=8jZBksh-bUMC&amp;amp;lpg=PP1&amp;amp;dq=artificial%20intelligence&amp;amp;pg=PA68#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;p 68 on Google Books&lt;/a&gt;) with paths between them with the goal of having to find the shortest route between any two cities.&lt;br /&gt;&lt;br /&gt;Let&#39;s get started. First, I created a few simple Java classes to represent a Node (a city), an Action (go from one node to another), a Path (list of Actions that have gotten me where I am now), and a State (what Node am I at and how did I get here).  These classes are not that interesting, but here are the basics of each one without the implementations:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Node&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;public class Node {&lt;br /&gt;  public Node(String nodeName, List availableActions) {  }&lt;br /&gt;  public String getNodeName() {  }&lt;br /&gt;  public List getAvailableActions() {  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Action&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;public class Action {&lt;br /&gt;  public Action(String actionName, int stepCost) { }&lt;br /&gt;  public Node resultOfAction(List availableNodes) {&lt;br /&gt;      //if I execute this action, where am I?&lt;br /&gt;  }&lt;br /&gt;  public int getStepCost() { }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Path&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;public class Path {&lt;br /&gt;  public void addAction(Action action) {  }&lt;br /&gt;  public int getPathCost() {  }&lt;br /&gt;  public List&amp;lt;action&amp;gt; getActions() {  }&lt;br /&gt;  public Path clone() {  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;State&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;public class State {&lt;br /&gt;  public State(Node currentNode, Path currentPath, List closedNodes){ }&lt;br /&gt;  public Node getCurrentNode() { }&lt;br /&gt;  public Path getCurrentPath() { }&lt;br /&gt;  public List getClosedNodes() { }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;I also created a couple simple data structures to represent a &lt;a href=&quot;http://en.wikipedia.org/wiki/Queue_%28data_structure%29&quot;&gt;Queue&lt;/a&gt; and a &lt;a href=&quot;http://en.wikipedia.org/wiki/Stack_%28data_structure%29&quot;&gt;Stack&lt;/a&gt; (called SimpleQueue and SimpleStack).  When searching for the solution, I needed to decide if I wanted to do a Depth-first search (using a Stack - LIFO) or a Breadth-first search (Queue - FIFO).  I decided to use a depth-first search since I tried to be a little smart about only finding solutions that are shorter than any others I&#39;ve already found.  By doing a depth-first search, I tried to expand the search tree until I found a solution, then went back and tried to find other solutions that were shorter.&lt;br /&gt;&lt;br /&gt;Now is where the actual searching happens.  I created another Java class called RouteFindingProblem.  I&#39;ll describe each part of that class starting with the &quot;solve&quot; method:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;solve&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;public Path solve(final Node initialNode, final Node goalNode) {&lt;br /&gt;  if (initialNode.getNodeName().equalsIgnoreCase(goalNode.getNodeName())) {&lt;br /&gt;      resultPath = new Path(new Action(&quot;Stay&quot; + initialNode.getNodeName(), 0));&lt;br /&gt;  } else {&lt;br /&gt;      resultPath = null;&lt;br /&gt;      lowestPathCost = Integer.MAX_VALUE;&lt;br /&gt;&lt;br /&gt;      // find the paths&lt;br /&gt;      doSearch(initialNode, goalNode);&lt;br /&gt;  }&lt;br /&gt;  return resultPath;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;This is the entry point into the solving logic.  It will return the shortest path from the initialNode to the goalNode. The first check is just to make sure the initialNode is not the same as the goalNode.  If it is I am done and I&#39;ll just create a new &quot;stay&quot; action.  Next (in the else statement) I initialize some variables and then call doSearch.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;doSearch&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;private void doSearch(final Node initialNode, final Node goalNode) {&lt;br /&gt;  stateDataStructure = new SimpleStack&amp;lt;State&amp;gt;();&lt;br /&gt;&lt;br /&gt;  // create the first state&lt;br /&gt;  stateDataStructure&lt;br /&gt;      .put(new State(initialNode, new Path(), new ArrayList&amp;lt;node&amp;gt;()));&lt;br /&gt;&lt;br /&gt;  while (!stateDataStructure.isEmpty()) {&lt;br /&gt;      if (goalTest(stateDataStructure.peek(), goalNode)) {&lt;br /&gt;          handleGoalReached(stateDataStructure.get());&lt;br /&gt;      } else {&lt;br /&gt;          successorFunction(stateDataStructure.get(), goalNode);&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;I basically create the first state and add it to the stack.  Then I keep going until the stack is empty.  If I&#39;ve reached the goal, then I just check if this is the shortest path  (in handleGoalReached).  If I haven&#39;t reached the goal, then I find all the successors of this state and add them to the Stack.&lt;br /&gt;&lt;br /&gt;The last interesting piece of code in this class is the successorFunction method.  I don&#39;t love this name, but that is what they call it my book.  It would be more appropriately called &quot;addSubStates&quot; or something.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;successorFunciton&lt;/span&gt;:&lt;br /&gt;&lt;pre class=&quot;brush: java&quot;&gt;&lt;br /&gt;private void successorFunction(final State currentState, final Node goalNode) {&lt;br /&gt;  // make a copy of the closed list for each sub state&lt;br /&gt;  final List&amp;lt;Node&amp;gt; cloneClosedList = cloneClosedList(currentState&lt;br /&gt;      .getClosedNodes());&lt;br /&gt;  cloneClosedList.add(currentState.getCurrentNode());&lt;br /&gt;&lt;br /&gt;  // for each action after this, add it to the queue&lt;br /&gt;  for (Action nextAction : currentState.getCurrentNode()&lt;br /&gt;      .getAvailableActions()) {&lt;br /&gt;      final Node nextNode = nextAction.resultOfAction(nodes);&lt;br /&gt;      // make sure I haven&#39;t already checked this node (circular)&lt;br /&gt;      if (!cloneClosedList.contains(nextNode)) {&lt;br /&gt;          final Path clonePath = currentState.getCurrentPath().clone();&lt;br /&gt;          clonePath.addAction(nextAction);&lt;br /&gt;&lt;br /&gt;          // only add the next state if the current path is&lt;br /&gt;          // lower than any previous paths&lt;br /&gt;          if (clonePath.getPathCost() &amp;lt; lowestPathCost) {&lt;br /&gt;              stateDataStructure.put(new State(nextNode, clonePath,&lt;br /&gt;                  cloneClosedList));&lt;br /&gt;          }&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;In a nutshell, this just finds the next set of states and adds them to the stack.&lt;br /&gt;&lt;br /&gt;All that is left is to set up the actual data and run some tests.  To do this I used Groovy, partly because I thought it would be easier to script some of the setup and tests, and partly because I don&#39;t know much about Groovy so this was a chance for me to learn more about it.&lt;br /&gt;&lt;br /&gt;First off, I created some helper methods (&lt;a href=&quot;http://groovy.codehaus.org/Closures&quot;&gt;closures&lt;/a&gt;) to use in my Groovy script.&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;createActionList = {actionMap -&amp;gt;&lt;br /&gt;  List&amp;lt;Action&amp;gt; actions = new ArrayList&amp;lt;Action&amp;gt;();&lt;br /&gt;  actionMap.each {place,cost -&amp;gt; actions.add(new Action(&quot;Go(&quot; + place + &quot;)&quot;, cost)) };&lt;br /&gt;  return actions;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;createNode = {nodeName, actionMap -&amp;gt;&lt;br /&gt;  return new Node(&quot;In(&quot; + nodeName + &quot;)&quot;, createActionList(actionMap));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;findNode = {nodeList, nodeToFind -&amp;gt;&lt;br /&gt;  for(nextNode in nodeList){&lt;br /&gt;      if(nextNode != null &amp;amp;&amp;amp; nextNode.getNodeName().equalsIgnoreCase(&quot;In(&quot; + nodeToFind + &quot;)&quot;)){&lt;br /&gt;          return nextNode;&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Then I set up all the nodes in this problem (cities in Romania):&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;nodes = [&lt;br /&gt;  createNode(&quot;Arad&quot;, [&quot;Zerind&quot;:75, &quot;Sibiu&quot;:140, &quot;Timisoara&quot;:118]),&lt;br /&gt;  createNode(&quot;Bucharest&quot;, [&quot;Pitesti&quot;:101, &quot;Fagaras&quot;:211, &quot;Giurgiu&quot;:90, &quot;Urziemi&quot;:85]),&lt;br /&gt;  createNode(&quot;Craiova&quot;, [&quot;Drobeta&quot;:120, &quot;Rimnieu Vilcea&quot;:146, &quot;Pitesti&quot;:138]),&lt;br /&gt;  createNode(&quot;Drobeta&quot;, [&quot;Mehadia&quot;:75, &quot;Craiova&quot;:120]),&lt;br /&gt;  createNode(&quot;Eforie&quot;, [&quot;Hirsova&quot;:86]),&lt;br /&gt;  createNode(&quot;Fagaras&quot;, [&quot;Sibiu&quot;:99, &quot;Bucharest&quot;:211]),&lt;br /&gt;  createNode(&quot;Giurgiu&quot;, [&quot;Bucharest&quot;:90]),&lt;br /&gt;  createNode(&quot;Hirsova&quot;, [&quot;Urziemi&quot;:98, &quot;Eforie&quot;:86]),&lt;br /&gt;  createNode(&quot;Iasi&quot;, [&quot;Vaslui&quot;:92, &quot;Neamt&quot;:87]),&lt;br /&gt;  createNode(&quot;Lugoj&quot;, [&quot;Timisoara&quot;:111, &quot;Mehadia&quot;:70]),&lt;br /&gt;  createNode(&quot;Mehadia&quot;, [&quot;Lugoj&quot;:70, &quot;Drobeta&quot;:75]),&lt;br /&gt;  createNode(&quot;Neamt&quot;, [&quot;Iasi&quot;:87]),&lt;br /&gt;  createNode(&quot;Oradea&quot;, [&quot;Zerind&quot;:71, &quot;Sibiu&quot;:151]),&lt;br /&gt;  createNode(&quot;Pitesti&quot;, [&quot;Rimnieu Vilcea&quot;:97, &quot;Craiova&quot;:138, &quot;Bucharest&quot;:101]),&lt;br /&gt;  createNode(&quot;Rimnieu Vilcea&quot;, [&quot;Sibiu&quot;:80, &quot;Craiova&quot;:146, &quot;Pitesti&quot;:97]),&lt;br /&gt;  createNode(&quot;Sibiu&quot;, [&quot;Oradea&quot;:151, &quot;Arad&quot;: 140, &quot;Fagaras&quot;:999, &quot;Rimnieu Vilcea&quot;:80]),&lt;br /&gt;  createNode(&quot;Timisoara&quot;, [&quot;Arad&quot;:118, &quot;Lugoj&quot;:70]),&lt;br /&gt;  createNode(&quot;Urziemi&quot;, [&quot;Bucharest&quot;:85, &quot;Vaslui&quot;:142, &quot;Hirsova&quot;:98]),&lt;br /&gt;  createNode(&quot;Vaslui&quot;, [&quot;Urziemi&quot;:142, &quot;Iasi&quot;:92]),&lt;br /&gt;  createNode(&quot;Zerind&quot;, [&quot;Oradea&quot;:71, &quot;Arad&quot;:75])&lt;br /&gt;];&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;Update:&lt;/b&gt;See &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/adding-segments.html&quot;&gt;this post&lt;/a&gt; about defining Segments instead of Nodes.&lt;br /&gt;&lt;br /&gt;Then I set up some Groovy unit tests to make sure this was working as expected:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;RouteFindingProblem problem = new RouteFindingProblem(nodes);&lt;br /&gt;&lt;br /&gt;//same initial/goal&lt;br /&gt;assert 0 == (problem.solve(findNode(nodes, &quot;Arad&quot;), findNode(nodes, &quot;Arad&quot;))).getPathCost();&lt;br /&gt;//neighbors&lt;br /&gt;assert 75 == (problem.solve(findNode(nodes, &quot;Arad&quot;), findNode(nodes, &quot;Zerind&quot;))).getPathCost();&lt;br /&gt;assert 75 == (problem.solve(findNode(nodes, &quot;Zerind&quot;), findNode(nodes, &quot;Arad&quot;))).getPathCost();&lt;br /&gt;//2 hops&lt;br /&gt;assert 215 == (problem.solve(findNode(nodes, &quot;Zerind&quot;), findNode(nodes, &quot;Sibiu&quot;))).getPathCost();&lt;br /&gt;//one end to the other&lt;br /&gt;assert 698 == (problem.solve(findNode(nodes, &quot;Oradea&quot;), findNode(nodes, &quot;Eforie&quot;))).getPathCost();&lt;/pre&gt;&lt;br /&gt;Finally I did a test to print out the path from one end of Romania to the other:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;Path resultPath = problem.solve(findNode(nodes, &quot;Oradea&quot;), findNode(nodes, &quot;Eforie&quot;));&lt;br /&gt;//display the results&lt;br /&gt;println(&quot;Shortest path from &quot; + findNode(nodes, &quot;Oradea&quot;).toString() + &quot; to &quot; + findNode(nodes, &quot;Eforie&quot;).toString());&lt;br /&gt;for (Action nextAction : resultPath.getActions()) {&lt;br /&gt;  println(nextAction.toString());&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The output is:&lt;br /&gt;&lt;pre class=&quot;brush: groovy&quot;&gt;&lt;br /&gt;Shortest path from In(Oradea) to In(Eforie)&lt;br /&gt;Go(Sibiu)(151)&lt;br /&gt;Go(Rimnieu Vilcea)(80)&lt;br /&gt;Go(Pitesti)(97)&lt;br /&gt;Go(Bucharest)(101)&lt;br /&gt;Go(Urziemi)(85)&lt;br /&gt;Go(Hirsova)(98)&lt;br /&gt;Go(Eforie)(86)&lt;/pre&gt;&lt;br /&gt;Next Steps:&lt;br /&gt;This is just one simple problem-solving agent.  I tried to make this fairly generic, but to implement the other examples from the book (sliding block puzzle, 8-queens problem, touring problem), it will take a little work.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Update 7/27/10: &lt;/span&gt;Uploaded the &lt;a href=&quot;http://geekmatt.blogspot.com/2010/07/route-finding-code.html&quot;&gt;source  code&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/5614352455964059195/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/groovyjava-problem-solving-agent.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/5614352455964059195'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/5614352455964059195'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/groovyjava-problem-solving-agent.html' title='Groovy/Java Problem Solving Agent'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-8840551178728378680</id><published>2010-07-12T14:10:00.003-05:00</published><updated>2010-07-12T14:18:38.137-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="artificialintelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="book"/><title type='text'>A.I. Reference Book</title><content type='html'>&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-eGFUiVeejO9y8pny74qEubnh2xQINdv1gJUzrLVmjNaEedruFJjB9h3BS7goSW-uChe7LiivZVjDrLewYwG8bnKD0fZ6cmntxcLamPxrssuNhsTq-RyntDuYIuFrokeaV3XfdBiQVII/s1600/ai_book.jpg&quot;&gt;&lt;img style=&quot;display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 258px; height: 320px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-eGFUiVeejO9y8pny74qEubnh2xQINdv1gJUzrLVmjNaEedruFJjB9h3BS7goSW-uChe7LiivZVjDrLewYwG8bnKD0fZ6cmntxcLamPxrssuNhsTq-RyntDuYIuFrokeaV3XfdBiQVII/s320/ai_book.jpg&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5493099387210422050&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;&lt;br /&gt;While doing my graduate work, I took a class on Artificial  Intelligence.  It was very interesting though I haven&#39;t had much time  until now to look into it more.  I am looking back though the textbook:  Artificial Intelligence: A Modern Approach (2nd Edition).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There is a limited version of the book at &lt;a href=&quot;http://books.google.com/books?id=8jZBksh-bUMC&amp;amp;printsec=frontcover&amp;amp;dq=artificial+intelligence&amp;amp;hl=en&amp;amp;ei=tig7TPLMHpKfccnhkfsO&amp;amp;sa=X&amp;amp;oi=book_result&amp;amp;ct=result&amp;amp;resnum=1&amp;amp;ved=0CCUQ6AEwAA#v=onepage&amp;amp;q&amp;amp;f=false&quot; mce_href=&quot;http://books.google.com/books?id=8jZBksh-bUMC&amp;amp;printsec=frontcover&amp;amp;dq=artificial+intelligence&amp;amp;hl=en&amp;amp;ei=tig7TPLMHpKfccnhkfsO&amp;amp;sa=X&amp;amp;oi=book_result&amp;amp;ct=result&amp;amp;resnum=1&amp;amp;ved=0CCUQ6AEwAA#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;Google  Books here&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/8840551178728378680/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/ai-reference-book.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8840551178728378680'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/8840551178728378680'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/ai-reference-book.html' title='A.I. Reference Book'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-eGFUiVeejO9y8pny74qEubnh2xQINdv1gJUzrLVmjNaEedruFJjB9h3BS7goSW-uChe7LiivZVjDrLewYwG8bnKD0fZ6cmntxcLamPxrssuNhsTq-RyntDuYIuFrokeaV3XfdBiQVII/s72-c/ai_book.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4161371356646794331.post-6542455552550056224</id><published>2010-07-12T14:10:00.001-05:00</published><updated>2010-07-12T14:10:23.831-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="geeks"/><title type='text'>Geek vs. Nerd vs. Dork</title><content type='html'>I am a geek.  Not to be confused with a &quot;nerd,&quot; &quot;dork,&quot; or &quot;dweeb.&quot;  If  you are confused, check out the &lt;a href=&quot;http://www.greatwhitesnark.com/2010/03/25/difference-between-nerd-dork-and-geek-explained-in-a-venn-diagram/&quot; mce_href=&quot;http://www.greatwhitesnark.com/2010/03/25/difference-between-nerd-dork-and-geek-explained-in-a-venn-diagram/&quot;&gt;Venn  diagram at this site&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://geekmatt.blogspot.com/feeds/6542455552550056224/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://geekmatt.blogspot.com/2010/07/geek-vs-nerd-vs-dork.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/6542455552550056224'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4161371356646794331/posts/default/6542455552550056224'/><link rel='alternate' type='text/html' href='http://geekmatt.blogspot.com/2010/07/geek-vs-nerd-vs-dork.html' title='Geek vs. Nerd vs. Dork'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>