Drew Bourne

Plucking Chickens

I have some chickens, and I want to know how many feathers they have each.

var chickens:Array = [new Chicken({ feathers: Math.round(Math.random() * 5000) }), 
                      new Chicken({ feathers: Math.round(Math.random() * 5000) }),
                      new Chicken({ feathers: Math.round(Math.random() * 5000) })];
var feathersPerChicken:Array = null;

I would rather not sit there and iterate through my chickens, picking up each one, counting its feathers and then letting it run off, remembering where I got up to, and then possible forgetting which chicken I was up to.

for (var i:int = 0, n:int = chickens.length; i < n; i++) {
  var chicken:Chicken = chickens[i];
  var feathers:Number = chicken.feathers;
  feathersPerChicken.push(feathers);
}

Eww, I almost stepped on a temporary variable one of the chickens left behind.

Good thing we know how to clean that up with Array.map.

var feathersPerChicken:Array = chicken.map(function(chicken:Chicken, i:int, a:Array):void {
  return chicken.feathers;
});

I just remembered I need to know the weights of all the chickens too. We should probably get out our map again.

var weightsPerChicken:Array = chicken.map(function(chicken:Chicken, i:int, a:Array):void {
  return chicken.weight;
});

It is looking pretty similar to last time. Could we do something to remove the repetition and show only what changes? Lets give this a few go’s.

function getChickenProperty(property:String):Function {
  return function(chicken:Chicken, i:int, a:Array):Object {
    return chickenProperty[property];
  }
}
var feathersPerChicken:Array = chickens.map(getChickenProperty("feathers"));
var weightsPerChicken:Array = chickens.map(getChickenProperty("weight"));

Reasonable attempt, but we might not always be dealing with chickens, feathers and weights. Lets go again.

function getProperty(property:String):Function {
  return function(item:Object, i:int, a:Array):Object {
    return item[property];
  }
}
var feathersPerChicken:Array = chickens.map(getProperty("feathers"));
var weightsPerChicken:Array = chickens.map(getProperty("weight"));

Thats more like it. However why do we have to get out the Array.map and getProperty when what we want is to pluck some feathers.

If we shuffle the code around a little we can forget about Array.map, getProperty, temporary variables, and iterator functions and say what we really mean.

function pluck(array:Array, property:String):Array {
  return array.map(function(item:Object, i:int, a:Array):Object {
    return item[property];
  });
}
  
var feathersPerChicken:Array = pluck(chickens, "feathers");
var weightsPerChicken:Array = pluck(chickens, "weight");

What if the Chicken.feathers property was an Array instead of a Number, how could we change pluck() to support the following?

var feathersPerChicken:Array = pluck(chickens, "feathers.length");

pluck() is available in asx and supports chained look-ups "propertyA.propertyB.length" and simple methods "property.toLowerCase()".