2 de mayo de 2023

Org-Mode: Exportación a LaTeX con resaltado de sinaxis para código fuente.

Para exportar desde Org a LaTeX con resaltado de sintaxis para código fuente, se puede utilizar el paquete minted, el cual es parte de la distribución "Tex Live". Para ello se pueden seguir los siguientes pasos.

1.1. Instalar dependencias y paquetes.

En distribuciones basadas en Debian se instalan los siguientes paquetes:

  • python3-pygments
  • texlive-latex-extra

1.2. Configurar parámetros de Org-LaTeX

  1. Usar minted para listados:

    (setq org-latex-listings 'minted)
    
  2. Incluír minted en la lista de paquetes por defecto:

    (setq org-latex-packages-alist '(("" "minted")))
    
  3. Cambiar opciones de compilación para compatibilidad con minted:

    (setq org-latex-pdf-process
          '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
            "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
    
  4. Opcionalmente se puede activar el plegado de líneas de código para evitar truncamiento de aquellas que sean demasiado largas para el ancho de página.

    (setq org-latex-minted-options '(("breaklines" "true")
                                     ("breakanywhere" "true")))
    

1.3. Ejemplo

Considerando y aplicando todo lo anterior, se puede generar la siguiente configuración:

;; Exportación de Org a LaTeX con resaltado de sinaxis.
(setq org-latex-listings 'minted  ;Usar Minted p/listados
      org-latex-packages-alist '(("" "minted"))
      ;; Activa plegado de líneas
      org-latex-minted-options '(("breaklines" "true")
                                 ("breakanywhere" "true"))
      ;; Ajusta opciones de compilación
      org-latex-pdf-process
      '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
        "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

Autor: Alcides Flores Pineda

Created: 2023-05-02 mar 14:06

Validate

20 de mayo de 2022

Funciones matemáticas con Lisp

Funciones matemáticas con Lisp

Funciones matemáticas con Lisp

1 Funciones Factoriales

El factorial de un número natural n se define como el producto de todos los números naturales hasta n:

\begin{equation} n!=1\cdot2\cdot3\cdot\ldots\cdot(n-1)\cdot n \end{equation}

O bien en forma abreviada:

\begin{equation} n!=\prod\limits_{i=1}^{n}i \end{equation}

Considerando también el factorial de cero como \(0!=1\), tenemos que se puede expresar de forma recursiva como:

\begin{equation} n!=\begin{cases} 1 & (n < 2) \\ n\cdot(n-1)! & (n >= 2) \end{cases} \end{equation}

Para todo número n tal que \(n\in\left\{ 0,\mathbb{N}\right\}\)

Partiendo de las definiciones anteriores, se pueden programar dos versiones de la función factorial.

1.1 Versión recursiva

Utilizando la última definición (3) tenemos:

(defun r-factorial (n)
  (if (< n 2) 1
      (* n (r-factorial (- n 1)))))

1.2 Versión iterativa

Utilizando la segunda definición (2) tenemos:

(defun i-factorial (n)
  (do ((i 0 (+ i 1))
       (p 1 (* p (+ i 1))))
      ((>= i n) p)))

2 Cálculo aproximado de e

El valor de constante matemática e es un límite:

\begin{equation} e=\lim_{n \to \infty}(1 + \frac{1}{n})^{n} \end{equation}

Y también puede expresarse como el valor de la suma infinita

\begin{equation} e=\sum\limits_{n=0}^{\infty}\frac{1}{n!}=1+\frac{1}{1!}+\frac{1}{2!}+... \end{equation}

Por lo tanto, puede calcularse su valor (aproximado) con un programa.

2.1 Versión factorial

Usando la versión factorial de la definición anterior (5) tenemos lo siguiente.

(defun approx-e (p)
  (labels ((fact (n) (do ((i 0 (+ i 1))
                          (p 1 (* p (+ i 1))))
                         ((>= i n) p))))
          (do ((i 0 (+ i 1))
               (s 1 (+ s (/ 1.0 (fact (+ i 1))))))
              ((>= i p) s))))

3 Constantes Matemáticas π y e

Independientemente de la implementación a usar (Common-Lisp, Scheme, Racket, etc) y de que dichas constantes se encuentren definidas en las biliotecas o estándares respectivos. Los valores de las constantes π y e, siempre pueden ser obtenidos a partir de sus definiciones matemáticas por medio de las funciones inversa del coseno (\(\arccos(x)\)) y exponencial (\(e^{x}\)) respectivamente:

\begin{equation} \arccos(-1)=\cos^{-1}(-1)=\pi \end{equation}

Y en el caso de la función exponencial, se tiene obviamente que:

\begin{equation} e^{1}=e \end{equation}

Por lo cual, sus valores se obtienen invocando las funciones correspondientes:

;; Valor de PI
(acos -1)
;; ==> 3.1415927

;; Valor de E
(exp 1)
;; ==> 2.7182817

Created: 2022-05-20 vie 16:54

Validate

20 de noviembre de 2017

Redirección fuertemente tipada en ASP.NET MVC

Recientemente volví a caer en un error de los que considero de primer año: cambié la firma de un método de acción en un controlador de MVC y olvidé buscar el 100% de todos los lugares que redirigían hacia él. Por supuesto, esto causó un error --que afortunadamente fue encontrado en QA-- ya que el código en cuestión (claro está) no tenía pruebas de ninguna clase.

El código en cuestión era similar al siguiente:

public ActionResult Foo()
{
  ...
  return RedirectToAction(
      "SomeAction", "SomePlace",
      new { usr = "Username", key = blah });
}

El problema es que fue necesario agregar un parámetro al método SomePlaceController.SomeAction, pero absolutamente nada en el código me advirtió que había nada más que cambiar además de la función de JavaScript que estaba modificando en ese momento. Fue la gota que derramó el vaso: desde que he estado dando mantenimiento a código legacy en ASP.NET MVC, no sé cuantas veces me ha sucedido este mismo problema y ya era hora de hacer algo al respecto, así que decidí que ya que estoy trabajando con C#, tal vez podría utilizar su sistema de tipos a mi favor. Lo que me gustaría poder hacer sería algo así:

public ActionResult Foo()
{
  ...
  return RedirectToAction<SomePlaceController>(
    x => x.SomeAction("Username", blah, 125));
}

Por supuesto, no existe una definición de RedirectToAction como esta, pero es relativamente fácil de definir usando expresiones de linq:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;

using RouteValue = System.Tuple<string, object>;

public static class RedirectTo
{
  #region Public Methods

  private const string InvalidTarget =
    "El argumento debe ser una invocación a un método del controlador.";

  private const string UnsupportedExpression = "Expresion no soportada";

  #endregion

  #region Public Methods

  public static RedirectToRouteResult RedirectToAction<T>(
    this T controller, Expression<Action<T>> selector)
    where T : Controller => Action(selector);

  public static RedirectToRouteResult Action<T>(
    Expression<Action<T>> selector) where T : Controller
  {
    if (!(selector.Body is MethodCallExpression))
    {
      throw new ArgumentException(InvalidTarget, nameof(selector));
    }

    var methodEx = (MethodCallExpression)selector.Body;

    var routeValues = MakeRouteValues(
      controllerName: GetControllerName<T>(),
      actionName: GetActionName(methodEx),
      arguments: GetArguments(methodEx));

    return new RedirectToRouteResult(routeValues);
  }

  #endregion

  #region Methods

  private static string GetControllerName<T>() where T : Controller
  {
    const string Suffix = "Controller";

    var name = typeof(T).Name;

    return name.EndsWith(Suffix)
      ? name.Substring(0, name.Length - Suffix.Length)
      : name;
  }

  private static string GetActionName(MethodCallExpression expression)
    => expression.Method.Name;

  private static RouteValue[] GetArguments(MethodCallExpression expression)
  {
    var names = expression.Method.GetParameters().Select(o => o.Name);
    var values = expression.Arguments.Select(GetValueOf);

    return names
      .Zip(values, (n, v) => new RouteValue(n, v))
      .Where(o => o.Item2 != null)
      .ToArray();
  }

  private static object GetValueOf(Expression expression)
  {
    if (expression is MemberExpression)
    {
      return GetValueOf((MemberExpression)expression);
    }
    else if (expression is ConstantExpression)
    {
      return GetValueOf((ConstantExpression)expression);
    }

    throw new InvalidOperationException(UnsupportedExpression);
  }

  private static object GetValueOf(MemberExpression expression)
  {
    var container = GetValueOf(expression.Expression);

    if (expression.Member is FieldInfo)
    {
      return ((FieldInfo)expression.Member).GetValue(container);
    }
    else if (expression.Member is PropertyInfo)
    {
      return ((PropertyInfo)expression.Member).GetValue(container);
    }

    throw new InvalidOperationException(UnsupportedExpression);
  }

  private static object GetValueOf(ConstantExpression expression)
    => expression.Value;

  private static RouteValueDictionary MakeRouteValues(
    string actionName, string controllerName, params RouteValue[] arguments)
  {
    var args = new RouteValue[]
      {
        new RouteValue("action", actionName),
        new RouteValue("controller", controllerName),
      };

    return new RouteValueDictionary(args
      .Concat(arguments)
      .ToDictionary(o => o.Item1, o => o.Item2));
  }

  #endregion
}

Con esto, el sintaxis que obtenemos es muy similar al que queríamos:
public ActionResult Foo()
{
  ...
  return RedirectTo.Action<SomePlaceController>(
    x => x.SomeAction("Username", blah, 125));
}

o incluso
public ActionResult SomeAction(
  string username, Guid key, long token)
{
  var credentials = GetCredentials(username, token);
  ...
  return this.RedirectToAction(x => x.Continue(username, credentials));
}

Con esto, me aseguraré de que la próxima vez que necesite agregar un parámetro al un método de un controlador, no pueda hacerlo sin tener por lo menos que revisar cualquier redirección que haya hacia el mismo (por lo menos en código compilado del lado del servidor).

11 de septiembre de 2017

"agile" está en el ojo de quien mira

Recientemente cambié de trabajo –se pueden dar cuenta por que tengo tiempo de escribir esto– y mi nuevo hogar es una empresa que ha decidido abrazar agile como su forma de hacer las cosas. Así, “agile” con “a” minúscula, como me lo dijera Sergio Acosta la primera vez que platicamos, refiriéndose a un agile sin marcas registradas, sin pedigrí.

En un principio pensé que tendría yo algo que enseñar al equipo al cual me estaba integrando. Si bien, hemos tenido algunas pláticas por demás interesantes sobre algunas cuestiones de diseño, TDD, etc., de primera instancia este último par de meses han sido una incesante carrera por entender el negocio. No digo que no hay nada que mejorar –siempre hay algo– si no que en el punto donde se encuentra el equipo actualmente, son muy buenos en lo que hacen:
  • Han integrado elementos a su proceso de desarrollo de aquí y de allá como apoyo al quehacer del día a día, sin seguir necesariamente una metodología fija.
  • Cuando sucede un incidente que afecta al equipo, al negocio o a la capacidad del equipo de entregar valor, se dedica el tiempo necesario para entenderlo y tomar medidas para que no vuelva a pasar (o por lo menos que no nos vuelva a tomar desprevenidos).
  • Tienen los problemas habituales cualquier equipo trabajando con código legacy, especialmente cuando se trata de desarrollar funcionalidad nueva sin caer en los mismos antipatrones anteriores… y a veces no lo logramos.
  • Mientras que algunos jamás escriben una sola prueba unitaria (que en ocasiones resulta prácticamente imposible por las características del código), otros intentan hacer pruebas por lo menos para cada nueva característica y algunos incluso intentan hacer pruebas para el código legacy antes de intentar cambiarlo.
    Pero nuevamente, a pesar de las limitaciones, existe una ventaja fundamental: el equipo sabe en dónde está la mayor parte de la deuda técnica y aprovecha cualquier oportunidad para reducirla.
  • Técnicamente es un equipo sólido, pero no hay rock stars. Y creo seriamente que la forma de trabajo del mismo de hecho previene el surgimiento de dichos personajes. Simplemente no son necesarios.

Pero quizás la característica más notable del equipo es su capacidad de comunicación (por supuesto, incluyendo a “producto”, pues todos somos un solo equipo). Algunos mientras que miembros del equipo se integran en sesiones impromptu de pair programming, otros trabajan la mayor parte del tiempo de esta forma, e incluso otros casi siempre trabajan solos. Pero sin importar cual sea su configuración favorita para trabajar, la mayor parte del tiempo todo el equipo sabe lo que están haciendo los demás, por qué e inclusive cómo. Nada pasa desapercibido. Ni una falla, ni un éxito. Todos están prestos a ofrecer ayuda cuando se necesita.

Me atrevo a opinar que esta es la característica que hace de este equipo un equipo efectivo, cohesivo y valioso para el negocio. Y es sin duda, el aspecto que debo aprender y dominar antes siquiera de hablar siquiera por primera vez de asuntos técnicos.

Así que no, lo que hacemos no es Scrum, ni Kanban, ni FDD, ni mucho menos XP. Solo hacemos agile y nada más.

20 de noviembre de 2014

Probamos el béisbol y no funcionó

por Ron Jeffries
¿Una alegoría?
¿Sarcasmo?
¿Plagio humorístico?

Usted decida.
Los defensores fanáticos del béisbol nos dicen que es un juego muy emocionante, divertido de jugar y divertido de ver. Ellos son claramente estúpidos o malvados, o ambos, porque nosotros probamos el béisbol y no funcionó.

En primer lugar, los requisitos para el juego son estúpidos: no escalan. Dicen que se necesitan al menos nueve jugadores por lado. Eso es estúpidamente ineficiente. El número mínimo de jugadores es claramente cuatro: tres hombres envasados y uno bateando. Esa es la forma en que nosotros jugamos: cuatro personas por lado.

Con sólo cuatro jugadores, no necesitamos todas esas bases (otra ineficiencia evidente y la falla de escala), así que sólo utilizamos una base: el home. Cuando un bateador hace un hit, sólo se corre alrededor del diamante (pero véase más adelante), regresando a la base de "home". Al principio hacíamos al corredor recorrer los noventa pies por lado marcados por los proponentes del béisbol, pero por alguna razón los chicos del béisbol no se dieron cuenta que, en la mayoría de los casos, ¡360 ​​pies son demasiado para que el corredor los corra en un solo hit!. Nadie pudo completar toda la vuelta sin ser puesto out. Encontramos que un círculo con un diámetro de 28,64 pies es ideal. (El círculo, con su relación óptima de área contra circunferencia, es mucho más eficiente que el cuadrado propuesto por los promotores del béisbol.)

El equipo "requerido" por estos chicos del béisbol es ridículo, anacrónico, exigente y cruel. En primer lugar, se supone que la "bola" esté cubierta con piel de caballo. Esto es a la vez cruel con los caballos, y discriminatorio en contra de los proveedores tradicionales de forros para bolas: los cerdos y las vacas. En vista de esto, hemos utilizado otras bolas. Tratamos una pelota de cordel sin cubierta, pero su tamaño disminuye continuamente, haciendo el juego cada vez más difícil, especialmente en las últimas entradas. Las pelotas de golf viajan demasiado lejos y eran difíciles de atrapar. Uno de nuestros equipos utilizó las cabezas de muñecas que habían tomado de sus hermanas, pero esto condujo a dificultades en el hogar. El cómo los proponentes del béisbol pueden impulsar un juego que lleva a este tipo de problemas, va más allá de nuestra comprensión.

Finalmente nos decidimos por los calcetines de lana, rellenos con papel de desecho y anudados. Estos eran fáciles de atrapar, no iban demasiado lejos, y el resto colgante del calcetín los hace fáciles lanzar.
Los llamados "bats"1 son también una mala idea. En primer lugar no son nada parecido a esos peludos caza-insectos del cielo nocturno. La confusión causada por ese nombre ha retrasado más de un juego. Además, los "bats de béisbol" (tuvimos usar el nombre totalmente calificado) son caros de adquirir y difíciles de fabricar. Tratamos dejar que cada hombre al bat usara cualquier cosa que quisiera, pero esto retrasó el juego y dificultó la tarea de llevar estadísticas (una parte clave del béisbol, de acuerdo con sus proponentes). Nos ajustamos a usar de palos de los alrededores del campo. Las ramas de pino funcionan mejor, ya que son fáciles de romper las ramas de los árboles, y como las manos se ponen pegajosas por la resina, se hace más fácil atrapar el calcetín, cuando se está en el campo.

Y hablando del campo, otro problema que los defensores del béisbol no han resuelto adecuadamente es que con un pitcher, un catcher, y sólo dos fielders, la bola va a dar con demasiada frecuencia lejos de los jugadores, lo que causa correr en exceso y también que los juegos tomen mucho tiempo y tengan puntajes demasiado altos. Finalmente nos dimos cuenta - por qué los chicos del béisbol originales no lo hicieron, no lo podemos imaginar - que el uso de uno de los jugadores como el catcher es terriblemente ineficiente. En cambio, si el "palero" --el término "batter" es a la vez ambiguo e inadecuado en vista de los cambios en el equipo que encontramos necesarios para poder jugar en absoluto-- no conecta, simplemente recoge el calcetín él mismo y lo arroja de nuevo al lanzador. Esto nos permite disponer de los cuatro jugadores defensivos en el campo, lo cual es un uso mucho más eficiente de los recursos, una prueba más de que el béisbol no escala.

Sin embargo, lo que finalmente nos hizo abandonar toda esa idea del "béisbol", es lo siguiente: a pesar de todas las mejoras que hicimos, ¡el juego no es divertido en absoluto!

Probamos el béisbol y no funcionó.


Artículo original disponible en http://xprogramming.com/articles/jatbaseball.

Notas

1. Se conservan los términos en inglés ya que en caso contrario, las bromas en doble sentido (pun) se pierden con la traducción.

6 de marzo de 2013

String Calculator revisada

Bueno, después de practicar esta kata unas cuantas veces, logré bajar mi tiempo promedio a 12 minutos. Esta vez, ¡cumpliendo con todos los requerimientos del kata!

A continuación anexo el código actualizado:

Pruebas:

#!/usr/bin/env python

import unittest
#from strcalc_re import add
from strcalc import add


class SimpleTest(unittest.TestCase):

  def testEmptyString(self):
    self.assertEquals(0, add(''))

  def testSingleNumberString(self):
    self.assertEquals(2, add('2'))
    self.assertEquals(2, add('\n2'))
    self.assertEquals(2, add('2\n'))

  def testTwoNumberString(self):
    self.assertEquals(7, add('2\n5'))
    self.assertEquals(7, add('2, 5'))

  def testThreeNumberString(self):
    self.assertEquals(15, add('2,5,8'))
    self.assertEquals(15, add('2,5\n8'))


class UserDefinedSeparators(unittest.TestCase):

  def testEmptyString(self):
    self.assertEquals(0, add('//|\n'))

  def testSingleNumberString(self):
    self.assertEquals(2, add('//;\n2'))

  def testTwoNumberString(self):
    self.assertEquals(7, add('///\n2/5'))

  def testThreeNumberString(self):
    self.assertEquals(15, add('//#\n2#5#8'))
    self.assertEquals(15, add('''//!\n2!5\n8'''))


class NegativesNotAllowed(unittest.TestCase):

  def runTest(self):
    with self.assertRaises(ValueError):
      add('2,-1')
    with self.assertRaises(ValueError):
      add('//&\n2&-1')


class IgnoreGreaterThan1000(unittest.TestCase):

  def runTest(self):
    self.assertEquals(1, add('1\n1001'))
    self.assertEquals(8, add('//$\n1\n1002$3$4$1005'))


class ArbitraryLengthSeparators(unittest.TestCase):

  def runTest(self):
    self.assertEquals(8, add('//[$$$$]\n1\n1002$$$$3$$$$4\n0'))
    self.assertEquals(6, add('//[***]\n1***3***2\n**1'))


class MultipleSeparators(unittest.TestCase):

  def runTest(self):
    self.assertEquals(10, add('//[$][#][%]\n1$2#3%4'))
    self.assertEquals(10, add("//[a][lil][delim]\n1a2lil3delim4"))


unittest.TestSuite.addNestedSuites = lambda self, *classes: \
  self.addTests(map(unittest.makeSuite, classes))

unittest.TestSuite.addAllTests = lambda self, *classes: \
  self.addTests(cls() for cls in classes)


def suite():
  result = unittest.TestSuite()
  result.addNestedSuites(SimpleTest, UserDefinedSeparators)
  result.addAllTests(NegativesNotAllowed, IgnoreGreaterThan1000,
                     ArbitraryLengthSeparators, MultipleSeparators)
  return result


if __name__ == '__main__':
  runner = unittest.TextTestRunner(verbosity=2)
  runner.run(suite())

Existen algunas diferencias con respecto a la última versión que publiqué:

  • A diferencia del post anterior, esta ocasión he organizado las pruebas según el requerimiento, a fin de hacerlas más comprensibles.
  • La clase MultipleSeparators incluye el caso de prueba para los requerimientos 8 y 9.
  • Las lineas 4 y 5 indican que estas pruebas pueden ser usadas con dos implementaciones del código de producción: strcalc y strcalc_re.

Código: Implementación con expresiones regulares

#!/usr/bin/env python

import re


def parse_separators(line):
  if line.startswith('//[') and line.endswith(']'):
    result = line[3:-1].split('][')
  else:
    result = line[2:3]
  return '|'.join(re.escape(r) for r in result)


def config(s):
  separators = ','
  lines = s.split()
  if lines and lines[0].startswith('//'):
    separators = parse_separators(lines[0])
    lines = lines[1:]
  return (lines, separators)


def to_num(s):
  try:
    return int(s)
  except ValueError:
    return 0


def to_nums(lines, sep):
  pattern = re.compile(sep)
  return [to_num(part) for line in lines for part in
          pattern.split(line)]


def negatives_in(nums):
  return [n for n in nums if n < 0]


def add(s):
  (lines, sep) = config(s)
  nums = to_nums(lines, sep)
  negatives = negatives_in(nums)
  if negatives:
    raise ValueError('No se permiten negativos: %r' % negatives)
  return sum(n for n in nums if n <= 1000)

La forma más sencilla que encontré de soportar múltiples separadores fue utilizar re.split (linea 33). La complicación, claro, es que los delimitadores soportados por la implementación deberían ser arbitrarios. Sin embargo, re interpreta ciertos caracteres de forma especial, así que es necesario evitar que re haga de las suyas. ¿La solución? Escapar los delimitadores antes de llamar a re.split (linea 11).

Código: Implementación sin expresiones regulares

Por supuesto, quienes me conozcan sabrán que no me iba a contentar con la solución de la librería de python sin haber por lo menos implementado esa solución por mi mismo una vez. El resultado es algo así:

#!/usr/bin/env python

import re

def parse_separators(line):
  if line.startswith('//[') and line.endswith(']'):
    return line[3:-1].split('][')
  return [line[2:3]]


def config(s):
  separators = [',']
  lines = s.split()
  if lines and lines[0].startswith('//'):
    separators = parse_separators(lines[0])
    lines = lines[1:]
  return (lines, separators)


def to_num(s):
  try:
    return int(s)
  except ValueError:
    return 0


def to_nums(lines, sep):
  for s in sep:
    lines = sum((line.split(s) for line in lines), [])
  return map(to_num, lines)


def negatives_in(nums):
  return [n for n in nums if n < 0]


def add(s):
  (lines, sep) = config(s)
  nums = to_nums(lines, sep)
  negatives = negatives_in(nums)
  if negatives:
    raise ValueError('No se permiten negativos: %r' % negatives)
  return sum(n for n in nums if n <= 1000)

Las lineas resaltadas indican las ediciones para pasar de una versión a la otra.

La parte más dificil de esta versión fue encontrar una manera eficiente y legible de expandir una lista anidada en una lista simple con todos los elementos de sus sub-listas. Despues de probar varias soluciones llegué a una comprensión un tanto monstruosa:

lines = [part for parts in [line.split(s) for line in lines] for part in parts]

¡Ugh!, ¡Me lloran los ojos de solo verla!

La solución es simplemente que sum aplica el operador + a todos los elementos de la secuencia que recibe como argumento, sustituyendo in situ el resultado y las listas, claro está, implementan dicho operador (linea 29). Esta aplicación puede parecer un hack, por lo que es posible encapsularla en una función con un nombre ilustrativo como:

def to_nums(lines, sep):
    flatten = lambda nested_list: sum(nested_list, [])
    for s in sep:
        lines = flatten(line.split(s) for line in lines)
    return map(to_num, lines)

Sin embargo, me pregunto ¿qué tanto realmente gana el código en legibilidad con dicho cambio?

Saludos

6 de febrero de 2013

String Calculator en Python

Para complementar la entrada del joven +Alcides Flores Pineda, aquí anexo mi versión de la Kata en Python:

Pruebas:

#!/usr/bin/env python
import unittest
from strcalc import calc

def expected(exception):
    def argcatcher(f):
        def wrapper(self, *args):
            self.assertRaises(exception, lambda: f(self, *args))
        return wrapper
    return argcatcher


class StringCalcTest(unittest.TestCase):
    def testEmptyString(self):
        self.assertEquals(0, add(""))

    def testOneNumberString(self):
        self.assertEquals(2, add("2"))

    def testTwoNumberString(self):
        self.assertEquals(7, add("2,5"))

    def testThreeNumberString(self):
        self.assertEquals(15, add("2,5,8"))

    def testThreeNumberStringNewLine(self):
        self.assertEquals(15, add("2,5\n8"))

    def testEmptyStringWithUserSeparator(self):
        self.assertEquals(0, add("//;\n"))

    def testOneNumberStringWithUserSeparator(self):
        self.assertEquals(2, add("//;\n2"))

    def testTwoNumberStringWithUserSeparator(self):
        self.assertEquals(7, add("///\n2/5"))

    def testThreeNumberStringWithUserSeparator(self):
        self.assertEquals(15, add("//#\n2#5#8"))

    def testThreeNumberStringNewLineAndUserSeparator(self):
        self.assertEquals(15, add("//!\n2!5\n8"))

    @expected(ValueError)
    def testNegativesNotAllowed(self):
        add("2,-1")

    @expected(ValueError)
    def testNegativesNotAllowedWithUserSeparator(self):
        add("//&\n2&-1")

    def testIgnoreGreaterThan1000(self):
        self.assertEquals(1, add("1\n1001"))

    def testIgnoreGreaterThan1000WithUserSeparator(self):
        self.assertEquals(8, add("//$\n1\n1002$3$4$1005"))


if __name__ == '__main__':
    unittest.main()

Código:

#!/usr/bin/env python

def parse(s):
    return int(s) if s else 0

def tonums(lines, sep=','):
    return [parse(part) for line in lines for part in line.split(sep)]

def config(s):
    lines = s.split()
    if lines and lines[0].startswith('//'):
        return lines[1:], lines[0][2:]
    return lines, ','

def add(s):
    lines, sep = config(s)
    nums = tonums(lines, sep)
    negatives = [n for n in nums if n < 0]
    if negatives:
        raise ValueError('No se permiten negativos: %r' % negatives)
    return sum(n for n in nums if n <= 1000)

Al igual que en el caso de +Alcides Flores Pineda y +rodrigo salado anaya , el presente código no implementa el requerimiento de separadores de longitud arbitraria.

El código resultante es más largo que la versión en scheme/kawa, en algunos casos por la verbosidad propia de las comprensiones y generadores de Python, y en otras por las diferencias propias de la implementación, más que por cuestiones sintácticas.

A mi en lo particular me resulta muy ilustrativo ver las diferentes respuestas que se han generado para la misma kata en diversos lenguajes o incluso de personas distintas usando el mismo lenguaje.

La siguiente parte del ejercicio es: ejecutarlo en 15 o menos minutos todos los días por una semana y al cabo de ese tiempo, reflexionar:

  • ¿Mi solución a la kata está evolucionando cada vez que la ejecuto o por el contrario, repito los mismos pasos "como periquito"?
  • ¿Puedo encontrar alguna forma más elegante/clara/eficiente de resolver el problema o alguna parte de el mismo?
  • ¿Si la primera vez resolví el problema de forma, digamos, procedural, puedo intentar una solución orientada a objetos?, ¿funcional?
  • ¿Qué ventajas/desventajas tendrían dichas soluciones?

Saludos