a GreaseMonkey script for Follow Rank and Follows in Common for Twitter User Profiles

I’ve released a GreaseMonkey script Follow Rank and Follows in Common for Twitter User Profiles which you can use to display the Follows in Common and the Follow Rank for a given users profile page and discover how important they are in your Twitter Social Network.

Simmilar in aim to FaceBooks “friends in common”, adjusted to reflect twitter’s asymmetrical following as opossed to FaceBooks symmetrical Friending. i.e. in Facebook we both have to agree to be “Friends”, but it Twitter it only takes one of us to follow the other, following back is optional.

It order to use this script you need to have the GreaseMonkey extension installed, add the script, and then go to a users profile page on Twiiter.com, like Robert Scoble’s twitter page, (or Ashton Kutcher’s!)

FollowRank tells you how many, and which, of the people you follow are also following Robert Scoble (or Ashton Kutcher).

Follows in Common tells you how many, and which, of the people you follow are also followed by Robert Scoble (or Ashton Kutcher).

If you look at your own profile you will see a) 100% Follows in Common, b) How many of the people you follow who follow you back!

Robert Scoble with Follow Rank and FiC GM Script

Notice the small gray down arrows? (This is visible if the result is none zero.) Clicking on the text line will reveal the avatar picture of the people who contribute to the score. Hovering over the picture will show their name and clicking on it open their user profile page (and compute their Follow Rank and Follows in Common).
Robert Scoble with Follow Rank and FiC GM Scrip showing People
Clicking on the text line a second time (see the small gray up arrows?) will collapse the view of Follow Rank or Follows in Common user avatar’s. The data is cached so reopening the view is super fast.

So Why Follow Rank?

This information lets me know this persons relevance and weight in my social network, the people I have choicen to follow (my first Order Social Network in Twitter).

It lets me know, quickly, why they discovered me, or why I might follow them.

How am I doing this?

All JavaScript, using the Twitter’s API calls for their Social Graph Methods (Twitter REST API Method: friends ids and Twitter REST API Method: followers ids), lots of caching of results (but only for a period of time using my aged Cache function – persistent, but not too persistent!! ) to get around the Twitter throttle limit (currently ~150 requests per hour limit, see Rate limiting), and lots of JSON AJAX calls of those api calls. Plus, taking advantage of Twitters.com’s use of jQuery on their web pages, their own plug-in’s and css and images. Easy!!

What’s next

There is still work to be done and features to be added add to the GM script (LiC : Lists in Common?), and then to go beyond what can be done in a GreaseMonkey script!

I’m thinking of exposing your 2nd order Twitter Social Network : Who are the people being followed by the people you follow? What’s their Follow Rank? Also, who of them follows almost the same people who follow?

I think this would make a organic “Suggested Users List”, personalized just for you because it based on your social network.

Now do that for your -1 & -2 order networks.

Plus sorting (by alpha, by Follow Rank, by FiC), and searching of all that.

This will keep me busy! Whatcha think?

Posted in GreaseMonkey, JavaScript, Social Networking, Web | 1 Comment

2010 Will be The Year of the Tablet!

In all likelihood 2010 will be the year that tablet computers (and activities that belong on such a device) will become mainstream.

A large part of this is due the the (expected) announcement of Apple to release a large(er) factor iPhone or iPod Touch device, which has a chance of doing what the original iPod’s and iPhone did in their categories (mp3 player and smart phone) which was to cement them as must have devices and (at the same time) changed the rules they where judged on and operated by.

But even more so, even if Apple did nothing (to the howls of fan boys), there is enough activity out there that tablets and their kissing cousins (e-readers) will still become more capable, more affordable, and in many more hands.

One thing that can be learned from the Crunch Pad debacle is that almost anyone can created a tablet computer given the determination. (although maybe not well, or cheap) The Tech is out there. Tablet’s are where Netbooks where 3 years ago.

And will we do on these new devices? Browse and Read.

One thing that Tablet’s can do at least as well as Netbooks is in browsing, whether that is a local file or the net, and given the comprises involved with keyboards on a netbook, and the solutions for (small amounts) of typing in iPod/Phone and Android devices. (Bluetooth can be used to extend all of these devices to external full size keyboards and headsets)

Now Browsing and Reading are slightly different, and hence the difference in “Browsing” newspaper’s, magazines, and books as apposed to “Reading” them in print. It maybe a historical and accidental side effect of those media growing up in a era of “reading” (and also in part creating a culture of “Reading”), and is no longer necessary. Of course those media are now trying to re create “Reading” by creating devices (and software) which is only a little connected and less friendly to “Browsing”.

Non-the-less: Tablets for Reading and Browsing are here to stay.

As to what Apple will actual announcing in late January 2010 will be much watched. Here want I think they could announce (I have no inside knowledge):

  • It might be called a iSlate, which fits well with the Tablet style look and feel.
  • iGuide has also been suggested as a name but I wonder if that is the software part of new media reader. Also the software known as iTunes needs a new name since it has less and less to do with music (tunes) and more with other media and applications. So iGuide could be the new iTunes navigator and store, and iTunes becomes just the music player.
  • The Price is going to be a big issue if this is to be a widely used device and platform. The regular price needs to be under $500 (if not 400), which means it needs to be subsided with a cellular data contract. (if only not to eat into Apple profit margins).
  • They could put a forward facing camera to make it a social chat device, and different than all the other e-readers and tablets out there.
  • They could do something more radical and bring out a folding device which is twice as wide as a iPod touch (4.3″ x 2.4″) which would make it 4.3″” x 4.8″ and that unfolds to 4.3″ to 9.2″ giving it a over a 10″ diagonal. They could do so using a slide fold (as done with numerous cellphone keypads, and allow you to use the smaller screen in the unfolded state), or a book like unfolding. Apple also has “iBook” as a name they are not using but still protect.

Could be very very good, although I would be worried if it is too expensive or too locked down.

Update (Jan 27th): we have an iPad!! ~9.5″ by 7.5″ for a # 9.7-inch (diagonal) 1024-by-768-pixel resolution at 132 pixels per inch (ppi). WiFi and 3G; new iWorks and iBooks applications. $USD 499 for the 16Gb and WiFi only model (which looks pretty good. Must Resist Shinny Shinny). I expect we have a it on our hands.

Posted in Hardware | Leave a comment

Using GreaseMonkey’s persistent data functions as a aged Cache, revised.

In eating my own dog food I have revised, corrected, improved and fixed the code I posted last week. The biggest changes are related to ensuring that all that cached data does not just accumulate and never gets cleared away.

Values set using the “GM_setValue” function are saved in the Firefox preferences back end and can be manually changed by typing “about:config” in the address bar and searching for the preference name “greasemonkey.scriptvals.namespace/name.foo” or the key that you set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    var MaxCacheAge = 60 * 60 * 24 * 1000; // Max Age for the Cached data is 1 day in milliseconds
    var currentTime = new Date().valueOf();  // current datetime in milliseconds

    function getIntersect(arr1, arr2){
        /**
         * given 2 arrays returns an array with all elements in arr1 that are in arr2
         * see http://www.falsepositives.com/index.php/2009/12/01/javascript-function-to-get-the-intersect-of-2-arrays/ for more details
         */

        var r = [], o = {}, l = arr2.length, i, v;
        for (i = 0; i < l; i++) {
            o[arr2[i]] = true;
        }
        l = arr1.length;
        for (i = 0; i < l; i++) {
            v = arr1[i];
            if (v in o) {
                r.push(v);
            }
        }
        return r;
    }
   
    function Array_diff(arr1, arr2){
        /**
         * given 2 arrays returns an array with all elements in arr1 that are NOT in arr2
         */

        var r = [], o = {}, l = arr2.length, i, v;
        for (i = 0; i < l; i++) {
            o[arr2[i]] = true;
        }
        l = arr1.length;
        for (i = 0; i < l; i++) {
            v = arr1[i];
            if (!(v in o)) {
                r.push(v);
            }
        }
        return r;    
       
    }


/**
 * add key to the array of values we are tracking (if it's not aready there)
 * @param {string} key
 */

function GM_setCachedDataTrackingValueKey(key){
    var Tracking = eval(GM_getValue('CachedDataTracking_cache', ""));
    if (Tracking == "" || Tracking == undefined) {
        var Tracking = [];
    }
    var i, l = Tracking.length;
    for (i = 0; i < l; i++) {
        if (Tracking[i] === key) {
            return ""; // the key is already in the Tracking array so we can stop;
        }
    }
    Tracking.push(key);
    GM_setValue('CachedDataTracking_cache', uneval(Tracking));
 
}

function GM_removeCachedDataTrackingValueKey(key){
    console.log('GM_removeCachedDataTrackingValueKey,key=' + key);
    var Tracking = eval(GM_getValue('CachedDataTracking_cache', ""));
    if (Tracking == "" || Tracking == undefined) {
        return "";
    }
    var i, l = Tracking.length, NewTracking = [], j = 0;
    for (i = 0; i < l; i++) {
        if (Tracking[i] != key) {
            NewTracking[j++] = Tracking[i];
        }
    }    
    GM_setValue('CachedDataTracking_cache', uneval(NewTracking));
    GM_deleteValue(key);
}

/**
 * delete all cached values older than MaxCacheAge
 * @param {Number} MaxCacheAge 0=all. Required.
 */

function GM_flushCachedData(MaxCacheAge){          
    var Tracking = eval(GM_getValue('CachedDataTracking_cache', ""));
    if (!(Tracking == "" || Tracking == undefined)) {        
   
        var l = Tracking.length, i, v, who, DeleteWhoFromTracking = [];        
        for (i = 0; i < l; i++) {
            who = Tracking[i];
            var DateofWho = GM_getValue(who + '_cacheDate', ""); // get the age of the cache for Who
            if (DateofWho != "") {              
                if (MaxCacheAge == 0 || (DateofWho < (currentTime - MaxCacheAge))) {   // if age is older that MaxCacheAge
                    //the blank the date and the data and remove from CachedDataTracking_cache
                    GM_deleteValue(who);
                    GM_deleteValue(who + '_cacheDate', "");
                    DeleteWhoFromTracking.push(who)
                }
            }
           
        }
        if (DeleteWhoFromTracking.length > 0) { // if there is anyone to remove from tracking
            var NewTracking = Array_diff(Tracking, DeleteWhoFromTracking); //NewTracking is new array minus element from 2nd array        
            if (NewTracking.length == 0) {
                GM_setValue('CachedDataTracking_cache', "")
            }
            else {
                GM_setValue('CachedDataTracking_cache', uneval(NewTracking));
            }
        }        
    }
GM_setValue('CachedDataTracking_cacheDate', currentTime);
}


  /**
     * GM_setCachedDataValue set the value, using the key name, and the cacheDate for the value based on the current datetime.
     * to see the values set use about:config in the firefox awsome bar and filter with greasemonkey.scriptvals.http://www.FollowRank.com/
     * @param {String} key  The key argument is a string of no fixed format.  Required.  
     * @param {Object} value The value argument can be a string, boolean, or integer. Required.
     * @returns void
     *
     */

    function GM_setCachedDataValue(key, value){
    // key is the name of the value stored
    // value is the value to be stored
    var currentTime = new Date().valueOf();
    var raw = currentTime.valueOf();
    GM_setValue(key, value);
    GM_setValue(key + '_cacheDate', raw.toString());
}

    /**
     * GM_getCachedDataValue returns the value indicated by the key if a) it exists, b) if it is less the maxDuration milliseconds old; otherwise return a blank.
     * @param {String} key key is the name of the value stored. key argument is a string of no fixed format.  Required.
     * @param {Number} maxDuration is the  Maximum Duration (or how old) in milliseconds the cached data can be. maxDuration is a number. Required.
     * @returns a Integer, String or Boolean
     */

function GM_getCachedDataValue(key, maxDuration){
    // key is the name of the value stored
    // maxDuration is Maximum Duration (or how old) in milliseconds the cached data can be
    // note fillter for  about:config is greasemonkey.scriptvals.http://www.FollowRank.com/
    if (typeof maxDuration != "number") {
        console.error('maxDuration is NOT a number, but rather a ' + typeof maxDuration);
        return "";
    }
    var currentTime = new Date();
    var raw = GM_getValue(key + '_cacheDate', ""); // get the age of the cache
    if (raw != "") {
             var cache_dt = new Date(parseInt(raw));
             var age = currentTime.getTime() - cache_dt.getTime();
              var allowed_age = maxDuration;
       
        if (age < = allowed_age) {
            // if the age is less than the max allowed age the get and return the cached value
            GM_setCachedDataTrackingValueKey(key);
            return GM_getValue(key, "");
        }
        else {// if the age is greater than the max then blank out the cache date and value and return black
            GM_deleteValue(key);
            GM_deleteValue(key + '_cacheDate');
            console.info('GM cahaced data was too old so I killed it! Age was = ' + age + ' and allowed aged was =' + allowed_age);
        }
    }
    return "";
}

So there are several changes made to the code :
In rereading the Grease Monkey Wiki I spotted the GM_deleteValue function in it’s api wiki. ack!!

I moved 2 variables (MaxCacheAge and currentTime) outside the function themselves. They could almost be constants, but as long as they are only calcualted once and not hard coded! Also “currentTime” is stored in it’s milliseconds format since this is the only format I use it in.

I’ve included two utility functions : getIntersect (which returns an array with all elements in arr1 that are in arr2 as seen in a JavaScript Function to get the Intersect of 2 Arrays) and Array_diff (which returns an array with all elements in arr1 that are NOT in arr2)

The biggest change is the addition of 3 functions : GM_setCachedDataTrackingValueKey, GM_removeCachedDataTrackingValueKey and GM_flushCachedData which track the keys I have put in the Cache and ensure that nothing too old is ever stored there. I believe the first two are straight forward but it is flushCachedData that is more interesting / complicated. flushCachedData excepts a number which is the max age of all values in the cache which might be 48 hours (MaxCacheAge * 2) or 0 (Zero) in which case all values tracked in the cache are removed. I can see some further optimizations for these functions, but they solve the basic problem of all that cached data always accumulating and never getting cleared away.

The basic usage remains the same. The only thing I would add is that I have used it in the beginning of my GM script to do a cleanup

1
2
3
4
        var TrackingDate = eval(GM_getValue('CachedDataTracking_cacheDate', ""));
        if (TrackingDate < (currentTime - MaxCacheAge) ) {
         GM_flushCachedData(MaxCacheAge); //cleanup the cache by remove anything older than 24 hours   
        }

If you wanted to blow away all the values stored by a GM script just include (once only!!) this:

1
var vals = GM_listValues().map(GM_deleteValue);

Hope you find this useful. cache away...

If you wish t see and use it in practice then check out Follow Rank and Follows in Common for Twitter User Profiles

Posted in Ajax, Code, GreaseMonkey, JavaScript, Web | Leave a comment

Use GreaseMonkey’s persistent data functions as a aged Cache.

GreaseMonkey (Don’t know that GreaseMonkey is the greatest thing since slice bread? See Greasemonkey) has two persistent data function’s, GM_setValue and GM_getValue, which let you set and get values. See Mark Pilgrams excellent Dive Into Greasemonkey : Storing and retrieving persistent data for more details. (even better, go to GreaseSpot and it’s api wiki).

UPDATE : Revised

All well and good, but I wanted to store some data there (the results of a AJAX GET request), but didn’t want to store ithem forever just for a day or two or at least a couple of hours.

and so I “created” 2 functions, GM_setCachedDataValue and GM_getCachedDataValue, which allow use of the GM persistent data function’s as a aged Cache! This allows me to “say” get me this value, unless it is older that X milliseconds old.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    /**
     * GM_setCachedDataValue set the value, using the key name, and the cacheDate for the value based on the current datetime.
     * to see the values set use about:config in the firefox awsome bar and filter with greasemonkey.scriptvals.http://www.FollowRank.com/
     * @param {String} key  The key argument is a string of no fixed format.  Required.  
     * @param {Object} value The value argument can be a string, boolean, or integer. Required.
     * @returns void
     *
     */

    function GM_setCachedDataValue(key, value){
    // key is the name of the value stored
    // value is the value to be stored
    var currentTime = new Date().valueOf();
    var raw = currentTime.valueOf();
    GM_setValue(key, value);
    GM_setValue(key + '_cacheDate', raw.toString());
}

    /**
     * GM_getCachedDataValue returns the value indicated by the key if a) it exists, b) if it is less the maxDuration milliseconds old; otherwise return a blank.
     * @param {String} key key is the name of the value stored. key argument is a string of no fixed format.  Required.
     * @param {Number} maxDuration is the  Maximum Duration (or how old) in milliseconds the cached data can be. maxDuration is a number. Required.
     * @returns a Integer, String or Boolean
     */

function GM_getCachedDataValue(key, maxDuration){
    // key is the name of the value stored
    // maxDuration is Maximum Duration (or how old) in milliseconds the cached data can be
    // note fillter for  about:config is greasemonkey.scriptvals.http://www.FollowRank.com/
    if (typeof maxDuration != "number") {
        console.error('maxDuration is NOT a number, but rather a ' + typeof maxDuration);
        return "";
    }
    var currentTime = new Date();
    var raw = GM_getValue(key + '_cacheDate', ""); // get the age of the cache
    if (raw != "") {
             var cache_dt = new Date(parseInt(raw));
             var age = currentTime.getTime() - cache_dt.getTime();
              var allowed_age = maxDuration;
       
        if (age < = allowed_age) {
            // if the age is less than the max allowed age the get and return the cached value
            return GM_getValue(key, "");
        }
        else {// if the age is greater than the max then blank out the cache date and value and return black
            GM_setValue(key, "");
            GM_setValue(key + '_cacheDate', "");
            console.info('GM cahaced data was too old so I killed it! Age was = ' + age + ' and allowed aged was =' + allowed_age);
        }
    }
    return "";
}

How to use this :

before doing my Ajax request I check my aged cahce to see if the value is there and is no older (in milliseconds) that a given age. If it returns a non blank value that take that as the value and proceed, otherwise assume that it is either non there or a stale value, in either case that means I have to fetch fresh data which then needs to be stored. Clear as mud?

here is a sample javascript code to show how to use it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var MaxCacheAge = 60 * 60 * 24 * 1000; // Max Age for the Cached data is 1 day in milliseconds
var tmp = GM_getCachedDataValue("LastRequest", MaxCacheAge);
if (tmp != "") {
    LastRequest = eval(tmp);
}
else {
    GM_xmlhttpRequest({
        method: 'GET',
        url: 'http://greaseblog.blogspot.com/atom.xml',
        onload: function(response){
            LastRequest = eval(response.responseText);
            GM_setCachedDataValue("LastRequest", response.responseText);
        },
    onerror: function(response){
                LastRequest = "";
                console.error('ERROR' + response.status);
            }
    });
}

This is generally faster than waiting for the calling service to return the get request, and if that service has a throttle limit (for Twitter it is 150 requests per hour limit, see Rate limiting) it will (might) save you from hitting that too soon.

UPDATE : Revised

Posted in Code, GreaseMonkey, JavaScript | Leave a comment

Using an iPod as a travel device and the what could be (aka the tripPad™)

This fall I was lucky to travel to Berlin and Paris on vacation, but as I geek I of course brought along a computer or 2.

On last years trip to Los Angles I had a NetBook (Aspire One) and using our friends WiFi was able to keep up on email and access the locations I had added to a google map as well as look up info on place and restaurants either by local or type. But the netbook stayed back at home base after the first few times of bringing it out with me, and only notes or hard copy came with me.

On this year travel I had a new and shinny iPod Touch to test out as a travel aid. The availability of WiFi was limited in both places. In Berlin the hotel (Park Inn Berlin Alexanderplatz) had only paid (expensive) Internet, but we found some coffee shops with 1/2 hour free WiFi with a purchase. In Paris the hotel (the excellent Paris Castex Hôtel) had free WiFi, but the only open Wifi I found out and about was at the new Louvre Apple Store and the Le Palais de Tokyo. So with some planning it was possible to get fresh net and email, and do some goggling. Certainly a iPod wins out over a netbook with respect with easy of carrying and quickly using it.

The key application I used was Maps, and the secret is to have it load the tiles of every where you are going in advance. (Question how long does the ipod cache the map data and titles? or the pages within safari?) Sometimes it does not want to display the tiles and that requires spreading and pinching the map to get it to refresh from it cache. The pseudo gps function was not something that worked well enough to rely on. But I was able to use it to replace my constant referring to a hardcopy map (with I still had, as backup).

The other App that came in handy for Tourist purposes was MetrO which after preloading the data for Paris and Berlin, simplified the act of traveling on the subway, especially when multiple lines where involved.

I was not aware of any app’s equal to UrbanSpoon or Yelp that covered Berlin or Paris, although the app versions of Lonely Planet might have done (we had the hardcopy books).

And the IPod was good for watch emails, with a few brief replies, and following Twitter from 6 time zone from home (Eastern Standard Tribe), plus the occasional research google.

All in all using the iPod as a travel device worked very well.

But, the big short fall was the lack of connectivity, and which given roaming rates, would not have been solved it I had a iPhone hope to use cellular data connectivity. One possibility might be using a pre paid phone as a tether for my iPod.

So, short of universal free Wifi and / or cellular data becoming available, this got me to thinking about how this might be a business opportunity aimed a overseas tourists.

I’m thinking of a iPod like device, call it a tripPad ™, TouristPad ™, TravelPad ™™ or tPad (maybe even a tPod ?), based on the Google Android platform with touch screen, no keyboard, Data cellular connectivity, and GPS.

So here’s the scenario:

  • Arrive at airport, and put your credit card in a vendor machine which after verifying my card, and confirming my preferred language, pops out a configured and ready to go device, with a lanyard.
  • Charges are ~ $10 a day, with a deposit of ~ 100 to ~200 for non-return. (they will have to be more scratch resistant that iPods currently are).
  • some limited configuration can be done: preferred language, temperature (Fahrenheit or Celsius); your hotels address; other emergency info.
  • by default, or in standby mode, the home screen shows the local time, weather, a basic map where you are.
  • the key application is the “Map” which happens you get around and locates you but also can show you additional layers, based on the key verbs.
  • The Key Verbs on the Home Bar are things like Map,Food, Attractions, Events, Shopping, Metro, Historical. Food is restaurants and (maybe) grocery stores; Attractions are cultural places like museums and galleries; Events shows things happening like music or special exhibitions next days to a week out. Shopping is just what you would expect; Metro is subway, streetcar and buses, it could also have a “call me a cab feature” using voip (also a source of revenue for the referral); Historical is the places where where major past in the event. All the place can be “bookmarked” and for events a date and time reminder and be set.
  • Restaurants and stores could also have discount offers push thought the device. (and an additional source of revenue).
  • You could add self guided walking tours to various neighborhoods and on various themes (food, shopping, history, etc).
  • Because you have Data cellular connectivity (like the Kindle whispernet) you can get some updates like ,the weather and weeks in the next 7 to 9 days, new discount offers, and any other data updates.
  • One of the key difference between this device and a standard smart phone is the end user can’t install addtional application or make a phone call (save in special cases like the “call a cab” or a 411 help feature which is billed for). This is not a replacement for someone who could use a smart phone, this is for people who can’t use a smart phone.
  • At the end of the trip, you return it to a similar vending machine, where is cleans it and re formats for the next visitor; If you have lost it you put it your card card, not it as lost and pay the deposit; the device get remotely deactivated and the device it tracked down by it’s gps. I would not sell them, since that might create a grey market for device stolen from beat up tourist’s
  • All of the hardware, software, or data is around, there is nothing special it “just” a matter of putting it and the infrastructure (the vendor machines -which might be the hardest part-, and the cellular data network) together.
  • Other “interesting (for internet values of interesting) things you could do is to link multiple devices together. Think of it as very small (nano scale) location based social network wired into the platform. If we both have TripPads then exchange id’s and after giving permission (becoming “friends”) we could:
    • know each others location, with the ability to go
    • send short text messages (sms) to one or more of my “friends”
    • use the voip to talk to one or more of my “friends” at a time
  • In rolling this out I would focus first on (english) North America’s traveling in Paris first, then London, Rome, and then add Tokyo, Hong Kong, Shanghai, Singapore. Then I would look at Europeans traveling to North America (New York, Los Angeles, and ?) and Asia – adding French German Italian and Spanish language versions; and Asians traveling to North America and Europe – adding Japanese and Chinese language. This list of Most visited cities is a nice start but I would want to break it down for oversea visitors. You need to look for large number of well off travelings going to destinations outside of their cell phone roaming areas.
  • If you wanted to also get people traveling within their cell phone roaming areas (people from Los Angeles or Kansas traveling to New York; or people from Berlin going to Paris), I would target their existing smart phones. This might be a bigger market, but their needs are being meet with a collection of existing applications, althouoght if you can build a better, more integrated, mousetrap …

Here me O’ Internet and grant me my LWR (lazy web request), or if you’re a VC then let’s do lunch!

update : a couple of additional functional points to add to the TripPad :

  • Help with verbal translation: because so many of us north americans are so impaired with non English languages
  • Help with Text translation: this one is easier and is one of the few justifications to add a camera onto the TripPad, save for AR, is to be able to image some text; OCR it to text (because It don’t want to re text it); and translate that text. Give it a special empathize on menu phrases and this alone could make it a hit!! (any iPhone app doing this? for people too embarrassed to admit they don’t know what all those fancy Italian and French food words mean?)
  • As a part all that map layer data, how about walking tours (based on themes – art , history architecture, food – or your combination of those) with a text to voice component?

The December 21, 2009 New York Times takes about For Travelers, a Personal Concierge on Your Phone

Posted in Brain Farts, Hardware, travel | 1 Comment

JavaScript Function to get the Intersect of 2 Arrays

In mathematics, the (denoted as ∩) of two sets A and B is the set that contains all elements of A that also belong to B.

I needed the javascript array equivalent, so that given an array A = ["a","b","c", "d"] and B = ["b", "d", "e"], getIntersect(A, B) = ["b", "d"]

1
2
3
4
5
6
7
8
9
10
11
12
function getIntersect(arr1, arr2) {
    var temp = [];
    for(var i = 0; i < arr1.length; i++){
        for(var k = 0; k < arr2.length; k++){
            if(arr1[i] == arr2[k]){
                temp.push( arr1[i]);
                break;
            }
        }
    }
    return temp;
}

Update:

In the comments, Jeffery suggests a version using a javascript hash table (also called an associative array or a map), which would be much faster especially if arr2 was much shorter than arr1. I will do some benchmarking with some live data (from the app I extracted this from) to see how badly my version does. (See below : he sunk me big time! use his version.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getIntersect(arr1, arr2) {
    var r = [], o = {}, l = arr2.length, i, v;
    for (i = 0; i < l; i++) {
        o[arr2[i]] = true;
    }
    l = arr1.length;
    for (i = 0; i < l; i++) {
        v = arr1[i];
        if (v in o) {
            r.push(v);
        }
    }
    return r;
}

He is also too kind to point out one inefficient / bad practice in the code above : that I should have stored the lengths of the arrays in a variable, rather than recalculating it again and again. Nevermind ...

And elsewhere, Pete pointed out the Underscore, a utility-belt library for JavaScript that provides a lot of the functional programming support, and its _.intersect function .

Further update:

Now with benchmark results and less smoke up my ass! (repeat after me : "test your assumptions before making an ass of yourself (again)", now repeat 100 times).

I created the (very) simple benchmarking file that would actual work, a html file with embedded javascript with has the 2 functions for testing and 2 arrays : one with elements 4646 and one with 1103 elements.

The file is http://www.falsepositives.com/files/test_array_interect_file.html. There is also a version which iterates the test 100 tests, and shows the average result in a table as below.

I also rewrote the test from using firefox's console.log and console.time functions to using document.write and some raw javascript to log the time it took for a given function to run so I could run this in different browsers. (more about this later)

So the raw results where that Jeffery's version blew my version out of the water! in FF 3.3 mine ran ~ 85 to 90 ms and his ~1 to 2 ms !! So creating the hash and searching it are really really optimized.

The second surprise was that my suggested optimization of my version ("storing the lengths of the arrays in a variable") added several ms of run time! I think this is because a) the lengths are already calculated and stored as a property of each array, and b) their is some overhead of storing the 2 length values as local variables. Since object properties are really hashs, and we have just learned that hashs are really really fast the result make some sense. Storing a value instead of recalculating it is still a "really good idea", just not this time.

Having rewritten the test in more browser generic fashion here are the results for different browsers (times are in milliseconds) :

getIntersect of a1 , a2 getIntersect of a2 , a1 getIntersect JT of a1 , a2 getIntersect JT of a2 , a1
FireFox 3.5.5 89.27 74.52 1.19 1.44
Safari 4.0.3 52.32 43.60 3.11 2.97
Safari on iPdod Touch (3.1.2) 2049.00 1712.00 48.00 39.00
Chrome 4.0.223 63.91 53.00 0.82 1.45
IE 7.0.5730 3656.00 3068.60 9.60 9.40

Your mileage will vary. The absolute number are not as interesting as the relative values.

I have also run my test against Underscore, and its _.intersect function for some interesting results:

FireFox 232.75
Safari 137.22
Chrome 42.26
IE 1953

So not as fast as even my humble version, but keep in mind that the Underscore _.intersect function handls any number of arrays to intersect, and has 50 additional utility functions.

So what can we learn from this?

Hashes are very fast; Jeffery is smart; IE stinks; Ian needs to test his ASSumptions!

Posted in Code, JavaScript, Web | 1 Comment

Changes in the Junction : Jocelyn’s Place, a Stop Light & Buddha Pie

This fall has seen a few changes in Toronto’s west end Junction neighborhood (of New York Times fame):

Bronto Burger is now Jocelyn’s Place (2982 Dundas St. West, just west of Pacific Ave.), with a new owner but same menu/price and deco (at least for now). The food and service continue to be as good.
Jocelyn's  Place

The High Park Avenue and Dundas Street West intersection now has a stop light, breaking up the block and routing around the traffic from the no frill on Pacific.
STOP!!
The junctioneer has details on Ribbon-cutting ceremony, Dundas High Park Ave traffic lights.

And Lou’s Coffee Bar has been transformed (reincarnated?) into Buddha Pie (514 Annette just East of Runnymede Road), serving up gluten free thin crust pizza and sandwich’s with the best ingredients, plus great coffee!
Buddha Pie
It’s great to to see Lou is back after a loong renovation. And the pizza is buzz worthy too!
A Slice of nirvana?
that’s the “Presto Pesto Pizza” with pesto sause, red onions, green peppers, sun dried tomatoes and (real) Bufalo Mozzarella (you could have Goat Cheese) (and you can add fresh garlic and/or chicken).

Update : Buddha Pie is now Twittering @eatBuddhaPie and has a FaceBook page, and BlogTo has a in depth review (they like!)

Posted in Food, Junction City, Toronto | 1 Comment

False Positives on Planet Lotus

Once upon a time, 2006, there was “Show ‘n Tell Thursdays – A Project Of The Lotus Notes And Domino“, and the tag is still used, but Planet Lotus is where the action is now, an aggregation of Lotus related blogs and news.

Recently False Positives was accepted for inclusion as part of that community. It is a great honor (although also scary to boot) to have my Domino and Lotus Notes related post’s in such fine company.

And while you’re here there is all the “other stuff” both code and / culture ….

Posted in Code, Lotus Domino, Show-n-Tell+Thursday, Site | Leave a comment

Five films from the TIFF

We saw 5 great films in the Contemporary World Cinema program in this years (2009) Toronto International Film Festival (Tiff).

We started off with a Irish film, Perrier’s Bounty, set in the underside of North Dublin. Great and meaty characters. I hope the DVD has English subtitles and translation so I can understand the other half of the dialog.

Next was a Danish film Applause, very very northern European, although not totally bleak. (or at least, not as bleak as I thought it was going to turn out.) The big surprise was now tall and beautiful the main actress, Paprika Steen, was!!

Fatih Akin’s Soul Kitchen was a great comedy, set in Hamburg Germany!!

Hong Kong’s Yonfan presented his Prince of Tears and it was very sad and gorgeous.

The Double Hour, an Italian film, keep us guessing with each twist, and I wouldn’t say they were happy twists but they were delightful surprises as we followed these characters we came to care about.

All the film very of the highest quality and enjoyable. I would recommend them all, but Perrier’s Bounty and Soul Kitchen are more mainstream cinema, easier to watch and understand. It was also great hearing the Directors and Actors after for a Q&A.

Posted in Movies, Toronto | Leave a comment

Validating URL and Email Addresses with regexp in Lotus Notes

I needed to recreate this from scratch, and so I’m documenting this here for the next time.

Most of the credit should go to Julian Robichauxand his ls2j examples db, and in particultar the JakartaOroWrapper Library

My “invention” involves creating a isValidateURL and isValidateEmailAddress Regular Expression (or regexp ) routines that worked for me.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Uselsx "*javacon"
Use "JakartaOroWrapper"

Function isValidateURL (Url As String) As Boolean
   
    Dim jSession As New JavaSession
    Dim oroClass As JavaClass
    Dim oro As JavaObject
    Dim vector As JavaObject
    Dim jError As JavaError
   
    '** get the ORO wrapper class and instantiate an instance of it
    Set oroClass = jSession.GetClass("JakartaOroWrapper")
    Set oro = oroClass.CreateObject
   
    Dim pattern As String  
'   pattern = "^((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$"
'   pattern = "(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?"  
    pattern = "^(http|https|ftp)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\-\._\?\,\'/\\\+&amp;%\$#\=~])*$"
   
    isValidateURL = oro.matches(Url, pattern, False)   
End Function

Function isValidateEmailAddress (emailAddress As String) As Boolean
   
    Dim jSession As New JavaSession
    Dim oroClass As JavaClass
    Dim oro As JavaObject
    Dim vector As JavaObject
    Dim jError As JavaError
   
    '** get the ORO wrapper class and instantiate an instance of it
    Set oroClass = jSession.GetClass("JakartaOroWrapper")
    Set oro = oroClass.CreateObject
   
    '** here are the strings and patterns we'll play with
    Dim testString As String
    Dim pattern As String  
    'pattern = "\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b"
    'RFC_2822 version
    pattern = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
   
   
    isValidateEmailAddress = oro.matches(emailAddress, pattern, False)
   
   
End Function

How I used it was not very exciting but for completeness.

1
2
3
4
5
6
If Not NotesDoc.url(0) =  "" Then  
        If Not isValidateURL ( NotesDoc.url(0)) Then
            Msgbox "The URL Address ("+NotesDoc.url(0)+") doesn't look right, Please confirm it is correct.", 0 + 32, "Validation Error"       
            NotesUIDoc.GotoField("URL")
    End If
End If

A big cautionary note about the regexp patterns I used. for both Url’s and emails addresses, I seen them very short and very very long. It’s easy to make assumtion about the validate TLD and other aspects of the strings, so test test test, and be aware.

And, of course, this is but a small piece of what you could do with regexp!!

Posted in Code, Lotus Domino, Show-n-Tell+Thursday | Leave a comment
  • Follow

  • Archives (since 2003)

  • Categories

  • Recent Posts

  • Twitter Updates

  • Flickr

    Runnymede Healthcare Centre Demolition : The EndRunnymede Healthcare Centre Demolition : The EndRunnymede Healthcare Centre Demolition : The EndRunnymede Healthcare Centre Demolition
  •  

    February 2010
    M T W T F S S
    « Jan    
    1234567
    891011121314
    15161718192021
    22232425262728
  • Spam Blocked