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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/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("<id ")
 
    def values(self):
        for dateId in sorted(self.keys()):
            yield self[dateId]
 
    def items(self):
        for dateId in self.keys():
            yield (dateId, self[dateId])
 
    def __iter__(self):
        for dateId in sorted(super().keys()):
            yield dateId
 
    def save(self, file):
        fh = None
 
        try:
           fh = gzip.open(file, "wb")
           fh.write(self.__FILE_MAGIC)
           fh.write(self.__FILE_VERSION)
           for dollar in self.values():
               data = bytearray()
               data.extend(
                           self.__dollarStruct.pack(
                                dollar.date.toordinal(),
                                dollar.value
                           )
                )
               fh.write(data)
        except (Exception) as err:
            raise
        finally:
            if fh is not None:
                fh.close()
 
    def readBinary(self, file, verbose=False):
        fh = None
        try:
            fh = gzip.open(file, "rb")
            magic = fh.read(len(self.__FILE_MAGIC))
            if magic != self.__FILE_MAGIC:
                raise "File doesn't look like a KodeGeek.com binary file!"
            version = fh.read(len(self.__FILE_VERSION))
            if version > 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)
</id>

Un ejemplo de como correrlo:

1
2
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:

1
2
3
4
5
6
7
8
9
10
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’).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/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:

1
2
3
4
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 :-))
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/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.

 

Como seguir mi progreso durante el Maratón de Nueva York 2013

Ya falta muy poco para el evento. Al momento de escribir estas líneas me estoy recuperando de un resfriado y ya estoy finalizando mi últimas carreras de recuperación (“tapering“) antes de la gran carrera.

¿Como me pueden localizar?

 BIB #17578, Green wave #1, corral #17, hora de comienzo 9:40 AM.

El recorrido oficial de la carrera (con una explicación de que hay en cada milla) esta aquí. Les recomiendo que se bajen la aplicación oficial del Maratón de Nueva York, metan mi número de BIB allí y !ya están listos¡

¿Como ver el progreso de la carrera?

Para aquellos de ustedes que quieran ver mi progreso durante la carrera, les dejo a continuación algunos enlaces:

  • Para ver la carrera en vivo. Más opciones aquí.
  • Runners World tiene gente corriendo en la carrera, reportando en vivo.
  • El blog de Iliana Zuniga. Ella también esta corriendo el maratón y va a estar reportando en vivo.
  • Por twitter (aunque no creo que escriba nada durante la carrera)
  • Si eres Venezolano como yo, el Blog de Ernesto Linzalata seguro tendrá información relacionada con el evento.

 Información miscelanea

Otros detalles importantes de la carrera, por si aún estan curiosos.