Should we learn a new way of writing loops?

(This post is a slightly-edited English translation of a previous one in Spanish, with some added comments at the end) 

 

Until now, every time somebody mentions the potential dangers that will “surround” programmers with the introduction of the new features of C# 3.0 (specially those related to the abuse of local variable type inference and extension methods), my reaction is always the same: I consider that these language enhancements are necessary in order to support the integrated query mechanism; that they are not, in principle, awful or anyhow fundamentally flawed; and that through continuous education we will be able to warn developers so that they can sort out the possible dangers that the use of these features could convey.

 

A few days ago I read a post by Luke Hoban that seems to have convinced me even more of the role that we, trainers, will have to play in the future education of .NET programmers; in this case, by showing them not to use C# 3.0 features or integrated queries for tasks where these features don't make much sense. Luke's post shows how to solve a weight distribution puzzle using a kind of "brute force" approach: a series of “nested loops” implemented using the standard query operator Range(min, max), that sequentially produces the integer values between min y max, both inclusive. That seemed to me (at least at first sight) a situation in which the use of an integrated query does not offer any advantages over traditional loops, but can severely affect performance.

 

The puzzle reminded me of a certain kind of problems I used to propose back in my years as University teacher (20 years ago) to freshman students receiving the course “Introduction to Programming” (we used Pascal then). So I decided to program one of those in two different ways: using traditional loops and “a la Hoban”, in order to compare both implementations.

 

The problem: Write a program that finds all the four-digit integers that are equal to the sum of the fourth powers of each of its digits. For instance, one of these numbers is 1634:

 

                 1634 = 14 + 64 + 34 + 44

 

Solution (naïve): To iterate over four nested loops covering all the possible combinations of digits, and check if the condition holds true for each of these combinations.

 

At the end of this post is included the code of a program that implements both variants and measures the time they take to execute. I have tried to make both variants as similar as possible; in particular, I have introduced a generic list in the loop-based variant, taking into account that the LINQ-based version will have to deal with sequences.

 

On my laptop, the ratio between the times spent by both variants was of 80:95. Honestly, I was expecting a somewhat bigger difference. Maybe just the size of the problem is too small. But the important thing is: ¿does the integrated query offers any advantage here? ¿should we learn a new way of writing loops? Opinions are welcome.

 

(Added 04/27/2007)

 

The thread following the publication of the original post has raised two possible advantages of the LINQ approach: a) it leads to more declarative code that expresses more the intent rather than the explicit way of obtaining the result; b) being expression-based, the LINQ approach can be more amenable to certain compiler optimizations.

The author wishes to thank Alfredo Novoa for his very helpful comments.

 

//*********************************************************************

class Program

{

    static void Main(string[] args)

    {

        Stopwatch sw = new Stopwatch();

        sw.Start();

        // "classic" version

        List<int> numbers = new List<int>();

        for (int i1 = 1; i1 <= 9; i1++)

        for (int i2 = 0; i2 <= 9; i2++)         for (int i3 = 0; i3 <= 9; i3++)

        for (int i4 = 0; i4 <= 9; i4++)

            if (Condition(i1, i2, i3, i4))

                numbers.Add(Number(i1, i2, i3, i4));

        foreach (int n in numbers)

            Console.WriteLine(n);

        sw.Stop();

        Console.WriteLine("Ellapsed: " + sw.ElapsedMilliseconds.ToString());

        sw.Start();

        // LINQ version

        IEnumerable<int> numbers2 =

            from j1 in Enumerable.Range(1, 9)

            from j2 in Enumerable.Range(0, 9)

            from j3 in Enumerable.Range(0, 9)

            from j4 in Enumerable.Range(0, 9)

                where Condition(j1, j2, j3, j4)

                    select Number(j1, j2, j3, j4);

        foreach (int n in numbers2)

            Console.WriteLine(n);

        sw.Stop();

        Console.WriteLine("Ellapsed: " + sw.ElapsedMilliseconds.ToString());        Console.ReadLine();

    }

    static bool Condition(int a, int b, int c, int d)

    {

        return Number(a, b, c, d) == Fourth(a) + Fourth(b) + Fourth(c) + Fourth(d);

    }

    static int Number(int a, int b, int c, int d) { return 1000 * a + 100 * b + 10 * c + d; }

    static int Square(int a) { return a * a; }     static int Fourth(int a) { return Square(a) * Square(a); }

}

//*********************************************************************

Published 27/4/2007 11:07 por Octavio Hernández
Archivado en:
Comparte este post:

Comentarios

Sunday, August 31, 2008 7:03 PM por Bill Blogs in C#

# Euler Problem 10

Patrick posted his solution earlier this week. I figure it was time to add mine. Also, Octavio Hernandez