Thursday, July 16, 2009

Net::Twitter::Lite 0.05

The latest version of Net::Twitter::Lite has pretty much made my tweet script redundant! Or at least removed the need for half of the code: http://gist.github.com/143950 Enjoy!

Tuesday, July 14, 2009

Following a Topic on Twitter with Net::Twitter

Last week I was keeping up to date with Triple J's Hottest 100 of All Time for 2009 and the hashtag to follow was #hottest100, so that's what I typed into search.twitter.com and kept refreshing every few minutes to see the latest thoughts as the countdown progressed. However, it very quickly became annoying to constantly refresh the search page to view the latest tweets. So I threw together this quick Perl script to display Growl notifications for the latest updates:

#!/usr/bin/env perl

use warnings;
use strict;

use Config::ApacheFormat;
use Encode qw/encode/;
use HTML::Entities;
use Mac::Growl;
use Net::Twitter;

use constant {
    TWEETRC             => "$ENV{HOME}/.tweetrc",
    MAX_RESULTS_DISPLAY => 5,
    POLL_FREQUENCY      => 30
};

die "usage: tweet-follow \n" unless @ARGV > 0;

Mac::Growl::RegisterNotifications(
    'tweet-follow',
    ['tweet'],
    ['tweet']
);

my $num_results_display = MAX_RESULTS_DISPLAY;
my $poll_frequency      = POLL_FREQUENCY;

if ( -e TWEETRC ) { 
    my $config = new Config::ApacheFormat;
    $config->read(TWEETRC);
    my $block = $config->block('follow');
    $num_results_display = $block->get('max_display_results');
    $poll_frequency      = $block->get('poll_frequency');
}

my ($query) = @ARGV;
my $twitter = Net::Twitter->new( traits => [qw/API::Search/] );

my $since_id = 0;
my $max_id   = 0;
while (1) {
    my $response = $twitter->search(
        {   q   => $query,
            rpp => $num_results_display,
            $since_id ? ( since_id => $since_id ) : ()
        }   
    );  

    unless ( defined $response ) { 
        printf "error while following: %s\n",
            $twitter->get_error->{error};
        exit 1;
    }   

    for my $tweet ( @{ $response->{results} } ) { 
        next unless $tweet->{id} > $since_id;

        $max_id = $tweet->{id} if $tweet->{id} > $max_id;

        Mac::Growl::PostNotification(
            'tweet-follow',
            'tweet',
            decode_entities( $tweet->{from_user} ),
            encode( 'UTF-8', decode_entities( $tweet->{text} ) ) 
        );  
    }   

    $since_id = $max_id;
    sleep $poll_frequency;
}

Usage is simple:
tweet-follow hottest100

Add something like this to your ~/.tweetrc to tweak some basic settings:

<follow>
    max_display_results 7
    poll_frequency 30
</follow>

The options are pretty self-explanatory:
  • max_display_results controls how many tweets to post Growl notifications for.
  • poll_frequency configures how often, in seconds, to poll for, and display, new tweets.

If only I could get Unicode characters to display properly in the Growl boxes. At the moment they just display as question marks for me.

I made a design decision to only display the latest N tweets, rather than displaying the next N tweets since the last poll, otherwise if you are displaying 5 tweets every 30 seconds for a topic that receives 50 new tweets every 10 seconds (think #iranelection or celebrity deaths), the script will get far behind very quickly.

Wednesday, July 8, 2009

Unicode and Perl

Tonight I was having problems with different character sets while hacking together some code to display and colorize Twitter search results on the command line (will post code another time) and ended up finally, after all these years with Perl, reading up about Perl and unicode (see perlunicode, perluniintro, perlunifaq and perlunitut).

The Encode module makes dealing with different character encodings trivial. I wish I had known about it before. I remember using the bytes pragma a couple of years ago because it was the easiest and quickest solution and I was (am?) lazy, but it is recommended to not use it.

Wednesday, July 1, 2009

Project Euler problem 28

DISCLAIMER: This post contains a solution to Project Euler problem 28. Go away if you don't want to know. DISCLAIMER 2: I am not the most mathematically minded person, so this may/will seem slow for pro mathematicians.

See here for the problem description.

So I got around to solving problem 28 last night (14 more until I get to level 2!) and, like most problems, I quickly found out the reason why this problem is on Project Euler: there's a subtle and beautiful pattern hidden in the middle of what seems like a random math problem.

The first pattern is that as the pyramid builds, the top right corners are always perfect squares of 1, 3, 5, 7 etc...
43 44 45 46 47 48 49
42 21 22 23 24 25 26
41 20  7  8  9 10 27
40 19  6  1  2 11 28
39 18  5  4  3 12 29
38 17 16 15 14 13 30
37 36 35 34 33 32 31
So we start looping from 1 to 1001 by 2s.

The other subtle pattern is that the gap between the corners also increments by 1, 3, 5, 7 etc... with each new layer of the pyramid. Therefor if we are at the top right corner which is n2, the top left corner is n2 - n + 1, the bottom left corner is n2 - 2n + 2, and the bottom right corner is n2 - 3n + 3, which follows an easy summation pattern which we can represent with a simple Lisp function:
(defun sum-corners (n)
  (loop for x from 0 to 3
     sum (+ (- (* n n)
               (* x n))
            x)))
Note: That can be simplified further into one equation, rather than a loop, if you like.

And a simple loop, described earlier, to tie everything together:
(defun euler-28 ()
  (1+ (loop for n from 3 to 1001 by 2
         sum (sum-corners n))))
Done. A simple problem disguised as something a little more difficult than it really is and has a couple of simple but cool patterns.