Tuplas-valor en C# 7.0

“… Pero lo nuestro es pasar / Pasar haciendo caminos / Caminos sobre la mar …”
Joan Manuel Serrat, “Cantares” (1969), basada en  un poema de Antonio Machado

La idea de escribir una entrada relacionada con las tuplas-valor (value tuples) añadidas recientemente a C# 7.0 me trajo a la mente el artículo que escribí una vez para la revista dotNetManía con mi maestro y amigo Miguel Katrib cuando aparecieron las tuplas-referencia, aquellas que se sintetizan a través del uso de la clase genérica Tuple<> que fue incorporada a .NET Framework en la versión 4.0. Las nuevas tuplas-valor se introducen casi que con la idea de hacer obsoletas (“todo pasa y todo queda…“) las tuplas-referencia, resolviendo la mayor parte de los inconvenientes que éstas presentan, y algunas ideas en ese sentido ya las proponíamos en aquel lejano artículo (que el lector interesado puede encontrar, gracias a la generosidad de nuestro gran amigo Paco Marín, aquí).

El primer elemento a destacar es que para poder hacer uso de las tuplas-valor es necesario instalar el paquete de NuGet System.ValueTuple, a menos que esté usted desarrollando un proyecto basado en .NET Framework 4.7 o superior, o en .NET Core 2.0 o superior. Como tipo de datos, una diferencia crucial entre ValueTuple y System.Tuple es que el nuevo tipo es una estructura en vez de una clase y sus miembros son campos en vez de propiedades, lo que lo hace en principio más ligero y eficiente con relación a Tuple. Aparte de eso, un somero análisis de los ensamblados correspondientes le convencerá de que ambos tipos utilizan patrones de desarrollo parecidos (incluyendo el truco del “octavo pasajero” al que aludíamos en el artículo antes mencionado) y ofrecen posibilidades similares. Algunas de ellas pueden verse en la parte inicial del ejemplo que se muestra más abajo, aunque también varias diferencias:

  • Si bien por defecto el convenio de nombres para los elementos de una tupla es el mismo (Item1, Item2, etc.), para las tuplas-valor el compilador soporta el uso de nombres alternativos para los campos.
  • Como los elementos de las tuplas-valor son campos públicos, sus valores pueden ser modificados (son mutables).

Pero sin duda alguna lo más útil e interesante en relación con las tuplas-valor es que el compilador de C# las utiliza al transformar el nuevo “azúcar sintáctico” que ahora está soportado en C# 7.0. Dejaré para una próxima entrada un análisis de los “actos de magia” que utiliza el compilador para generar código basado en ValueTuple (que por supuesto no se basa en la herencia, dado que las estructuras no la soportan).

La segunda parte del código a continuación muestra un ejemplo de la que tal vez sea la aplicación más popular para las tuplas-valor: una función que devuelve varios valores. En este caso, el método FibonacciInterval determina simultáneamente el menor número de Fibonacci que es mayor o igual que el valor recibido como parámetro y el siguiente número de la secuencia. Note como se puede también utilizar nombres explícitos para hacer más legible el código que llame a la función:

using System;

namespace ValueTuples
{
    class MainClass
    {
        static void Main(string[] args)
        {
            var t1 = ValueTuple.Create("DH"ValueTuple.Create(27121985));
            var t2 = ("DH", (27121985)); // equivalente "endulzado"
            t1.Item2.Item2 = 10;
            Console.WriteLine(t1.Item2);

            var t3 = (Nombre: "Diana", Nacimiento: (Día: 2, Mes: 4, Año: 1998));
            Console.WriteLine(t3.Nacimiento.Día);

            for (uint i = 0; i < 50; i++)
                Console.WriteLine($"Fibonacci interval for {i} = " +
                    FibonacciInterval(i));
        }

        public static (uint Min, uint Max) FibonacciInterval(uint number)
        {
            (uint min, uint max) = (01);
            while (true)
            {
                if (number < max)
                    return (min, max);
                uint sum = min + max;
                min = max;
                max = sum;
            }
        }
    }
}

Observe también la simpática asignación (uint min, uint max) = (01) al inicio del método. Se trata de un ejemplo de deconstrucción (del inglés deconstruction) o desmontaje de una tupla-valor en sus componentes individuales. Esta característica de deconstrucción (que es más general y puede aplicarse a tipos que no sean tuplas-valor) me recuerda, como ya he dicho antes, al mecanismo de unificación utilizado en el lenguaje Prolog; la abordaré con más detalle en otra futura entrega.


Referencia musical: Tendría yo unos doce años cuando Joan Manuel Serrat fue a cantar a nuestra escuela en las afueras de La Habana, y desde ese entonces lo cuento entre mis favoritos. Adicionalmente, el hecho de que las letras de sus temas más populares de aquellos tiempos estuvieran basadas en poemas de Antonio Machado y Miguel Hernández me incitó a estudiar la vida y obra de esos poetas, sin saber que un día el destino me llevaría a España. ¡Gracias, Maestro!

Solving Combinatory Problems with LINQ

Originally published by MSDN in April, 2010.

The other day I ran into a very interesting blog post by Intel engineer James Cownie, Intel Parallel Studio: Great for Serial Code Too (Episode 1). The article uses as an example an application that solves the following problem (I quote):

Find a number consisting of 9 digits in which each of the digits from 1 to 9 appears only once. This number must also satisfy these divisibility requirements:

  1. The number should be divisible by 9.
  2. If the rightmost digit is removed, the remaining number should be divisible by 8.
  3. If the rightmost digit of the new number is removed, the remaining number should be divisible by 7.
  4. And so on, until there’s only one digit (which will necessarily be divisible by 1).

While the C++ solution proposed by the author in that article runs along more “conventional” lines, I (having the enormous luck of being a C# developer) was immediately drawn to applying LINQ to tackle this problem. I find LINQ an ideal tool for solving this type of exploratory problem, where one needs to traverse a full tree of possible combinations, backtracking from unsuccessful branches as soon as they are encountered.

With LINQ queries, cascaded from clauses serve as the generators of combinations, while where clauses determine the failure points that trigger backtracking. The first time I saw this technique used was in the blog post Using LINQ to solve puzzles, by Luke Hoban, a member of the Microsoft Languages Team. I was inspired to write about the approach in my book C# 3.0 and LINQ (C# 3.0 y LINQ, Krasis Press, 2007 (in Spanish)), and then occasionally in my blog, the last time to solve a puzzle involving the generation of a magic square similar to the Dührer square mentioned in the latest Dan Brown bestseller, The Lost Symbol. The post, Buscando el símbolo perdido con LINQ, is in Spanish.

The code that follows shows my implementation of the solution to the problem, which I find a good example of the enormous expressive power of LINQ.

using System;
using System.Linq;
 
namespace Geeks
{
  // C# and LINQ solution to the numeric problem presented in: 
  // http://software.intel.com/en-us/blogs/2009/12/07/intel-parallel-studio-great-for-serial-code-too-episode-1/
  class MainClass
  {
    static void Main()
    {
      int[] oneToNine = new int[] { 123456789 };

      // the query 
      var query =
        from i1 in oneToNine
        from i2 in oneToNine
        where i2 != i1
           && (i1 * 10 + i2) % 2 == 0
        from i3 in oneToNine
        where i3 != i2 && i3 != i1
           && (i1 * 100 + i2 * 10 + i3) % 3 == 0
        from i4 in oneToNine
        where i4 != i3 && i4 != i2 && i4 != i1
           && (i1 * 1000 + i2 * 100 + i3 * 10 + i4) % 4 == 0
        from i5 in oneToNine
        where i5 != i4 && i5 != i3 && i5 != i2 && i5 != i1
           && (i1 * 10000 + i2 * 1000 + i3 * 100 + i4 * 10 + i5) % 5 == 0
        from i6 in oneToNine
        where i6 != i5 && i6 != i4 && i6 != i3 && i6 != i2 && i6 != i1
          && (i1 * 100000 + i2 * 10000 + i3 * 1000 + i4 * 100 + i5 * 10 + i6) % 6 == 0
        from i7 in oneToNine
        where i7 != i6 && i7 != i5 && i7 != i4 && i7 != i3 && i7 != i2 && i7 != i1
          && (i1 * 1000000 + i2 * 100000 + i3 * 10000 + i4 * 1000 + i5 * 100 + i6 * 10 + i7) % 7 == 0
        from i8 in oneToNine
        where i8 != i7 && i8 != i6 && i8 != i5 && i8 != i4 && i8 != i3 && i8 != i2 && i8 != i1
          && (i1 * 10000000 + i2 * 1000000 + i3 * 100000 + i4 * 10000 +
              i5 * 1000 + i6 * 100 + i7 * 10 + i8) % 8 == 0
        from i9 in oneToNine
        where i9 != i8 && i9 != i7 && i9 != i6 && i9 != i5 && i9 != i4 && i9 != i3 && i9 != i2 && i9 != i1
        let number = i1 * 100000000 +
                     i2 * 10000000 +
                     i3 * 1000000 +
                     i4 * 100000 +
                     i5 * 10000 +
                     i6 * 1000 +
                     i7 * 100 +
                     i8 * 10 +
                     i9 * 1
        where number % 9 == 0
        select number;

      // run it! 
      foreach (int n in query)
        Console.WriteLine(n);
    }
  }
}

Note that no attempt at all has been made to optimize the code (for instance, applying individual divisibility criteria for the different positions). I firmly believe in Don Knuth‘s statement that “premature optimization is the root of all evil,” and the program as it stands perfectly satisfied my expectations. On my laptop, it finds the only solution to the problem (the number 381654729) in much less than a second.