tags:

views:

148

answers:

2

I'm often confronted with the following situation: I have some data in a table (nested arrays) and need to loop over all items in it.

Requirements:

  1. The first item should be identified and handled separately.
  2. The last item should be identified and handled separately.
  3. If similiar items are sorted they should be identified in groups with the first and last group item identified. (in this case sort/group by gender)
  4. Subtotals of group items and total of all items should be given.

DATA: Comic characters with firstname, lastname, tvshow, gender assuming to be ordered by gender, lastname.

Turanga|Leela|Futurama|Female
Marge|Simpson|The Simpsons|Female
Eric|Cartman|South Park|Male
Peter|Griffin|Family Guy|Male
Homer|Simpson|The Simpsons|Male

QUESTION: How would you code a loop (any language welcome!) that produces the following output? Indention is not important.

OUTPUT:

First Entry: Turanga Leele (Futurama)
--
First Female: Turanga Leela (Futurama)
Last Female : Marge Simpson (The Simpsons)
--
Total Females: 2
--
First Male: Eric Cartman (South Park)
            Peter Griffin (Family Guy)
Last Male : Homer Simpson (The Simpsons)
--
Total Males: 3
--
Last Entry: Homer Simpson (The Simpsons)
--
Total Entries: 5
+1  A: 

I picked the world's most verbose language to do this in (Java), but this should do what you want. It requires only a single pass, but needs one row of look ahead. It also requires at least two lines of input although it would be relatively easy to adapt to any number of lines:

    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;

    import static java.lang.System.out;

    public class Loop {
        static enum Col { FIRST_NAME, LAST_NAME, TV_SHOW, GENDER }

    private static final List<String[]> characters = Arrays.asList(
            new String[] {"Turanga", "Leela", "Futurama", "Female"},
            new String[] {"Marge", "Simpson", "The Simpsons", "Female"},
            new String[] {"Eric", "Cartman", "South Park", "Male"},
            new String[] {"Peter", "Griffin", "Family Guy", "Male"},
            new String[] {"Homer", "Simpson", "The Simpsons", "Male"});    

    public static void summarize(List<String[]> character, Col groupBy) {
        assert character.size() > 1;        
        int total = 0;
        Iterator<String[]> i = characters.iterator();
        String[] row = next(i);
        String[] peek = next(i);
        String group;
        out.print("First Entry:" + format(row));        
        Map<String, Integer> subTotals = new HashMap<String, Integer>();
        do {
            group = col(row, groupBy);
            out.print("First " + group + ":");            
            subTotals.put(group, 0);
            do {
                out.print(format(row));
                total = incrementTotals(total, group, subTotals);
                row = peek;
                peek = next(i);
            } while (peek != null && col(peek, groupBy).equals(group));
            total = incrementTotals(total, group, subTotals);
            out.print("Last " + group + ":" + format(row));
            out.println("--");
            out.println("Total " + group + "s:" + subTotals.get(group));
            out.println("--");
            if (peek == null) break;
            row = peek;
            peek = next(i);            
        } while(true);
        out.print("Last Entry:" + format(row));
        out.println("--");
        out.println("Total Entries:" + total);        
    }


    private static String[] next(Iterator<String[]> i) {
        if (i.hasNext())
            return i.next();
        return null;
    }


    private static int incrementTotals(int total, String group,
            Map<String, Integer> subTotals) {
        total++;
        subTotals.put(group, subTotals.get(group) + 1);
        return total;
    }

    private static String format(String[] row) {
        return col(row, Col.FIRST_NAME) + " " + col(row, Col.LAST_NAME)
            + "(" + col(row, Col.TV_SHOW) + ")\n";
    }

    private static String col(String[] row, Col col) {
        return row[col.ordinal()];
    }

    public static void main(String args[]) {
        summarize(characters, Col.GENDER);
    }

}

Output is:

First Entry:Turanga Leela(Futurama)
First Female:Turanga Leela(Futurama)
Last Female:Marge Simpson(The Simpsons)
--
Total Females:2
--
First Male:Eric Cartman(South Park)
Peter Griffin(Family Guy)
Last Male:Homer Simpson(The Simpsons)
--
Total Males:3
--
Last Entry:Homer Simpson(The Simpsons)
--
Total Entries:5
Dean Povey
Impressive design.
capfu
+1  A: 

For interest sake, I decided to see what this would look like in Groovy which is a real language with closures, metaprogramming and some cool list operators. The following code gives more or less the same results, although it requires the list to be in memory and is not particularly efficient. It is however far more readable, and well.. groovier.

enum Col { FIRST_NAME, LAST_NAME, TV_SHOW, GENDER }
def characters = [
        [ "Turanga", "Leela", "Futurama", "Female" ],
        [ "Marge", "Simpson", "The Simpsons", "Female" ],
        [ "Eric", "Cartman", "South Park", "Male" ],
        [ "Peter", "Griffin", "Family Guy", "Male" ],
        [ "Homer", "Simpson", "The Simpsons", "Male" ]
]

// Use Groovy Metaprogramming to add some methods to the List class
List.metaClass.first = { cl -> if (delegate.size > 0) cl(delegate[0]) }
List.metaClass.middle = { cl -> 
    if (delegate.size > 2) 1..delegate.size-2.each { i -> cl(delegate[i]) }
}
List.metaClass.last = {  cl -> if (delegate.size > 1) return cl(delegate[delegate.size-1]) }

def format(row) { 
    return row[Col.FIRST_NAME.ordinal()] + " " +
           row[Col.LAST_NAME.ordinal()] + " (" +
           row[Col.TV_SHOW.ordinal()] + ")"
}

// Loop through and summarize the Characters
characters.first { row ->
    println("First Entry: ${format(row)}")
}
def groups = characters.groupBy { row -> row[Col.GENDER.ordinal()] }
groups.each { groupType, group ->    
    group.first { row -> println("First ${groupType}: ${format(row)}") }
    group.middle { row -> println(format(row)) }
    group.last { row -> println("Last ${groupType}: ${format(row)}") }
    println("--")
    println("Total ${groupType}s: ${group.size}")
    println("--")
}
characters.last { row ->
    println("Last Entry : ${format(row)}")
    println("--")
    println("Total Items: " + characters.size())
}
Dean Povey
What I like most (if I read this correctly) is that you don't trust the characters to be sorted and explicitly order them by GENDER (although omitting LASTNAME).
capfu