This post was originally published on Coding Glamour.

Het Tweakers Developer Summit was weer een mooie gelegenheid als web-nerd om he-le-maal los te gaan. Als eerst, mijn implementatie:

  • Klop ID's van de pricewatch in, komma seperated
  • Hit de button
  • Je krijgt een lijst met shops waar alle producten te koop zijn, met de totaalprijs
http://100procentjan.nl/files/rerhm4ywk1cqad519zx38yi5x.PNG
Plaatje klikken is spelen. Let op! Er is geen visuele feedback, gebruik de Firebug / Chrome Console om in de gaten te houden wat er gebeurt. Implementatie
Omdat een groot deel van het werk scrapen is, en ik daar zelf geen zin in heb; heb ik daarvoor Yahoo Pipes voor gepakt. "Pipes is a powerful composition tool to aggregate, manipulate, and mashup content from around the web". In Pipes is het de bedoeling om blokjes aan elkaar te verbinden om data te manipuleren. De volgende pipe doet het zware werk:
http://100procentjan.nl/files/4y6y9nxlzqbo2tcyh1hfki5jb.PNG
  • 1. We beginnen met het vragen naar een getal. Deze is ook beschikbaar als parameter in je URL straks.
  • 2. Bouw een URL in de vorm: http://tweakers.net/pricewatch/GETAL
  • 3. Fetch page, en zoek de pricewatch tabel; split dit in <tr/>'s
  • 4. Filter om te zorgen dat we alleen pricewatch rijen uitfilteren; en niet bv. de lijst met verzendprijzen.
  • 5. Manipuleer de set. Omdat ik het niet goed voor elkaar kreeg om een fatsoenlijke feed te bakken, heb ik alleen replaces uitgevoerd. Beetje vreemd, maar dat fix ik later wel in de javascript.
  • 6. Output. Per shop een regel in de vorm van '<veel><html><meuk>_Shop:Azerty_<meer><meuk>_Prijs:123,20_<blah>'
Javascript
Aan elkaar knopen van de pipe en javascript is heel makkelijk, want er is een JSON interface beschikbaar.

var pipe = 'http://pipes.yahoo.com/pipes/pipe.run?_id=a74913b36aa04797a2ac37b4ee019192&_render=json&_callback=?';
$.getJSON(pipe, { TweakersId: 12345 }, function (data) {
    // data.value.items heeft nu je content!
    for(var ix = 0, item = data.value.items[ix]; ix < data.value.items.length; item = data.value.items[++ix]) {                    
        // maak objectje met 'shop', 'prijs', 'prijs incl verzend'
        var pw = {
            shop: /_Shop:(.*?)_/.exec(item.content)[1],
            prijs: Number((/_Prijs:([\w\s,\.]+)_/.exec(item.content)[1]).replace(/,/, '.')),
            totaalprijs: Number((/_Totaal:([\w\s,\.]+)_/.exec(item.content)[1]).replace(/,/, '.'))
        };
    }
});

We hebben nu dus de data van de pricewatch vertaalt naar een lijstje objecten die netjes geformatteerd in javascript zitten.

Map/Reduce
Map/reduce is een manier van werken om grote sets data op te delen in kleine deelsetjes. Handig voor bijvoorbeeld aggregatie, want je moet goed nadenken hoe je je units zo klein en overzichtelijk mogelijk kan houden. We krijgen dus de data terug uit de pipe, en moeten nu gaan aggregeren.
  • Stop alle items in een array. Welk product het om gaat maakt ons niet uit. Alle data zit dus door elkaar heen.
  • Map functie: hier moet een key/value pair uitkomen. We willen groeperen op shopnaam, dus ik yield { key: shopnaam, value: item }.
  • Reduce functie: gegroepeerd op key komen de objecten nu in mijn reduce functie: {key: shopnaam, value: item-array}.
  • Elke value-array die niet net zoveel items heeft als er ID's waren, waren niet te koop in alle shops; dus skip die maar
  • Tel de prijzen van alle items bij elkaar op
Syntax hiervoor is supersimpel:

mapreduce(items, function (item) {
    // map functie, retouneer key/value
    emit(item.shop, item);
}, function(key, data) {
    // reduce functie
    // het boeit alleen als de items allebei te koop zijn
    if(data.length === ids.length) {
        var prijs = 0;
        for(var entry in data) {
            prijs += data[entry].prijs;
        }
        // alle items zijn te koop, voor deze totaalprijs
        output.push({ shop: key, prijs: prijs });
    }
});

En in 'output' zitten nu alle shops. Hoef alleen nog te sorteren en naar HTML te spugen.

Map/reduce implementatie

var set = [];
function emit(key, value) {
    if(!set[key]) {
        set[key] = [];
    }
    set[key].push(value);
}
function mapreduce(data, map, reduce) {
    for(var ix = 0; ix < items.length; ix++) {
        map(items[ix]);
    }
    
    for(var shop in set) {
        reduce(shop, set[shop]);
    }
}


Lekker weinig code!
Bij elkaar is de javascript zo'n 100 regels, maar dat is voornamelijk boilerplate om aan de regels van de contest te voldoen. De echte implementatie is een regel of 20 ofzo + 10 voor de mapreduce functie. Check de source van de HTML pagina voor de werking. De Yahoo Pipe kan je ook gewoon inzien.