Monday, May 2, 2011

The PickAkin Kata - a solution

The PickAkin kata has a simple specification which I copy straight from Ilker's text:

Write a program that takes two lists (ceoList and controlList) containing an arbitrary set of product codes in the above described format. The program produces three lists. The first list is the akinList, that is, the list of all akin products taken from the input lists. The remaining two lists are the reduced ceoList and controlList, respectively.

Algorithmically inclined peopla (like me) "just" look for a solution along these lines. So I'll give you just give a possible solution in a second.

But what about test-driven development? About incremental test-driven development? It took me a whole Sunday to find out why I do not have a bad conscience when I find a solution to a problem in a non-incremental way - I tried to explain this in my previous blog posting (which I might overhaul at times - it is somewhat chaotic ...). The quintessence is:
Of course it is ok to find a solution non-incrementally. You are then only obliged to test that solution sufficiently.
This posting will only contain the solution. Testing it will be the topic of the next one.

Finding a solution for the PickAkin kata is simple: Just take, from both lists, all product pairs and filter out the "akin" ones (similar, in my API) - this is the akinList. This is "almost easy":

    akinList = from a in listA
               from b in listB      // we now have all pairs (a,b)
               where similar(a, b)  // we take only the similar ones
               select new[] {a, b}; // assemble them into one structure 

We could happily decree that the problem is now solved. But of course, the data structure we get back from this Linq expression is not exactly what we need: We now have an IEnumerable<IEnumerable<T>> instead of the flattened list we need; and moreover, there will be duplicates in that structure. But these are "mere technical problems" - other collection concepts that are based on sets (where there are never duplicates) and do flattening implicitly would not need any additional code. In C#, flattening for IEnumerables is done with SelectMany(); and duplicates are removed with Distinct(). So here is the complete, C#-y solution for computing akinList:

    akinList = (from a in listA
                from b in listB
                where similar(a, b)
                select new[] { a, b }).SelectMany(li => li).Distinct();
 
Of course, we still have to reduce the two input lists - but this is simple:

    listAReduced = listA.Except(akinList);
    listBReduced = listB.Except(akinList);
 
Done ... Here is the complete code:

    public static class Pick {
        /// <summary>
        /// The pick-akin function of Ilker's Pick Akin Kata.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="listA">Input set A - order not important</param>
        /// <param name="listB">Input set B - order not important</param>
        /// <param name="similar">The function to designate similar elements</param>
        /// <param name="listAReduced">Output set A - order not important</param>
        /// <param name="listBReduced">Output set B - order not important</param>
        /// <param name="akinList">Akin list - order not important</param>
        public static void Akin<T>(
            IEnumerable<T> listA,
            IEnumerable<T> listB,
            Func<T, T, bool> similar,
            out IEnumerable<T> listAReduced,
            out IEnumerable<T> listBReduced,
            out IEnumerable<T> akinList) {
            akinList = (from a in listA
                        from b in listB
                        where similar(a, b)
                        select new[] { a, b }).SelectMany(li => li).Distinct();
            listAReduced = listA.Except(akinList);
            listBReduced = listB.Except(akinList);
        }
    }
 

Of course, this code now has to be tested!

No comments:

Post a Comment