Recetas en Python, Java, Perl y otros lenguajes

Internet ofrece un montón de lugares en los cuales puedes buscar pedazos de código, para aprender como hacer algo rápidamente en su lenguaje de programación favorito. Por ejemplo, como las tiendas de ‘todo a un dolar’, tienes sitios con pedazos de código como http://code.activestate.com/recipes/langs/; en otros casos si quieres una discusión un poco más profunda de como se hizo algo, con opiniones a favor y en contra, entonces tienes http://stackoverflow.com/.

¿Cual es tu favorito? Yo aún recuerdo cuando la única forma de aprender algo en Unix era escribiendo “man $command” o “info $command”, ya que Google no existia…

Guardando datos usando formatos de archivo a la medida, en Python 3

En Venezuela una de las pocas formas de saber la paridad entre el dolar “paralelo” y el Bolivar fuerte es utilizando el portal “DolarToday”. El sitio web (https://dolartoday.com/historico-dolar/) ofrece datos que van desde el 2010 hasta el presente, en los cuales puede ver la paridad entre las dos monedas.

En un arranque de ociosidad, decidí bajarme el archivo de Excel con las tasas de conversión, lo exporte a CSV y de allí escribí un programa en Python 3 que hace lo siguiente:

  • Extendiende la clase ‘dict’ en Python para mantener un diccionario con claves ordenadas, el cual se pueda guardar y recuperar a si mismo desde el disco duro
  • Usar struct.Struct para guardar y leer data binaria (también muestro como leer un archivo comprimido con gzip).

 

El código a continuación:

#!/usr/bin/env python3
# Simple program to save the 'Dolartoday' extra official dollar rates from CSV to a custom format
# @author Jose Vicente Nunez, josevnz@kodegeek.com
import os, sys, datetime, re, gzip, struct
from optparse import OptionParser, OptionValueError
from datetime import datetime

class DollarToday:
    
    def __init__(self, ddate, value):
        if isinstance(ddate, datetime):
            self.__date = ddate
        else:
            self.__date = datetime.strptime(ddate, "%m-%d-%Y")
        self.__value = float(value)
        assert self.__value > 0.0, "¡{} para la fecha {} es invalida!".format(value, self.__date)
        
    @property
    def date(self):
        return self.__date
    
    @date.setter
    def date(self, date):
        assert isinstance(date, datetime), "Invalid date {}".format(date)
        self.__date = date
    
    @property
    def value(self):
        return self.__value
    
    def __str__(self):
        return "DollarToday[date={}, value={}]".format(self.__date, self.__value)
    
    def __hash__(self):
        return str(id(self))
            
class DollarCollection(dict):
    
    __FILE_MAGIC = b"DLR\x00" # Me invente este numero mágico...
    __FILE_VERSION = b"\x00\x01"
    __dollarStruct = struct.Struct(" self.__FILE_VERSION:
                raise "Unsupported file version: {}, expected {}".format(version, self.__FILE_VERSION)
            self.clear()
            while True:
                data = fh.read(self.__dollarStruct.size)
                if len(data) == 0:
                    break
                numbers = self.__dollarStruct.unpack(data)
                #if verbose:
                #    print("{}".format(",".join([str(x) for x in numbers])))
                dolar = DollarToday(
                                    datetime.fromordinal(numbers[0]),
                                    numbers[1]
                                    )
                self[dolar.date] = dolar
        
        except (Exception) as err:
            raise
        finally:
            if (fh is not None):
                fh.close()
    def readCsv(self, file):
        fh = None
        tempMap = {}
        try:
            fh = open(file, "r", encoding="UTF-8")
            for line in fh.readlines():
                # 6-23-2010       9.92
                (date, value) = line.strip().split("\t")
                if re.match("Fecha", date):
                    continue
                try:
                    dolVsBol = DollarToday(date, value)
                    tempMap[dolVsBol.date] = dolVsBol
                except (Exception) as err:
                    print(err)
            self.clear()
            self.update(tempMap)
        except (Exception) as err:
            raise
        finally:
            if (fh is not None):
                fh.close()

    def __str__(self):
        return "Records={},\n{}".format(len(self), ",\n".join([str(date) for date in self.values()]))

def main(options):

    verbose = options.verbose
    dc = DollarCollection()
    if options.report == None:
        dc.readCsv(options.read)
        dc.save(options.write)
    else:
        dc.readBinary(options.report, verbose)
    if verbose:
        print("{}".format(dc))

if __name__ == "__main__":
    
    usagetext = """
%prog --read csv.file --write binary.file

Or:

%prog --report binary.file

"""

    op = OptionParser(usage=usagetext)
    op.add_option(
                  "-r", "--read",
                  action="store", 
                  dest="read", 
                  help="Ruta completa del archivo CSV fuente, cada linea tiene una clave=valor")
    op.add_option(
                  "-w", "--write", 
                  action="store", 
                  dest="write", 
                  help="Ruta completa para guardar el archivo en nuevo formato binario")
    op.add_option(
                  "-p", "--report", 
                  action="store", 
                  dest="report",
                  help="Lee el archivo binario en memoria. No es compatible con --read y --write")
    op.add_option(
                  "-v", "--verbose", 
                  action="store_true", 
                  default=False, 
                  dest="verbose", 
                  help="Activar impresión de valores por pantalla")
    
    (options, values) = op.parse_args()

    main(options)

Un ejemplo de como correrlo:

DolarToday.py --read /Users/josevnz/Documents/dolartoday.csv --write /Users/josevnz/Documents/dolartoday.jose --verbose
DolarToday.py --report /Users/josevnz/Documents/dolartoday.jose --verbose

Y la salida luce como esto:

Records=1917,
DollarToday[date=2010-06-23 00:00:00, value=9.92],
DollarToday[date=2010-06-25 00:00:00, value=8.05],
DollarToday[date=2010-06-26 00:00:00, value=8.05],
DollarToday[date=2010-06-27 00:00:00, value=7.91],
DollarToday[date=2010-06-28 00:00:00, value=7.91],
DollarToday[date=2010-06-29 00:00:00, value=7.92],
DollarToday[date=2010-06-30 00:00:00, value=7.97],
DollarToday[date=2010-07-01 00:00:00, value=7.97],
DollarToday[date=2010-07-02 00:00:00, value=7.98],

Si tan sólo el gobierno de Nicolás Maduro fuera tan transparente como mis programas 🙂

Guardando y recuperando datos en Python usando Pickle

Muy fácil de usar. Aquí les muestro como grabar un objeto (Account) el cual tiene otros objetos adentro (lista de objetos tipo ‘Transaction’).

#!/usr/bin/env python3
# @author Jose Vicente Nunez - josevnz@kodeek.com
import os, os.path, pickle, tempfile

class Transaction:
    
    def __init__(self, amount, date, currency="USD", conv_rate=1.0, description=None):
        self.__amount = amount
        self.__date = date
        self.__currency = currency
        if conv_rate < 0:
            raise ValueError("Invalid amount:{0}".format(conv_rate))
        self.__conv_rate = float(conv_rate)
        self.__description  = description
    
    @property
    def amount(self):
        return self.__amount
    
    @property
    def date(self):
        return self.__date

    @property
    def currency(self):
        return self.__currency
    
    @property
    def description(self):
        return self.__description
    
    @property
    def conv_rate(self):
        return self.__conv_rate
    
    @property
    def usd(self):
        return self.__conv_rate * self.__amount
    
class Account:
    
    def __init__(self, number, name, transactions = []):
        self.__number = number
        self.__name = name
        if transactions == None:
            self.__transactions = []
        else:
            self.__transactions = transactions
    
    @property
    def number(self):
        return self.__number
    
    @property
    def name(self, name):
        if name == None:
            return self.__name
        if len(name) < 6:
            raise ValueError("Account name too short!")
        self.__name = name

    def __len__(self):
        return len(self.__transactions)
    
    @property
    def name(self):
        return self.__name
        
    @property
    def balance(self):
        balance = 0
        for bal in self.__transactions:
            balance += bal.usd
        return balance
    
    @property
    def all_usd(self):
        return len([t for t in self.__transactions if t.currency == "USD"]) == len(self.__transactions)

    def apply(self, transaction):
        if not isinstance(transaction, Transaction):
            raise ValueError("Invalid argument, can only add transactions!")
        self.__transactions.append(transaction)

    def __getFilename(self):
        return os.path.join(tempfile.gettempdir(), str(self.number))

    def save(self):
        fn = self.__getFilename()
        fh = None
        try:
            fh = open(fn, 'wb')
            pickle.dump([self.__name, self.__number, self.__transactions], fh, pickle.HIGHEST_PROTOCOL)
        except (EnvironmentError, pickle.PicklingError) as Error:
            raise SaveError(str(err))
        finally:
            if fh is not None:
                fh.close()

    def load(self):
        fn = self.__getFilename()
        fh = None
        try:
            fh = open(fn, 'rb')
            (self.__name, self.__number, self.__transactions) = pickle.load(fh)
        except (EnvironmentError, pickle.UnpicklingError) as Error:
            raise LoadError(str(err))
        finally:
            if fh is not None:
                fh.close()
    
if __name__ == "__main__":
    transactions = []
    transactions.append(
                        Transaction(
                                    15.0, 
                                    "06-06-1966", 
                                    "USD", 
                                    1.0, 
                                    description="XXXX paid me some money"))
    transactions.append(
                        Transaction(
                                    -2, 
                                    "06-06-1966", 
                                    "USD", 
                                    1.0, 
                                    description="I had to pay YYYYY some cash"))
    transactions.append(
                        Transaction(
                                    10000, 
                                    "06-06-1966", 
                                    "BsF", 
                                    0.0001, 
                                    description="Maduro paid me some money in Venezuelan BS. LOL"))
    account = Account(666666, "Savings account", transactions)
    account.apply(Transaction(-3, "02-28-1016", "USD", 1.0, "Coffee time"))
    print(
          "'{0}' Balance: ${1}, all in USD: {2}".format(
                                                        account.name, 
                                                        account.balance, 
                                                        account.all_usd))
    account.save()
    account.load()
    print(
          "'{0}' Balance: ${1}, all in USD: {2}".format(
                                                        account.name, 
                                                        account.balance, 
                                                        account.all_usd))

Pickle no es seguro (ya que se lleva a cabo ninguna validación en el código leido desde el archivo), sin embargo es increíblemente conveniente para programas pequeños que requieren guardar datos rápidamente con estructuras de datos complejas, como por ejemplo objetos anidados.

¿Qué tienen en común los candidatos presidenciales y el lenguaje Python? Mucho más de lo que usted cree

Al menos Python es más fácil de entender :-). También me dió un excusa para mostrarles un poco de herencia y otros trucos de objetos en el lenguaje:

#!/usr/bin/env python3
# A little fun with the candidates for the US presidency for 2016 election year
# @author josevnz@kodegeek.com
#
class Candidate:
    
    def __init__(self, name, party, sex="F", age=18):
        self.__name = name
        self.__party = party
        self.__sex = "F" if sex not in ["M", "F"] else sex
        self.__age = 18 if not (18 < = age <= 120) else age
    
    @property
    def name(self):
        return self.__name
    
    @property
    def party(self):
        return self.__party
    
    @property
    def sex(self):
        return self.__sex
    
    @property
    def age(self):
        return self.__age
    
    def __hash__(self):
        return hash(id(self))
    
    def __str__(self):
        return "Candidate=[name={0}, party={1}, sex={2}, age={3}]".format(
                                                                      self.__name,
                                                                      self.__party,
                                                                      self.__sex,
                                                                      self.__age
                                                                      )
    
    def __repr__(self):
        return "{0}{1}{2}{3}{4}".format(
                                        self.__class__.__name__, 
                                        self.__name, 
                                        self.__party, 
                                        self.__sex, 
                                        self.__age
                                        )
    
    def __invert__(self):
        raise NotImplemented()
    
    # Are you a Democrat?    
    def __bool__(self):
        raise NotImplemented()
    
    def __lt__(self, other):
        return self.__age < other.__age
    
    def __gt__(self, other):
        return self.__age > other.__age
    
    def __eq__(self, other):
        return (
                self.__age == other.__age and 
                self.__name == other.__name and 
                self.__party == other.__party and 
                self.__sex == other.__sex
                )

class Republican(Candidate):
    
    def __init__(self, name, sex="F", age=21):
        return super().__init__(name, "Republican party", sex, age)
    
    def __bool__(self):
        return False
    
    def __invert__(self):
        raise NotImplementedError()
            
class Democrat(Candidate):
    
    def __init__(self, name, sex="F", age=21):
        return super().__init__(name, "Democratic party", sex, age)
    
    def __bool__(self):
        return True
    
    def __invert__(self):
        raise NotImplementedError()
    
if __name__ == "__main__":
        candidates = []
        candidates.append(Democrat("Hillary Clinton", "F", 68))
        candidates.append(Republican("Donald Trump", "M", 70))
        candidates.append(Democrat("Bernie Sanders", "M", 75))
        candidates.append(Republican("Marco Rubio", "M", 45))
        for candidate in sorted(candidates, reverse=True):
            isDemocrat = "yes" if candidate else "no"
            print("Candidate: {0}, democrat? {1}".format(candidate, isDemocrat))        

And the output for this run:

Candidate: Candidate=[name=Bernie Sanders, party=Democratic party, sex=M, age=75], democrat? yes
Candidate: Candidate=[name=Donald Trump, party=Republican party, sex=M, age=70], democrat? no
Candidate: Candidate=[name=Hillary Clinton, party=Democratic party, sex=F, age=68], democrat? yes
Candidate: Candidate=[name=Marco Rubio, party=Republican party, sex=M, age=45], democrat? no

Ahora si sólo pudiera escribir algo tan sencillo como esto para saber los resultados de las elecciones del 2016 🙂

Escribiendo ‘ls’ en Python3

El programa a continuación es un ejemplo de las cosas que se pueden hacer con Python 3. Para mí fue una excusa para aprender lo siguiente:

  • Uso de ‘.format’ para mostrar contenido con formato (mucho mejor que interpolación de cadena de caracteres con ‘%’)
  • La librería ‘OptionParser’ (Mejor que Getoptions)
  • Trucos con ‘list comprehensions’ , ordenaciones
  • Referencias a funciones

(Les debo el manejo de recursividad, me dio algo de flojera escribirlo :-))
 

#!/usr/bin/env python3
from optparse import OptionParser, OptionValueError
from collections import namedtuple
import locale, os, time
from _locale import LC_ALL
locale.setlocale(LC_ALL, "en_US.UTF-8") # locale -a
usage = '''
%prog [options] [path1 [path2] [... pathN]]]
The paths are optional; if not given '.' is used
@author: Jose Vicente Nunez (josevnz@kodegeek.com)
'''
Entry = namedtuple('Entry', 'name size modified')

def orderCheck(option, opt_str, value, parser):
    if value in ['n', 'name']:
        parser.values.order = "name"
    elif value in ['m', 'modified']:
        parser.values.order = "modified"
    elif value in ['s', 'size']:
        parser.values.order = "size"
    else:
        raise OptionValueError("Invalid value for --order received: {0}".format(value))

def getKeyByName(entry):
    return entry.name

def getKeyBySize(entry):
    return int(entry.size)

def getKeyByModif(entry):
    return int(entry.modified())

def createTuple(path):
    bits = os.stat(path)
    return Entry(path, bits.st_size, bits.st_mtime) 

def doLs(path, hidden, getKey):
    if (not os.path.isdir(path)):
        return path
    if hidden: # On Unix, hidden files start with '.'
        return sorted([ createTuple(os.path.join(path, entry)) for entry in os.listdir(path) ], key=getKey, reverse=False)
    return sorted([ createTuple(os.path.join(path, entry)) for entry in os.listdir(path) if entry[0] != "." ], key=getKey, reverse=False)

def doLsR(path, hidden, getKey):
    # TODO
    pass

def formatEntries(entries, modified, sizes):
    if entries == None:
        return
    dirs = 1
    files = 0
    for entry in entries:
        mod = time.ctime(entry.modified) if modified else ""
        size = entry.size if sizes else ""
        if os.path.isfile(entry.name):
            files += 1
        else:
            dirs += 1
        print("{modif}{theSize:>10,} bytes{name:>35}".format(modif=mod, theSize=size, name=entry.name))
    print("files={0}, directories={1}".format(files, dirs))
        
parser = OptionParser(usage=usage)
parser.add_option("-H", "--hidden", action="store_true", dest="hidden", default=False, help='Show hidden files [default: off]')
parser.add_option("-m", "--modified", action="store_true", dest="modified", default=False, help='Show last modified date/time [default: off]')
parser.add_option("-r", "--recursive", action="store_true", dest="recursive", default=False, help='Recurse into sub-directories [default: off]')
parser.add_option("-s", "--sizes", action="store_true", dest="sizes", default=False, help='Show sizes [default: off]')
parser.add_option("-o", "--order", action="callback", type="string",  callback=orderCheck, default="name", help='''Order by ('name', 'n', 'modified', 'm', 'size', 's') [default: name]''')
(options, args) = parser.parse_args()
hidden = options.hidden
modified = options.modified
recursive = options.recursive
sizes = options.sizes
order = options.order
getKey = getKeyByName
if order == 'size':
    getKey = getKeyBySize
elif order == 'modified':
    getKey = getKeyByModif
paths = args if len(args) > 0  else ["."]
lsCallback = doLs if not recursive else doLsR

for path in paths:
    formatEntries(lsCallback(path, hidden, getKey), modified, sizes)

IFIXIT: Salvando mi Iphone 4S de 16GB

I fix it: Replacing the broken dock connector.

 

Hace 2 meses atrás mi teléfono Iphone 4S dejó de cargar y de sincronizar con el computador usando el cable USB; Los Genios de la tienda Apple en Grand Central trataron de limpiar el conector pero fué en vano, aparentemente habia corrosión y no habia más nada que hacer.

Pregunté en una tienda especializada de productos Apple, Computer SuperCenter (son muy buenos, repararon mi Power PC este mismo año) y solamente mandarlo a revisar costaba $100.  Vendiéndolo en mal estado me pagaban sólamente $27 (según usell.com).

Así, ¿que que hacer? Para agravar la situación, mi difunto teléfono tenia adentro:

  • La única foto de Sebastian con su primera muda del diente de leche
  • Un respaldo de los caracteres del juego “Injustice, God Among Us“. Resulta que el juego se corrompió un mes después que mi teléfono  murió y perdí todos los caracteres.

Ya hace tiempo había leido de la gente de IFIXIT, y después de ver la guía de reemplazo (22 pasos, nivel de dificultad intermedio pero todo MUY BIEN EXPLICADO) me decidí a invertir en lo siguiente:

Las partes tardaron una semana en llegar por correo normal, armar y desarmar el teléfono me tomó una hora (mi hija de dos años estuvo a mi lado todo el tiempo preguntándome que estaba haciendo ;-))

Estoy muy contento con los resultados. La guía fue muy precisa en cuanto a los pasos y una inversión de sólo $25 para ahorrarme $100 lo justifica. Pero lo mejor es haberlo hecho yo sólo, pudiendo recuperar de paso una fotografía única que creia perdida :-).

¿Porqué corremos?

Ha pasado mucho tiempo desde que escribí la última actualización a esta bitácora. Hace mucho tiempo atrás decidí que mis prioridades son otras y por desgracia (o fortuna, depende a quien le pregunte) no he escrito con mucha frecuencia.

Pero hay un tema recurrente el cual me ha tocado la puerta de varias maneras y al cual creo nunca le he prestado la debida atención: ¿Porqué corremos?

 

Lyme disease research 5K in Cove Park, Stamford CTAlgunas veces lo hacemos para dar el ejemplo. Y muchas veces nosotros somos quienes aprendemos algo

Hace ya unas semanas atrás corrí un 5K, el “Lyme Disease Research 5K“. Es una carrera pequeña pero con un motivo enorme, el prevenir y educar la gente sobre esta terrible enfermedad, la cual es muy común en el estado de Connecticut y para la cual no hay pruebas confiables que la detecten.

Mi hijo Sebastian, corrió media milla junto con otros niños. El ya estaba advertido que su papá (yo) iba a correr 5 kilómetros pero que el sólo tenia que correr media milla (si usted va a una pista de carreras, eso es 2 vueltas o 800 metros aproximadamente). Nosotros ya habíamos practicado previamente corriendo una vez en la pista, mientras yo le explicaba como no debía salir muy rápido pero que poco a poco acelerara su velocidad hasta que terminara corriendo con todo.

Si, le dije hasta el termino correcto en Ingles, haz un “negative split“.

El no estaba muy convencido, de hecho lo hizo casi a regañadientes ya que no entendía el propósito de correr por correr media milla. Yo me las ingenie para venderle la idea de correr un rato para después ir a hacer algo que al el le gustara más.

Lyme disease research 5K in Cove Park, Stamford CT¿Que lo mueve a usted a correr? (5K Lyme disease research);

En fin, el día de la carrera llegó y yo a duras penas logré mejorar mi tiempo personal en 5 kilómetros; Una hora más tarde el estaba corriendo junto con otros niños, la mayoría de ellos más altos y maduros que Sebastian, manteniendo su ritmo justo en medio del grupo. A medida que se acercaban más y más a la meta pude ver como aceleraba su paso y cuando faltaba un cuarto de la distancia por recorrer el estaba corriendo a toda velocidad, seguido de cerca por un nuevo amigo que hizo mientras esperaba su turno para correr. Y luego en un momento el estaba al frente de todo el mundo, ganando distancia rápidamente…

Esta demás decir que me puse nervioso cuando vi que su cara reflejaba síntomas de agotamiento. Sin embargo vi algo más allí, una chispa de competitividad, de propósito, determinación.

En aquel momento me di cuenta que Sebastian habia cruzado una barrera que muchos adultos ni siquiera se atreven a rozar, fué de salir de su zona de comodidad; Como buen papa moderado empecé a gritarle que ya le faltaba poco y que no aflojara, ante la mirada atónita de algunos de los papas que estaban en el sito.

Gano y termino su carrera, así de rápido. Y mucho más rápido se fué  a celebrar con su hermana y su nuevo amigo.

Correr es un acto muy intimo y público a la vez; Algunos lo hacen para sentirse libres y para ver hasta donde pueden llegar, como nos dice Ed Ayres en su libro “The Longest Race“. En el Ed nos explica como correr es algo implícito del ser humano, y de como debemos buscar en nuestras raíces para mantener un contacto sano con nuestro entorno y en especial con la naturaleza. Otros en cambio lo hacen para explorarse más a si mismos como nos muestra Haruki Murakami en “What I Talk About When I talk Aboit running“. Este último libro me gusto mucho ya que como corredor me identifique con muchas cosas que el describe en sus carreras y experiencias.

De una u otra manera, correr es un acto natural y depende de cada quien buscarle un propósito. O quizas el propósito de correr es de por si suficiente y nosotros lo adornamos para justificar el tiempo que le dedicamos.

Ya para finalizar, si tienen algo de dinero y la inclinación entonces envíen sus donativos a lymeresearchalliance.org/. Es una buena causa y no hay nada mejor que gente sana.

Hasta el próximo escrito, prometo no tardarme tanto.

 

Ahora soy un maratonista (II), ¡aunque dolió un mundo!

Finisher medal and BIB. Finish time: 04:27:18
Me gane esta medalla con esfuerzo, esta carrera fué una prueba de improvisación ante los obstáculos.

 

Un día después de terminar el maratón más famoso y grande de la tierra (participaron y terminaron más de 50K personas). Me duelen las rodillas pero sin embargo me siento mejor de lo que esperaba.

Me da mucha pereza escribir paso a paso lo que ocurrió durante la carrera, esas son memorias que voy a llevar en mi cabeza hasta que me muera. Así que les voy a colocar un resumen de lo que  aconteció:

Today I was in route to finish my first (and most likely last) marathon at 03:30:00; Everything was going according to plan until the end of mile 21 came up and I had a cramp on my left quadricep, my right hamstring and my right arm.

My body decided to shutdown. Just like that.

While I was in the ground, I was told that I may have to leave the race; That was enough for me to say that I promised my wife and my 2 kids that I will finish the darn thing, even if I had to crawl.

And I got up. And walked away from the race organizer. And then ran a little bit until I felt another cramp. And then walked some more…

Doing a combination of running and walking I managed to reach the finish line (running, slow but running) and to keep my promise.

I’m really thankful for the huge support of Veronica Barrios, Alexa and Sebastian. Without them I could not have even started this challenge.

Also I have to thanks my friend Iliana Zuniga for her tips before the race, this is a journey that proved though but I feel that we both nailed. And also the love and support of ALL of you who called me or send me text messages before or after the race.

Love you all!

-José Vicente Núñez Zuleta

El desempeño en números

La mejor forma de describir mi desempeño es con una gráfica; Allí se puede ver que comencé a perder fuerza en la millas 18 pero fué en la 21 en donde los calambres (por falta de buena hidratación) hicieron lo suyo:

Track My Runners
Mi velocidad durante todo el recorrido. Y si, me comparo con Pamela Anderson, sólo por diversión

Y para quien no le gustan los gráficos:
Unofficial NYC finish time
Quede en el percentil %47 o dentro de los primero 32K+ corredores en cruzar la meta. Eso me valio aparecer en la sección impresa del New York Times 🙂

¿Como recuperarse de un calambre durante un maratón?

Untitled
Todo el mundo tiene una motivación. Este letrero me lo escribió mi hijo Sebastian con las 4 personas más importantes de mi vida

Una vez que estas metido en ese hueco es difícil salirse; Sin embargo yo hice lo siguiente:

  • No hay que desesperarse, no hay que rendirse. Yo mentalmente calculé que me faltaban 5 millas más (lo que se traduce en 1 hora más de carrera o más). Sin embargo ya llevaba 21 millas resueltas, además de que fallarle así a mis niños y esposa no era una opción así que ¡había que continuar! El maratón es una lucha mental después de la milla 20 y eso es algo que se práctica durante el entrenamiento. Repetir una frase una y otra vez.
  • Me levanté lentamente, me despedí del asistente de carreras y camine por una milla hasta la próxima estación de hidratación. Allí tomé bastante Gatorade (para reponer el potasio y el sodio, la causa de mi calambre) y un gel. También me estire un poco
  • A partir de allí pude volver a correr / caminar pero fué mucho más difícil porque el daño ya estaba hecho. Pero al menos estaba contenido

Mi error fué hidratarme cada 3 millas en vez de hacerlo cada milla después de la milla 3. Es algo que sólo se aprende con la práctica ya que cada persona es distinta.

Fotos, más fotos

Les dejo un enlace con todas las fotos, ellas capturan múltiples momentos antes, durante y después del maratón. Fué una experiencia única que quizas repita algún día.

¿Puedo correr tan rápido como un maratonista profesional?

Untitled
Yo era el número 45 en la lista de espera.

La respuesta es si … pero sólo por 2 minutos y 10 segundos (00:02:10) o media milla aproximadamente.

Eso lo aprendí corriendo hoy en la Avenida de Las Americas en Nueva York, justo después de buscar mi BIB y otras cosas en la exposición del maratón.

La maquina profesional que corre a la velocidad de Ryan Hall.

 

Sólo por correr, ASICS me regaló una gorra, un par de medias y una bola pequeña para el gimnasio. Nada mal por sólo dos minutos de esfuerzo 🙂

Si lo desean aquí estan las fotos de la exposición. Gente de más de 30 países, reunidas con un sólo propósito: correr en la gran Manzana.