2009-07-05

Patch en Regexp para poder usarlas como clave en un Hash

Siguiendo mi proyecto de hacer mi wiki en Ruby, encontré un comportamiento muy raro.
Generé un hash (que se llama @rules) que no tiene un elemento que tiene. O sea, @rules[@rules.keys[2]] da nil, pero @rules.values[2] devuelve el objeto asociado a la clave @rules.keys[2]. Como este hash tiene como claves un montón de expresiones regulares, me imaginé que había un problema con el hash y el eql? de Regexp, así que los implementé de nuevo y monkeypatchié.
Este es el código:

class Regexp
alias_method :old_rapidito_inspect, :inspect

def inspect
@inspect = old_rapidito_inspect if @inspect.nil?
@inspect
end

def eql?( other )
false if other.class != Regexp
self.inspect == other.inspect
end

alias_method :"==", :eql?

def hash
self.inspect.hash
end
end


Esta corrección me anduvo con la siguiente versión de ruby:
$ ruby --version
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]


Espero que les sirva.
Happy hacking,
Aureliano.

2009-07-03

Upcase con acentos

En ruby no se calculan los cambios minúscula mayúscula para las letras acentuadas. Por ejemplo:

irb(main):002:0> puts "áa".upcase
áA
=> nil

Para que ande un poco mejor (y tome las letras acentuadas de un montón de lenguajes), encontré un hack que en 10 líneas de código hace que puedan pasarse a mínusculas y mayúsculas los caracteres acentuados. Usando que todas las letras minúsculas y mayúsculas acentuadas están todas juntas, armé un par de gsubs que resuelven el problema.
class String
alias_method :old_rapidito_upcase, :upcase
def upcase
self.gsub( /\303[\240-\277]/ ) do
|match|
match[0].chr + (match[1] - 040).chr
end.old_rapidito_upcase
end

alias_method :old_rapidito_downcase, :downcase
def downcase
self.gsub( /\303[\200-\237]/ ) do
|match|
match[0].chr + (match[1] + 040).chr
end.old_rapidito_downcase
end
end

Lo que hago acá es reemplazar las funciones upcase y downcase para que manejen los acentos, sumando o restando 32 (40 en octal) al segundo byte. Pongo este código en lang_hacks.rb y sale todo con fritas. Mirando el irb
irb(main):001:0> require 'lang_hacks'
=> true
irb(main):002:0> "aÁ".upcase
=> "A\303\201"
irb(main):003:0> puts "aÁ".upcase

=> nil

Espero que les sirva.
Happy hacking,
Aureliano.

2009-07-01

Rap del dengue sojero

Entra enfermo de dengue
Mosquito pica enfermo
Sapo se come mosquito
¡No hay dengue!

Alfredo planta soja
Alfredo fumiga veneno
Veneno mata sapito
Sapo muerto no come mosquito
¡Hay soja!
¡Hay veneno!
¡No hay sapo!
¡Hay dengue!
¡Hay mosquito!

Hormiga atómica fumiga mosquito
Sapo vivo se caga de hambre
Año que viene
¡Se muere!
¡No hay sapo!
¡Hay dengue!
¡Hay malaria!
¡Hay peste!
¡Hay mosquito!

Nonsanto reparte los sobres
Al periodismo in-the-pendiente
¡Hay sobre!
¡No hay noticia!
¡Hay soja!
¡Hay veneno!
¡No hay sapo!
¡Hay dengue!
¡Hay mosquito!

Nonsanto no garpa bloguero
Bloguero publica noticia
¡Nonsanto se cae de culo!
¡Cristina prohibe la soja!
¡No hay sobre!
¡Hay sapo!
¡No hay dengue!
¡No hay mosquito!

Vía: artepolitica.com

Autofit guest solo anda si abrís la consola después de loguearte

Comprobado en Vmware Server 2.0.1.
Host: KUbuntu 9.0.4. 64 bits.
Guest: KUbuntu 9.0.4. 32 bits.

¿Estoy haciendo algo mal?
Aureliano.

2009-06-30

Más rapidito

Como les estuve contando, sigo escribiendo mi wiki. Ya parsea un subconjunto interesante del lenguaje definido por trac.
Siguiendo la tradición, les cuento como está avanzando el tokenizer. Al tokenizer lo simplifiqué para que devuelva la expresión regular que matcheo junto con el match (en vez del "tipo"). Esto hizo que la interfase para definir las reglas para tokenizar sea más simple. Si no hay ninguna regla que matchee sigue devolviendo ["string", :text].
Sin más, acá el código:

module Rapidito
class Tokenizer

def initialize( *delimiters )
@delimiter_list = delimiters + [/\z/]
@match_cache = nil
end

def source
valid_cache? ? @match_cache[0].to_s + @source : @source
end

def source=(s)
@match_cache = nil
@source = s
end

def has_next?
!@source.empty? || valid_cache?
end

def valid_cache?
(!@match_cache.nil?) && (@match_cache[0].to_s.length > 0)
end

def next_match
@delimiter_list.map {|regex| [regex.match(@source),regex]}.reject {|p| p[0].nil?}.inject do
|better,new|
better_pos = better[0].pre_match.length
new_pos = new[0].pre_match.length

if better_pos < new_pos
better
elsif new_pos < better_pos
new
elsif better[0].to_s.length > new[0].to_s.length
better
else
new
end
end
end

def next_token
if @match_cache #cached delimiter
rv = @match_cache
@match_cache = nil
return rv
end

match = next_match
p = match[0].pre_match.length
@source = @source[p + match[0].to_s.length, @source.length]

if p == 0 #delimiter
match
else #text
@match_cache = match
[match[0].pre_match, :text]
end
end

def all_tokens
tokens = []
while has_next?
tokens << next_token
end
tokens
end
end
end

Y si miran los tests de unidad, van a ver que también quedaron más lindos:
require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

def test_no_token
tok = Tokenizer.new
tok.source = "aaaa"
assert_equal true, tok.has_next?
assert_equal ["aaaa", :text], tok.next_token
assert_equal false, tok.has_next?
end

def assert_all_tokens( expected, tokenizer )
assert_equal expected,
tokenizer.all_tokens.map { |token, kind| [token.to_s, kind] }
end

def test_two_delimiters
tok = Tokenizer.new(
/\|/, /;;/
)

tok.source = "aa|bbb;;;;cccc"
assert_all_tokens \
[ ["aa", :text], ["|", /\|/], ["bbb", :text],
[";;", /;;/], [";;", /;;/], ["cccc", :text] ],
tok

tok.source = "aa;;bbb||cccc"
assert_all_tokens \
[ ["aa", :text], [";;", /;;/], ["bbb", :text],
["|", /\|/], ["|", /\|/], ["cccc", :text] ],
tok
end

def test_choose_longest_match
tok = Tokenizer.new(
/aa/, /aaa/
)
tok.source = "aaaa"
assert_all_tokens [ ["aaa", /aaa/], ["a", :text ] ], tok
end

def test_reset_precache
tok = Tokenizer.new(
/\|/, /,/
)
tok.source = "original start|original end"
tok.next_token
tok.source = "new start,new end"
assert_equal ["new start", :text], tok.next_token
end

def test_almost_finished
tok = Tokenizer.new( /!/ )
tok.source = "bang!"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal false, tok.has_next?
end

def test_carriage_return_ending
tok = Tokenizer.new( /!/ )
tok.source = "bang!\n"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal true, tok.has_next?
assert_equal "\n", tok.next_token[0].to_s
assert_equal false, tok.has_next?
end

def test_transparent_caching
tok = Tokenizer.new( /!/ )
tok.source = "bang!pum"
tok.next_token

assert_equal "!pum", tok.source
end

def test_match_klass
tok = Tokenizer.new( /!/ )
tok.source = "!bang!pum"

assert_equal \
[MatchData, String, MatchData, String],
tok.all_tokens.map { |tok, kind| tok.class }
end
end

Happy hacking,
Aureliano.

2009-06-19

Bouncer versión 2

Hice una nueva versión del bouncer que permite bouncear a varios targets desde un mismo host:

#!/usr/bin/env ruby

# == Synopsis
#
# bouncer.rb: Redirects TCP connections to distant machines. Handles simultaneously many connections.
#
# == Usage
#
# ruby redirect.rb [OPTION]
#
# -h, --help:
# Show help
#
# --ip ip, -i ip:
# Accept connections from ip (default 127.0.0.1)
#
# --port port, -p port:
# Listen on port (default 12345)
#
# --target ip:port, -t ip:port
# Connect to ip:port (default 127.0.0.1:23456)
#

require 'getoptlong'
require 'rdoc/usage'
require 'socket'

class Bouncer
def self.for_host( source_ip, start_port, targets )
rules = {}
targets.each_with_index do
|target, index|
rules[ start_port + index ] = target
end
self.new( source_ip, rules )
end
def initialize( source_ip, port_target_map )
@port_target_map = port_target_map
@source_ip = source_ip

@servers = @port_target_map.keys.map do
|port|
ss = TCPServer.new( "", port )
ss.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
ss
end

@descriptors = @servers.clone
@next_step = {}
end

def handle_new_connection( ss )
incoming = ss.accept
if incoming.peeraddr[3] == @source_ip
begin
outgoing = TCPSocket.new( *@port_target_map[incoming.addr[1]] )
@next_step[ incoming ] = outgoing
@next_step[ outgoing ] = incoming

@descriptors += [ incoming, outgoing ]
rescue
silent_close( incoming )
end
else
silent_close( incoming )
end
end

def silent_close( sock )
begin
sock.close
rescue
#do nothing intentionally
end
end

def propagate(sock)
next_sock = @next_step[sock]
next_sock.write(sock.read_nonblock(1000 * 1000))
end

def finish_connection(sock)
next_sock = @next_step[sock]
[ sock, next_sock ].each do
|s|
silent_close(s)
@descriptors.delete(s)
@next_step.delete(s)
end
end

def run
loop do
connections = select( @descriptors )
connections[0].each do
|sock|
if @servers.include? sock then
handle_new_connection( sock )
else
begin
sock.eof? ? finish_connection(sock) : propagate(sock)
rescue
finish_connection(sock)
end
end
end
end
end
end

if $0 == __FILE__ then

opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--ip', '-i', GetoptLong::REQUIRED_ARGUMENT ],
[ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
[ '--target', '-t', GetoptLong::REQUIRED_ARGUMENT ]
)

ip = '127.0.0.1'
port = '12345'
target = '127.0.0.1:23456'

opts.each do
|opt, arg|
case opt
when '--help'
RDoc::usage
exit
when '--ip'
ip = arg
when '--port'
port = arg
when '--target'
target = arg
end
end

port = port.to_i
target = target.split(":")

trap("SIGINT") do
exit
end

Bouncer.new(ip, port => [target[0], target[1].to_i]).run
end

2009-06-18

Mousewheel para arriba en vmware

Encontré como hacer para que ande bien la ruedita del mouse adentro de un Guest de VMWare con Kubuntu 9.04. Hay que instalar el paquete xserver-xorg-input-vmmouse.
Happy hacking,
Aureliano.

2009-06-17

Cambiando de resolución en X

El comando xrandr sirve, entre otras cosas para cambiar la resolución de X desde un shell. Primero listo las resoluciones disponibles:


$ xrandr
Screen 0: minimum 320 x 200, current 1280 x 1024, maximum 1280 x 1280
VGA connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm
1280x1024 60.0*+ 75.0 60.0*
1152x864 75.0
1024x768 75.0 60.0
800x600 75.0 60.3
640x480 75.0 59.9
720x400 70.1

y puedo elegir la que quiera con el parámetro -s. Por ejemplo:

$ xrandr -s 800x600

Happy hacking,
Aureliano

2009-06-13

Pequeñas delicias de las expresiones regulares

Como les conté acá y acá, estoy escribiendo un tokenizador para un wiki que estoy programando. Y hoy me encontré con una cosa muy extraña de las expresiones regulares.
En ruby la función match sirve para buscar el primer match de una regex dentro de un string. Por ejemplo (usando el irb):


irb(main):001:0> m = /a/.match "babab"
=> #<MatchData "a">
irb(main):002:0> m.pre_match
=> "b"
irb(main):003:0> m[0]
=> "a"

En particular, el pre_match es lo que está antes del match en el string. También según había entendido (mal) /\Z/ matchea con el final del string. Por ejemplo:

irb(main):004:0> m = /\Z/.match "hola"
=> #<MatchData "">
irb(main):005:0> m.pre_match
=> "hola"

Pero, /\Z/ tiene un comportamiento muy extraño, aunque documentado, cuando el último caracter antes del final es un \n. Lo que pasa es que el pre_match queda ¡sin el \n del final!. Lo muestro en el irb:

irb(main):006:0> m = /\Z/.match "\n"
=> #<MatchData "">
irb(main):007:0> m.pre_match
=> ""

Para que no se manduque el \n, hay que usar /\z/ (¡en minúscula!):

irb(main):008:0> m = /\z/.match "\n"
=> #<MatchData "">
irb(main):009:0> m.pre_match
=> "\n"

Por lo tanto tuve que tocar el tokenizer, ahora la función de initialize quedó así (miren el cambio de la "Z" a "z"):
    def initialize( delimiters )
@delimiter_list = [[/\z/, :finish]] +
delimiters.to_a.map { |k,arr| arr.map { |re| [re, k] } }.inject([]) { |ac,ps| ac + ps }
@match_cache = nil
end

Y el test que captura el problema que genera usar \Z en vez de \z quedó así:
  def test_carriage_return_ending
tok = Tokenizer.new( :a_kind => [/!/] )
tok.source = "bang!\n"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal true, tok.has_next?
assert_equal "\n", tok.next_token[0].to_s
assert_equal false, tok.has_next?
end

Happy hacking,
Aureliano.

2009-06-12

Tokenizer de rapidito, segunda versión

Como les conté hace un par de posts, estoy haciendo un wiki y blogueando sobre el tokenizer
Hoy les cuento de la segunda versión del tokenizer. A esta versión (que no es compatible con la anterior) le agregué la posibilidad de asociar un tipo (kind) a cada regexp que define un token. Por lo tanto, el método next_match ahora devuelve un par [match, kind]. Los matches de texto común (no delimitadores) tienen kind :text. Los matches de los delimitadores vienen con la MatchData entera, para poder usarla si interesan partes del match a la hora de procesar el token. También dejé de usar =~ para matchear regexps, ya que el
Me quedaron un par de cosas con dudas después de implementar esto.

  1. ¿Se puede hacer en ruby que busque un match de una regexp en un string a partir de una posición? Hacer una regexp del tipo /.{34,}(regex de verdad)/ no vale.
  2. ¿Puedo forzar a la unión de expresiones regulares a buscar el match más largo (en vez del primero que matchee)?
A pesar de las dudas, igual tengo una versión que creo que es significativamente mejor que la versión anterior.
Así que como la otra vez dejo el código:
module Rapidito
class Tokenizer

attr_reader :source

def initialize( delimiters )
@delimiter_list = [[/\Z/, :finish]] +
delimiters.to_a.map { |k,arr| arr.map { |re| [re, k] } }.inject([]) { |ac,ps| ac + ps }
@match_cache = nil
end

def source=(s)
@match_cache = nil
@source = s
end

def has_next?
!@source.empty? || valid_cache?
end

def valid_cache?
(!@match_cache.nil?) && (@match_cache[0].to_s.length > 0)
end

def next_match
@delimiter_list.map {|p| [p[0].match(@source), p[1]]}.reject {|p| p[0].nil?}.inject do
|better,new|
better_pos = better[0].pre_match.length
new_pos = new[0].pre_match.length

if better_pos < new_pos
better
elsif new_pos < better_pos
new
elsif better[0].to_s.length > new[0].to_s.length
better
else
new
end
end
end

def next_token
if @match_cache #cached delimiter
rv = @match_cache
@match_cache = nil
return rv
end

match = next_match
p = match[0].pre_match.length
@source = @source[p + match[0].to_s.length, @source.length]

if p == 0 #delimiter
[match[0].to_s, match[1]]
else #text
@match_cache = match
[match[0].pre_match, :text]
end
end

def all_tokens
tokens = []
while has_next?
tokens << next_token
end
tokens
end
end
end

Y los tests de unidad:
require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

def test_no_token
tok = Tokenizer.new( {} )
tok.source = "aaaa"
assert_equal true, tok.has_next?
assert_equal ["aaaa", :text], tok.next_token
assert_equal false, tok.has_next?
end

def assert_all_tokens( expected, tokenizer )
assert_equal expected,
tokenizer.all_tokens.map { |token, kind| [token.to_s, kind] }
end

def test_two_delimiters
tok = Tokenizer.new(
:a_kind => [/\|/, /;;/]
)

tok.source = "aa|bbb;;;;cccc"
assert_all_tokens \
[ ["aa", :text], ["|", :a_kind], ["bbb", :text],
[";;", :a_kind], [";;", :a_kind], ["cccc", :text] ],
tok

tok.source = "aa;;bbb||cccc"
assert_all_tokens \
[ ["aa", :text], [";;", :a_kind], ["bbb", :text],
["|", :a_kind], ["|", :a_kind], ["cccc", :text] ],
tok
end

def test_choose_longest_match
tok = Tokenizer.new(
:a_kind => [/aa/, /aaa/]
)
tok.source = "aaaa"
assert_equal [ ["aaa", :a_kind], ["a", :text ] ], tok.all_tokens
end

def test_reset_precache
tok = Tokenizer.new(
:a_kind => [/\|/, /,/]
)
tok.source = "original start|original end"
tok.next_token
tok.source = "new start,new end"
assert_equal ["new start", :text], tok.next_token
end

def test_almost_finished
tok = Tokenizer.new( :a_kind => [/!/] )
tok.source = "bang!"
tok.next_token
assert_equal true, tok.has_next?
tok.next_token
assert_equal false, tok.has_next?
end
end

Happy hacking,
Aureliano.

2009-06-11

Syntax highlighting de Ruby en mi blog

Tomando como base lo que dice en este post, hice un script en ruby que genera html con clases que se pueden poner coloritos con css:

#!/usr/bin/env ruby

require 'rubygems'
require 'syntax/convertors/html'

class Syntax::Convertors::HTML
def convert( text, klass="" )
html = "<pre class=\"#{klass}\">"
regions = []
@tokenizer.tokenize( text ) do |tok|
value = html_escape(tok)
case tok.instruction
when :region_close then
regions.pop
html << "</span>"
when :region_open then
regions.push tok.group
html << "<span class=\"#{tok.group}\">#{value}"
else
if tok.group == ( regions.last || :normal )
html << value
else
html << "<span class=\"#{tok.group}\">#{value}</span>"
end
end
end
html << "</span>" while regions.pop
html << "</pre>"
html
end
end

convertor = Syntax::Convertors::HTML.for_syntax "ruby"
puts convertor.convert( $stdin.read, "ruby" )



En el script monkeypatchié un toque para que agregue la clase "ruby" al tag pre que engloba todo el código y aparte estoy usando estos estilos:

<style>
pre.ruby {
background-color: #ffffcc;
color: #000000;
padding: 10px;
font-size: 1.1em;
overflow: auto;
margin: 4px 0px;
width: 95%;
border: thin dashed;
}
.ruby .normal {}
.ruby .comment { color: #005; font-style: italic; }
.ruby .keyword { color: #A00; font-weight: bold; }
.ruby .method { color: #077; }
.ruby .class { color: #074; }
.ruby .module { color: #050; }
.ruby .punct { color: #447; font-weight: bold; }
.ruby .symbol { color: #099; }
.ruby .string { color: #944; }
.ruby .char { color: #F07; }
.ruby .ident { color: #004; }
.ruby .constant { color: #07F; }
.ruby .regex { color: #B66; }
.ruby .number { color: #D55; }
.ruby .attribute { color: #377; }
.ruby .global { color: #3B7; }
.ruby .expr { color: #227; }
</style>


Una cosa más, si estás viendo este post en otro lugar que no sea aurelianito.blogspot.com no vas a ver el resaltado de sintaxis (ya que no va a tener los estilos).

Hasta la próxima,
Aureliano

2009-06-06

Tokenizer de rapidito

Después de mirar un rato el estado de las bibliotecas para hacer wikis en Ruby, y descubrir que ninguna me servía, Decidí que tenía que poner cartas en el asunto y hacer la mía. Mi idea es implementar el markup de Trac, haciéndolo extensible, y agregarle un par de cositas que por ahora son un secreto :-p.

Lo importante del asunto es que puse manos en el asunto. Al final, después de un intento fallido de hackear mi camino al andar, decidí que lo mejor es armar un tokenizer y un parser que use esos tokens para generar el árbol del que extraeré el HTML.

Así que me puse a programar. Como no encontré ningún tokenizer en ruby, programé uno. El tokenizer se contruye con un montón de expresiones regulares que definen cada delimitador. Después se le setea una fuente de caracteres (un string) y separa el string en los delimitadores de arriba (que se devuelven como símbolos) y cadenas que no matchean con ninguno de los delimitadores (que devuelve como strings).

Bueno, basta de cháchara, acá tá el código:


module Rapidito
class Tokenizer

def initialize( *delimiters )
@regexp = Regexp.union( *delimiters + [/$/] )
end

attr_accessor :source

def has_next?
! @source.empty?
end

def next_token
p = (@source =~ @regexp)
if p == 0 #delimiter
token = nil
@source.sub!( @regexp ) { |match| token=match.to_sym; "" }
token
else #text
token = @source[0,p]
@source = @source[p,@source.length]
token
end
end

def all_tokens
tokens = []
while has_next?
tokens << next_token
end
tokens
end

end
end



Y, acá abajo la única documentación que hice hasta ahora, o sea los tests de unidad:

require 'test/unit'
require 'rapidito/tokenizer'

include Rapidito

class TokenizerTest < Test::Unit::TestCase

def test_no_token
tok = Tokenizer.new
tok.source = "aaaa"
assert_equal true, tok.has_next?
assert_equal "aaaa", tok.next_token
assert_equal false, tok.has_next?
end

def test_two_delimiters
tok = Tokenizer.new( /\|/, /;;/ )
tok.source = "aa|bbb;;;;cccc"
assert_equal [ "aa", :"|", "bbb", :";;", :";;", "cccc" ], tok.all_tokens

tok.source = "aa;;bbb||cccc"
assert_equal [ "aa", :";;", "bbb", :"|", :"|", "cccc" ], tok.all_tokens
end

def test_choose_first_match
tok = Tokenizer.new( /aa/, /aaa/ )
tok.source = "aaa"
assert_equal [ :aa, "a" ], tok.all_tokens
end

end


Happy hacking,
Aureliano.

PD: ¿Prefieren que ponga el código con syntax highlighting?

2009-06-01

Quine en ruby

En el ruby quiz pusieron como problema de la semana hacer un quine en ruby. Un quine es un programa que genera como salida el código fuente del mismo (ver wikipedia).

Así que puse manos a la obra. Agarre el último quine en C de la página de wikipedia:


main() { char *s="main() { char *s=%c%s%c; printf(s,34,s,34); }"; printf(s,34,s,34); }


Y lo traduje a ruby:

s="s=%c%s%c;printf(s,34,s,34)";printf(s,34,s,34)


En ruby no hace falta tener main, porque todo lo que agarra el intérprete lo ejecuta y tampoco hay que declarar variables, así que quedó de 48 caracteres.

Y después hice uno que hace que tenga un ENTER al final, quedando más lindo cuando lo corrés de la consola:

s="s=%c%s%c;printf(s,34,s,34,13,10)%c%c";printf(s,34,s,34,13,10)


Fueron 10 minutos divertidos.

Happy hacking,
Aureliano.

2009-05-31

99 botellas de cerveza

Yo no soy un fan de Perl, pero a veces aparecen cosas increíbles. Por ejemplo, este es un programa válido en perl:


''=~( '(?{' .('`' |'%') .('[' ^'-')
.('`' |'!') .('`' |',') .'"'. '\\$'
.'==' .('[' ^'+') .('`' |'/') .('['
^'+') .'||' .(';' &'=') .(';' &'=')
.';-' .'-'. '\\$' .'=;' .('[' ^'(')
.('[' ^'.') .('`' |'"') .('!' ^'+')
.'_\\{' .'(\\$' .';=('. '\\$=|' ."\|".( '`'^'.'
).(('`')| '/').').' .'\\"'.+( '{'^'['). ('`'|'"') .('`'|'/'
).('['^'/') .('['^'/'). ('`'|',').( '`'|('%')). '\\".\\"'.( '['^('(')).
'\\"'.('['^ '#').'!!--' .'\\$=.\\"' .('{'^'['). ('`'|'/').( '`'|"\&").(
'{'^"\[").( '`'|"\"").( '`'|"\%").( '`'|"\%").( '['^(')')). '\\").\\"'.
('{'^'[').( '`'|"\/").( '`'|"\.").( '{'^"\[").( '['^"\/").( '`'|"\(").(
'`'|"\%").( '{'^"\[").( '['^"\,").( '`'|"\!").( '`'|"\,").( '`'|(',')).
'\\"\\}'.+( '['^"\+").( '['^"\)").( '`'|"\)").( '`'|"\.").( '['^('/')).
'+_,\\",'.( '{'^('[')). ('\\$;!').( '!'^"\+").( '{'^"\/").( '`'|"\!").(
'`'|"\+").( '`'|"\%").( '{'^"\[").( '`'|"\/").( '`'|"\.").( '`'|"\%").(
'{'^"\[").( '`'|"\$").( '`'|"\/").( '['^"\,").( '`'|('.')). ','.(('{')^
'[').("\["^ '+').("\`"| '!').("\["^ '(').("\["^ '(').("\{"^ '[').("\`"|
')').("\["^ '/').("\{"^ '[').("\`"| '!').("\["^ ')').("\`"| '/').("\["^
'.').("\`"| '.').("\`"| '$')."\,".( '!'^('+')). '\\",_,\\"' .'!'.("\!"^
'+').("\!"^ '+').'\\"'. ('['^',').( '`'|"\(").( '`'|"\)").( '`'|"\,").(
'`'|('%')). '++\\$="})' );$:=('.')^ '~';$~='@'| '(';$^=')'^ '[';$/='`';


Y genera la letra de la canción 99 bottles of beer.

Para ver la explicación sobre como hicieron esto, mirá acá.

Happy hacking,
Aureliano.

PD: ¿Se podrá hacer algo así en ruby?

2009-05-16

Referencia de administradores de consorcio

En el consorcio donde vivo estamos cambiando de administración. Para ello se recibieron propuestas escritas de un montón de administradoras. Después de ser analizadas, elegimos 4 para entrevistar. De esas 4, si sale todo bien, deberíamos elegir a la brevedad cuál de ellas será nuestra próxima administración.
Por lo tanto, quería pedirles que me cuenten si tienen referencias (tanto buenas como malas) de estas administraciones. Las mismas son:

Muchas gracias,
Aureliano.

2009-05-14

Pinky y Cerebro


Gracias Pedro Varangot por la idea.

Y para los que no conocen la versión original:

2009-05-06

YMCA

2009-05-05

Bouncer básico en ruby

Estuve programando un bouncer, usando como base el load balancer que hice el año pasado. Un bouncer es un programa que poner un server TCP en un puerto y cada vez que recibe una conexión abre una conexión a otro lado y las "ata", haciendo que el server al que se conecta le conteste al cliente que se conecta a él.
En mi caso lo programé para tener una salida controlada del entorno de máquinas virtuales que estoy armando para tener más ordenadas las cosas en el trabajo.
Por si les interesa, acá abajo pongo el código completo.


#!/usr/bin/env ruby

# == Synopsis
#
# redirect.rb: Redirects TCP connections to distant machines. Handles simultaneously many connections.
#
# == Usage
#
# ruby redirect.rb [OPTION]
#
# -h, --help:
# Show help
#
# --ip ip, -i ip:
# Accept connections from ip (default 127.0.0.1)
#
# --port port, -p port:
# Listen on port (default 12345)
#
# --target ip:port, -t ip:port
# Connect to ip:port (default 127.0.0.1:23456)
#

require 'getoptlong'
require 'rdoc/usage'
require 'socket'

class Redirect
def initialize( source_ip, listening_port, target_ip, target_port )
@source_ip = source_ip
@target_ip = target_ip
@target_port = target_port

@server_socket = TCPServer.new( "", listening_port )
@server_socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )

@descriptors = [ @server_socket ]
@next_step = {}
end

def handle_new_connection
incoming = @server_socket.accept
if incoming.peeraddr[3] == @source_ip
begin
outgoing = TCPSocket.new( @target_ip, @target_port )

@next_step[ incoming ] = outgoing
@next_step[ outgoing ] = incoming

@descriptors += [ incoming, outgoing ]
rescue
silent_close( incoming )
end
else
silent_close( incoming )
end
end

def silent_close( sock )
begin
sock.close
rescue
#do nothing intentionally
end
end

def propagate(sock)
next_sock = @next_step[sock]
next_sock.write(sock.read_nonblock(1000 * 1000))
end

def finish_connection(sock)
next_sock = @next_step[sock]
[ sock, next_sock ].each do
|s|
silent_close(s)
@descriptors.delete(s)
@next_step.delete(s)
end
end

def run
loop do
connections = select( @descriptors )
connections[0].each do
|sock|
if sock == @server_socket then
handle_new_connection
else
begin
sock.eof? ? finish_connection(sock) : propagate(sock)
rescue
finish_connection(sock)
end
end
end
end
end
end

if $0 == __FILE__ then

opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--ip', '-i', GetoptLong::REQUIRED_ARGUMENT ],
[ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
[ '--target', '-t', GetoptLong::REQUIRED_ARGUMENT ]
)

ip = '127.0.0.1'
port = '12345'
target = '127.0.0.1:23456'

opts.each do
|opt, arg|
case opt
when '--help'
RDoc::usage
exit
when '--ip'
ip = arg
when '--port'
port = arg
when '--target'
target = arg
end
end

port = port.to_i
target = target.split(":")

trap("SIGINT") do
exit
end

Redirect.new(ip, port, target[0], target[1].to_i).run
end

2009-04-21

Charla de erlang

El viernes pasado di una charla sobre erlang en el trabajo. En la misma, muestro como escribir programas secuenciales en erlang y después un poco de programación distribuida. Para presentar usé esta presentación (ojo, tenés que abrirla con OpenOffice). Abajo pongo lo que planifiqué dar (que es muy parecido a lo que di).

Programación secuencial

"Variables" inmutables

1> X = 1.
1
2> X = 2.
** exception error: no match of right hand side value 2
3> X = 1.
1

Atomos

4> aaaa.
aaaa
5> bbbb.
bbbb
6> Y = bbbb.
bbbb
7> Y.
bbbb
8> Y = aaaa.
** exception error: no match of right hand side value aaaa

Tuplas y listas (y pattern matching)

9> L = [1, 2, 3]
.
[1,2,3]
10> L2 = [-1, 0 | L]
.
[-1,0,1,2,3]
11> [H1, H2, H3 | _ ] = L2
.
[-1,0,1,2,3]
12> H1.
-1
13> H2.
0
14> H3.
1

15> Yo = {person, {fname, "Aureliano"}, {lname, "Calvo"}}.
{person,{fname,"Aureliano"},{lname,"Calvo"}}
16> Yo.
{person,{fname,"Aureliano"},{lname,"Calvo"}}
17> {person, {fname, Name},_} = Yo.
{person,{fname,"Aureliano"},{lname,"Calvo"}}
18> Name.
"Aureliano"

Expresiones case

34> X = 1. EL = []. L = [1,2,3].
1
35> []
36> [1,2,3]

38> case X of [] -> empty_list; [H|T] -> {head, H}; _ -> not_a_list end.
not_a_list
39> case EL of [] -> empty_list; [H|T] -> {head, H}; _ -> not_a_list end.
empty_list
40> case L of [] -> empty_list; [H|T] -> {head, H}; _ -> not_a_list end.
{head,1}

Funciones

En fruit.erl,

price( pear ) -> 4.0;
price( banana ) -> 3.0;
price( apple ) -> 6.0;
price( orange ) -> 2.0;

En consola,

13> fruit:price( pear ).
4.0
14> fruit:price( banana ).
3.0

Usando pattern matching un toque más en serio: En fruit.erl,

price( [] ) -> 0.0;
price( [{Kilos, Fruit}|Rest] ) -> Kilos * price( Fruit ) + price( Rest ).

En la consola,

15> fruit:price( [] ).
0.0
18> fruit:price( [{2.5, banana}, {1.5, apple}] ).
16.5

Pero para que use mejor el espacio (y no se acabe el stack), lo escribo tail recursive (acá explico que el stack sino se va a la mierda):

price2( L ) -> price2( L, 0.0 ).

price2( [], Acum ) -> Acum;
price2( [{Kilos, Fruit}|Rest], Acum ) -> price2( Rest, Kilos * price( Fruit ) + Acum ).

Programación distribuida

spawn, send y receive

spawn lanza un proceso nuevo de erlang, send (!) manda un mensaje a un proceso, y receive lo recibe.

Hago el fruit_shop.erl.

42> c( fruit_shop).
{ok,fruit_shop}
43> FS = fruit_shop:spawn_shop( [ {banana, 15.0}, {pear, 10.0}, {apple, 10.0}], [ {banana, 3.0}, {pear, 4.0}, {apple, 6.0} ] ).

Y lo uso:

(a@tango)3> fruit_shop:stock(FS).
[{banana,15.0},{pear,10.0},{apple,10.0}]
(a@tango)8> fruit_shop:buy(FS, [{2.0, apple}]).
{purchased,12.0}
(a@tango)10> fruit_shop:stock(FS).
[{apple,8.0},[{banana,15.0},{pear,10.0}]]

Por último, lanzar procesos en otros nodos es casi lo mismo: (spawn con un parámetro más, todo el resto igual).

Pongo 2 consolas de erlang:

>erl -sname a -setcookie oreo
>erl -sname b -setcookie oreo

En la primera lanzo la frutería sobre el segundo nodo:

(a@tango)7> FS = fruit_shop:spawn_shop( 'b@tango', [ {banana, 15.0}, {pear, 10.0
}, {apple, 10.0}], [ {banana, 3.0}, {pear, 4.0}, {apple, 6.0} ] ).

Hago lo mismo que antes:

(a@tango)3> fruit_shop:stock(FS).
[{banana,15.0},{pear,10.0},{apple,10.0}]
(a@tango)8> fruit_shop:buy(FS, [{2.0, apple}]).
{purchased,12.0}
(a@tango)10> fruit_shop:stock(FS).
[{apple,8.0},[{banana,15.0},{pear,10.0}]]

Mato la segunda consola y ahora las operaciones dan timeout:

(a@tango)11> fruit_shop:stock(FS).
timeout
(a@tango)12> fruit_shop:buy(FS, [{2.0, apple}]).
timeout

2009-04-18

Pintada



Vía volandovengo

2009-04-10

como sacar numeros del 0 al 9 con los digitos 4 4 4 4 (parte 3)

Sigo jugando con los 4s y ahora quería contarles una curiosidad. Si saco la potenciación de las operaciones posibles, se pueden armar los mismos números que antes:


0 == (4 / (4 * (4 * 4)))
1 == (4 / (4 / (4 / 4)))
2 == (4 / ((4 + 4) / 4))
3 == ((4 + (4 + 4)) / 4)
4 == (4 - (4 / (4 * 4)))
5 == ((4 + (4 * 4)) / 4)
6 == (4 + ((4 + 4) / 4))
7 == (4 + (4 - (4 / 4)))
8 == (4 - (4 - (4 + 4)))
9 == (4 + (4 + (4 / 4)))
10 ==
11 ==
12 == (4 * (4 - (4 / 4)))
13 ==
14 ==
15 == ((4 * 4) - (4 / 4))
16 == (4 * (4 / (4 / 4)))
17 == ((4 / 4) + (4 * 4))
18 ==
19 ==
20 == (4 * (4 + (4 / 4)))
21 ==
22 ==
23 ==
24 == (4 + (4 + (4 * 4)))
25 ==
26 ==
27 ==
28 == ((4 * (4 + 4)) - 4)
29 ==
30 ==
31 ==
32 == ((4 * 4) + (4 * 4))
33 ==
34 ==
35 ==
36 == (4 + (4 * (4 + 4)))
37 ==
38 ==
39 ==
40 ==
41 ==
42 ==
43 ==
44 ==
45 ==
46 ==
47 ==
48 == (4 * (4 + (4 + 4)))
49 ==
50 ==


¿Loco no? Para probarlo, saquen ** de la lista de posibles operadores y corran el script de la entrada anterior del blog.

Happy hacking,
Aureliano.

como sacar numeros del 0 al 9 con los digitos 4 4 4 4 (parte 2)

En la entrada anterior del blog puse el desafío de calcular los números del 1 al 50 haciendo operaciones aritméticas sobre 4 4s, y como generarlos para los números que van del 0 al 9. Pero me quedé con la duda de si es posible hacerlo para todos los números menores a 50, así que escribí un programita en ruby que calcula todas las posibles evaluaciones usando los operadores +, -, *, / y **. Lo único, hay que tener en cuenta que la división es entera, por lo que hay algunos resultados poco intuitivos.

Al final, el primer número imposible es el 10 (por eso no encontraba como armarlo :D). Acá les muestro la salida del programa:


0 == (4 / (4 ** (4 ** 4)))
1 == (4 ** (4 / (4 ** 4)))
2 == (4 / ((4 + 4) / 4))
3 == (4 - (4 ** (4 - 4)))
4 == (4 ** (4 ** (4 - 4)))
5 == (4 + (4 ** (4 - 4)))
6 == (4 + ((4 + 4) / 4))
7 == (4 + (4 - (4 / 4)))
8 == (4 - (4 - (4 + 4)))
9 == (4 + (4 + (4 / 4)))
10 ==
11 ==
12 == (4 * (4 - (4 / 4)))
13 ==
14 ==
15 == ((4 * 4) - (4 / 4))
16 == (4 * (4 ** (4 / 4)))
17 == ((4 / 4) + (4 * 4))
18 ==
19 ==
20 == (4 * (4 + (4 / 4)))
21 ==
22 ==
23 ==
24 == (4 + (4 + (4 * 4)))
25 ==
26 ==
27 ==
28 == ((4 * (4 + 4)) - 4)
29 ==
30 ==
31 ==
32 == ((4 ** 4) / (4 + 4))
33 ==
34 ==
35 ==
36 == (4 + (4 * (4 + 4)))
37 ==
38 ==
39 ==
40 ==
41 ==
42 ==
43 ==
44 ==
45 ==
46 ==
47 ==
48 == (4 * (4 + (4 + 4)))
49 ==
50 ==

Y para los geeks codeadores como yo, este fue el script rubyistico que usé para calcular las cosas:

def template_expressions( leafs = 4 )
return "4" if leafs == 1
previous_level = template_expressions( leafs - 1 )
previous_level.map do
|expr|
results = []
(0...expr.length).each do
|i|
if expr[i..i] == "4"
new_result = expr.clone
new_result[i] = "(4 o 4)"
results << new_result
end
end
results
end.flatten
end

OPERANDS = %W{+ - * / **}

def sample_evaluations( leafs = 4 )
evaluations = {}
exprs = template_expressions(leafs)
exprs.each do
|expr|
sample_evaluations0( leafs, expr, evaluations )
end
evaluations
end

def sample_evaluations0( leafs, expr, evaluations )
if leafs == 0
begin
evaluations[eval( expr )] = expr
rescue ZeroDivisionError
end
return
end

OPERANDS.each do
|op|
new_expr = expr.sub( /o/, op)
sample_evaluations0( leafs - 1, new_expr, evaluations )
end
end

evaluations = sample_evaluations
(0..50).each { |i| puts "#{i} == #{evaluations[i]}" }


Happy hacking,
Aureliano.

como sacar numeros del 0 al 9 con los digitos 4 4 4 4

Hoy estaba mirando la página del Google Analytics del blog y encontré una busqueda muy rara que entró por acá: "como sacar numeros del 1 al 50 con los digitos 4 4 4 4". Estuve pensando un poco que quería decir e inventé que tenía que hacer operaciones aritméticas comunes sobre los 4 4s y obtener los números del 1 al 50.
Me salieron los números del 1 al 9 (y le agregué el cero de yapa). Los anoté en notación "ruby" para que puedan hacer cut&paste en el irb.
0: 4+4-4-4
1: (4+4)/(4+4)
2: (4/4) + (4/4)
3: (4+4+4)/4
4: (4**(4-4))*4
5: (4**(4-4))+4
6: ((4+4) / 4) + 4
7: (4+4) - (4/4)
8: 4+4+4-4
9: (4+4) + (4/4)

Ahora me queda la duda de si se pueden, o no, armar todos los números del 1 al 50 de esta manera. Quizás debería escribir un programita que prueba todas las opciones.

Happy hacking,
Aureliano.

2009-03-14

Gitcasts

Hace un tiempo que estoy jugando con la idea de empezar a usar git como control de versiones. Pero como estoy acostumbrado a años de subversion, aunque entiendo que tiene ventajas, me cuesta mucho hacer el pase. Las cosas que me detienen son que tiene menos soporte en Windows, la integración con eclipse es peor y usamos subversion en el trabajo.
Las cosas que me tientan son que está bueno poder tener versiones sin conectarse a un server, que es una cosa nueva para aprender, que parece estar bien hecho y que hay un montón de gente usándolo y le sirve. La idea de usar un control de versiones distribuido me gusta mucho.
Así que me puse a usarlo en un proyectito que estoy haciendo para mi, y empecé a buscar como usarlo en internet. Y me encontré con unos screencasts que están re-buenos y quiero compartir con ustedes. Les recomiendo especialmente que vean el screencast de los slides de la rails-conf.

Me voy a dormir,
Aureliano.

2009-03-03

¡Soy papá!

Update: pueden ver las fotos de Nico en su blog.

El sábado 28 de febrero (o sea, anteayer) a las 12:39 hs nació Nicolás, mi primogénito. Estoy muy contento y emocionado y quería compartir con ustedes la aventura del nacimiento.

Contracciones


El viernes, cuando llegué del laburo, mi mujer me dijo que estaba con contracciones. Ya había tenido contracciones antes pero no muy seguido. Alrededor de las 9 y media empezamos a medir cuándo empezaban. Y se hacían cada vez más frecuentes. A partir de las 11 (o 12) se estabilizaron cada 10 minutos (aprox). Las contracciones se hacían cada vez más fuertes y a la una y media yo me adelanté y saqué el auto de la cochera, lo puse en la puerta del edificio y subí el bolso con las cosas que teníamos preparado para el parto. También me tomé un café re-cargado para estar despierto cuando hiciera falta llevarla al hospital (en mi cabeza, en una media hora).
Pero me apuré al re-pedo. Las contracciones se estabilizaron en los 10' de frecuencia. Y con contracciones cada 10 minutos, la partera nos dijo que esperemos y que la llamemos cuando lleguen a ser cada 5'. Las contracciones siguieron cada 10' un par de horas más y yo encontré que las risas podían inducir partos. Así que primero estuve leyendole a Agus un montón de chistes malos que encontré x la web; y como no se reía, vimos ZooLander. Mientras tanto seguimos anotando las contracciones. Como había tomado un montón de café, no me podía dormir (aunque Agus me lo pidió). Alrededor de las 5AM (¿o 6?), se fue el efecto del café. Las contracciones seguían cada 10 minutos, pero un poco más dolorosas.
Palmé y me dormí. Me despertó Agus a las 9. Me dijo que las contracciones al fin eran cada 5 minutos. Así que habló con la partera y quedamos encontrarnos en la Suizo.

La Suizo


Llegamos antes de las 10 a la Suizo. Fuimos al tercer piso. Le dimos el carnet de OSDE de mi mujer a la recepcionista y nos encontramos con la partera. En ese momento, ella nos dijo que no había habitaciones y que nos ¡teníamos que ir!. También la revisó y vio que el cuello del útero estaba casi completamente borrado, y que tenía un poquitito de dilatación.
Bajamos y pedí en el Valet Parking que me devuelvan el auto. Evidentemente Nico no nacería ahí.
Después de esperar un rato largo (las calles de la zona están medio enquilombadas porque hicieron Pueyrredón doble mano) trajeron el autito y partimos a la clínica Otamendi, donde la partera hizo una reserva.

Llegando a la Otamendi


Dejé a Agustina e Irma en la puerta de la clínica, y fui a estacionar. El estacionamiento de esta clínica salía ¡$55 x día! y había cola. Así que me fui a buscar otro. Encontré uno a 2 cuadras a $35 x día, que abre las 24hs y estacioné ahí. Fui a la clínica, a admisiones. Yo tenía el carnet de OSDE de Agus así que terminé el trámite y subí a la habitación que le habían asignado.

Preparativos para el parto


Llegué a la habitación 509, que es la habitación que asignaron para que estuviera Agus hasta que liberen una de las de internación. Al toque la partera me dijo que fuera con ella a cambiarme para presenciar el parto.
Acá empezó el primer inconveniente. Llegué a una especie de vestuario en el sexto piso del Otamendi donde se suponía que me tenían que dar la ropa que hace falta para entrar en un quirófano y darme un locker para guardar la mía. En el vestuario lo que encontré es un montón de ambos de cirujía tirados, lockers cerrados, y médicos rapiñando los 3 que quedaban. La partera vio lo mismo en su vestuario y volvimos.
A Agus ya la habían preparado y estaba lista para ser mamá. Después de un ratito (que me pareció una eternidad), me trajeron la ropa para entrar a la sala de partos, me cambié, dejé las cosas en la habitación 509 (la enfermera me dijo que dejaban todo con llave) y fui con la partera al parto.

El parto


Entré a la parte de cirujía, y ya estaba todo encaminado. La partera fue derecho a la sala de partos y a mi me dijeron que espere un rato. Mientras tanto me puse unas cosas para cubrir las zapatillas y otra para el pelo. Esperé un rato, y pedí si podía entrar. La enfermera que estaba en la puerta de la sala de parto me dijo que sí.
Entré y estaba el obstetra de Agus metiéndole una barreta en la cachucha. Por lo que había leído, hacen eso para romper la bolsa así que no me asusté. Rompió la bolsa, empezaron las contracciones fuertes, Agus imploró x la peridural y, después de chequear que el bebé esté bien, decidieron dársela. Ahí me echaron de la sala.
Esperé un rato más y volví a preguntar si podía entrar. De nuevo dijeron que sí. Cuando volví apoyaron la piernas de Agus levantadas a 90 grados. El parto, inminente. Aún con peridural a Agus le dolían muuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuucho las contracciones, así que aprovechando que ya tenía puesto un cateter en la espalda le dieron una segunda dosis. Después de eso hizo los pujos y salió Nicolás. A los 5 segundo de salir empezó a llorar. Cortaron el cordón umbilical y lo apoyaron un segundo sobre Agus.

Chequeos varios


Tal como me dijeron en el curso de preparto, ahí mismo dejé a mi mujer para que haga el alumbramiento (o sea, que expulse la placenta) y me fui siguiendo a mi flamante hijo. En una sala aledaña lo pesaron, midieron, le pusieron las pulseritas identificatorias y lo limpiaron un poco. Cuando terminaron, me lo dieron para que lo alce. Y se lo llevé a Agus, que había terminado de expulsar la placenta. Pusieron el bebé encima de Agus un toque más, terminamos algo de papeleo (incluyendo que me dieron el certificado de nacimiento) y vinieron de Nursery a llevarse al bebé. Yo me fui con el bebé.

El dilema


Todavía seguía en la 509. La hab. 509 es muy pequeña y era incómod estar ahí. Así que cuando la "nurse" me dijo si me llevaba al bebé directamente o dejaba que lo lleven un ratito a la nursery para acicalarlo, dudé un toque y después decidí que lo mejor era que se lo llevaran. Yo me fui a cambiar a la habitación (todavía tenía la ropa del quirófano).

La tarde


Llegué a la habitación, me cambié y al toque apareció Agustina, que me cagó a pedos porque dejé que se lleven a Nicolás a la nursery. Le expliqué que dejé que fuera porque me pareció lo mejor, porque la habitación es chiquita, etc y, por suerte, al toque lo trajeron así que la discusión se desvaneció. Esperamos un ratito y me puse a preguntar cuando nos pasaban a una habitación decente, sin conseguir demasiadas respuestas.
A las 2 de la tarde (más o menos), bajé a Admisiones para que me informen cuándo iba a tener el pase de habitación. No solo no había nadie, tampoco había nadie en la recepción y había gente haciendo cola hacía 45', para que los atiendan. Subí al quinto piso a acompañarla a Agus (Nico estaba frito) y al ratito volví a bajar.
Cuando bajé de nuevo, atendían en recepción así que me quejé de que antes no había nadie y que tuve problemas con la ropa para entrar a la sala de parto. Dejé una nota por escrito en la hoja 188 del libro de quejas. Si pasan por el sanatorio pueden pedirlo para leerla. También hablé con la supervisora de hotelería un rato y me quejé de que la habitación era muy chica, de que no tiene baño, de que no puedo recibir gente (y es un nacimiento), de que no había gente en Admisiones ni en recepción, que no andaba el interno de la hab. 509, etc. O sea, le quemé la cabeza. La mina me prometió que iba a hacer todo lo que pudiera y que iba a tener la habitación ese mismo día, así que volví al quinto piso. Después de un rato, llamó la mina diciendome que ya tenían habitación para nosotros. La 321. La 321 es una habitación de más categoría de lo que cubre mi plan de OSDE, tiene recepción, es grande y me la bonifican. Parece que quejarme sirvió.
Llegaron mi suegra y mi cuñada a la hab. chiquita, porque Agus quería que vinieran. Entró mi suegra a la hab y me quedé afuera hablando con mi cuñada. Después nos avisó la enfermera que nos asignaron la habitación y al ratito vienen a buscar a Nicolás para llevarlo. Me voy con los bolsos y él a la nueva habitación.
La nueva habitación (tal como me dijeron) estaba re-buena. Grande, luminosa, cómoda. Dejo durmiendo a Nico y me pongo a ver tele. Llegan Agus y su parentela están un rato más y se van. La verdad que estabamos muertos. A las 8 le traen la cena a Agus, yo compro un sanguche (que estaba bárbaro) en el restaurant del hotel y me lo traen a la habitación. Cenamos, nos bañamos, y a las 9 palmé.

Segundo día


Día de visitas. A la mañana vienen mis hermanos, mi tía y mis viejos. Se quedaron un rato (después aprenderíamos que demasiado). Al mediodía viene la puericultora y descubrimos que Nico no está tomando la teta bien. Después de debuggear el problema un rato, llega a la conclusión de que Nico no toma porque los pezones de Agus son muy chiquitos y no sabe succionar todavía. Para workarroundear el problema, compramos unas pezoneras, y Nico se prendió. Y la puericultora nos dijo que tengamos a ralla a las visitas para captar las señales "de teta" de Nico.
A la tarde vinieron mi abuela con mi vieja y su primo Gregory, que me cuidaba de chico, y que es como un tío para mi, y que ahora vive en Israel. También vinieron mi cuñado y su hija y una tía de Agustina. A todos les dije que estén poco tiempo porque nos dijo la puericultora.
A la noche la llamamos a mi suegra para coordinar la salida de la clínica. Ella nos ayudó a irnos de la clínica.

Chau clínica


Nos levantamos a la mañana. Llevaron a Nico a hacer unos exámenes (donde le pincharon el talón, pobrecito), fui a hacer el trámite administrativo del alta (o sea, pagar), inicié el trámite del DNI de Nico y nos quedamos esperando a la neonatóloga. Alrededor del 1/2 día llegó mi suegra. A las 12:30hs (o sea, media hora después de la hora del alta) fui a averiguar xq no venía la neonatóloga. Después de como 1 hora de quilombo, exigí que le dieran el almuerzo a Agus; compré 2 sanguches para mi suegra y yo y llegó la neonatóloga, que adujo que no sabía que tenía que darnos el alta. Revisó a Nicolás, lo llevó a la nursery para hacerle el control oftalmológico, terminamos de comer, e hicimos el alta.

La vuelta


Bajamos y fuimos a la puerta de la clínica. Yo agarré el bolso y me fui a buscar el auto. Arranqué el auto y lo llevé hasta la puerta de la clínica. Mientras venía, atrás a la derecha el auto hacía un ruido raro. Cuando andaba más rápido el ruido era más rápido.
Llegué a la clínica, subí con dificultad el asiento de Nico al auto y lo encastré en la base y me di cuenta que la rueda trasera derecha estaba completamente desinflada. Saqué la sillita y Agus y la mamá se fueron en un taxi. Yo abrí el baúl, saqué el tapizado, saqué la rueda de auxilio, saqué el crique y la llave. Aflojé la rueda desinflada, subí el auto con el crique, cambié la rueda y fui solo para casa.
Cuando llegué a casa a las 4 de la tarde, me la encontré a mi suegra cuidando a mi nene y a mi mujer durmiendo, palmada. Y yo al borde de un ataque de nervios. Mi suegra se fue porque tenía que ir a trabajar y me quedé cuidando a Nico que, por suerte, estaba dormido.

2009-02-27

Rakefile multiplataforma para erlang

Los últimos días estuve programando un toque en Erlang. Como estoy tratando de armar un proyectito algo más en serio, necesito algo como un makefile. Y como los makefiles son un dolor de huevos, decidí que es mejor hacer un rakefile.
Para eso estuve googleando un poco y encontré este post. Que casi resolvía los quilombos, pero le falta poder andar en Windows. Así que lo toquetié un poco para hacerlo multiplataforma.
Las cosas que tuve que hacer son:

  • Usar ruby en vez de llamar a pwd para obtener el path actual.
  • Generar nombres de archivos con back-slashes.
  • Hacer que maneje bien cosas con espacios.
Para obtener el PATH actual hice esto:
PWD = Pathname(".").realpath

Para que el Pathname fuera con backslashes, después de consultarlo con Luis Lavena, monkey-patchié Pathname para que el to_s mande "back-slashes":
#Hack to generate paths with back-slashes
class Pathname
alias_method :old_to_s, :to_s
def to_s
File::ALT_SEPARATOR ? old_to_s.gsub(File::SEPARATOR,File::ALT_SEPARATOR) : old_to_s
end
end

Por último, cambié un toque las llamadas a "sh" para que maneje paths con espacios. Para eso, en vez de pasarle un String le paso un Array por parámetro. Las llamadas quedaron algo así:
sh *%W{erl -switch param1 #{param_calculado}}

Al final, el Rakefile quedó así:

#Based on the Rakefile shown in http://medevyoujane.com/blog/2008/8/21/erlang-make-rake-and-emake.html

require 'rake'
require 'rake/clean'
require 'pathname'

#Hack to generate paths with back-slashes
class Pathname

alias_method :old_to_s, :to_s

def to_s
File::ALT_SEPARATOR ? old_to_s.gsub(File::SEPARATOR,File::ALT_SEPARATOR) : old_to_s
end
end


# Configuration
START_MODULE = "hellman"
TEST_MODULE = "TEST MODULE NOT DEFINED"

# No Need to change
PWD = Pathname(".").realpath
INCLUDE = "include"
ERLC_FLAGS = "-I#{INCLUDE} +warn_unused_vars +warn_unused_import"

SRC = FileList['src/**/*.erl']
OBJ = SRC.pathmap("%{src,ebin}X.beam")
CLEAN.include(['**/*.dump'])
CLOBBER.include(['**/*.beam'])

directory 'ebin'

rule ".beam" => ["%{ebin,src}X.erl"] do |t|
sh *%W{erlc -pa ebin -W #{ERLC_FLAGS} -o ebin #{t.source}}
end

desc "Compile all"
task :compile => ['ebin'] + OBJ

desc "Open up a shell"
task :shell => [:compile] do
sh *%W{erl -sname #{START_MODULE} -pa #{PWD + 'ebin'}}
end


desc "Open up a shell and run #{START_MODULE}:start()"
task :run => [:compile] do
sh *%W{erl -sname #{START_MODULE} -pa #{PWD + 'ebin'} -run #{START_MODULE} start}
end

desc "Run Unit Tests"
task :test do
sh *%W{erl -noshell -s #{TEST_MODULE} test -s init stop}
end

task :default => :compile


Happy jakin,
Aureliano.

2008-12-25

Traducciones hollywoodenses

Después de ir todos los mediodías a diversos restaurantes de Palermo Hollywood pude recabar información sobre el "argot" que usan para escribir las cartas. Estas son algunas de las cosas que pude entender hasta ahora:

  • colchón de verdes: ¡uy!, no compramos tomates.
  • a las finas hierbas: en una de esas le ponemos perejil.
  • papas rústicas: íbamos a hacer papas fritas pero nos dio mucha vagancia pelar las papas.
  • pezca del día: es lo más barato que había en la pezcadería.
  • menú ejecutivo: al mediodía son todos unos ratas, así que hay que darles algo barato.
  • brusquetas: nos sobró pan duro de ayer.
  • cocina gourmet: vamos a darte re-poca comida, te vas a quedar con mucha hambre y te vamos a cobrar un fangote de guita. Eso sí, cuando te llevemos el plato vas a decir "¡qué lindo! parece un cuadro"
  • spring-roll: arrolladito primavera, pero todavía más chiquito
  • explosión de vegetales: Lo que me sobró de ayer le agregué un huevo y lo cociné, AKA: "revuelto de gramajo".
Aureliano.

2008-12-09

C++ frequently questioned answers

La obra de Yossi Kreinin en su C++ Frequently questioned answers es soberbia. Cientos y cientos de Kbytes dedicados exclusivamente a explicar porqué no usar C++ para empezar proyectos nuevos de desarrollo de software (cosa que comparto) y explicar las falencias de C++ detalladamente (y, la verdad, las falencias están bien justificadas).

Con un estilo ácido, ágil y de fácil lectura, demuele carta a carta ese castillo de naipes que es C++. No puedo hacer más que recomendar la lectura de esta excelente página.

Happy hacking (pero no en C++),
Aureliano.

2008-12-03

Números de Aure

Update: Los números de Aure también se llaman A131689

La oficina de Agustina tiene en la puerta un teclado donde hay que ingresar una clave para poder entrar. Pensando en esto (y otras cosas donde hay que ingresar claves numéricas, como los cajeros automáticos) se me ocurrió el siguiente problema:

Hay que elegir un número de 6 dígitos (en base 10, con posibles 0s
adelante) como clave para ingresar en un pinpad para ingresar a una "habitación segura".
Pero un atacante tiene la posibilidad de leer huellas digitales y por lo tanto sabe que teclas apretaste pero no en que orden ni con que repetición (o sea, no puede distinguir las claves "111112" y "122222").

¿Cómo se debe elegir la clave de tal manera que el atacante tenga la mayor dificultad posible para adivinarla?


Este problema lo postié en los news internos del laburo y Charles, un compañero de laburo, se mandó una explicación buenísima en dos mensajes. Abajo pongo textuales los mails:

Primer mail:
Con 6 digitos hay 6! = 720 posibilidades.

Con 5 digitos, una forma de contar es así: supongamos que los digitos son 1,2,3,4,5 y que el 5 está repetido dos veces, lo contamos como 1,2,3,4,5a,5b y despues dividimos por las permutaciones de 5a y 5b. Eso da 6! / 2!, y como hay 5 posibilidades para el digito repetido el
resultado es:
6! / 2! * 5 = 1800 posibilidades.

Con 4 digitos, puede haber un digito repetido tres veces, por ejemplo 1,2,3,4a,4b,4c
Posibilidades 6! / 3! * 4 = 480
Puede haber dos digitos repetidos dos veces cada uno, por ejemplo 1,2,3a,3b,4a,4b
Posibilidades 6! / (2! * 2!) * Combinatorio(4,2) = 1080 El Combinatorio(4,2) es la cantidad de formas de elegir 2 elementos entre 4, es 4! / (2!*2!) = 6
En total: 1560 posibilidades con 4 digitos

Con 3 digitos, las estructuras son 1,2,3a,3b,3c,3d da 6! / 4! * 3 = 90
1,2a,2b,3a,3b,3c da 6! / (3! * 2!) * (3*2) = 360 1a,1b,2a,2b,3a,3b da 6! / (2! * 2! * 2!) = 90
Total: 540 posibilidades con 3 digitos

Con 2 digitos, las estructuras son 1,2a,2b,2c,2d,2e da 6! / 5! * 2 = 12
1a,1b,2a,2b,2c,2d da 6! / (4! * 2!) * 2 = 30 1a,1b,1c,2a,2b,2c da 6! / (3! * 3!) = 20
Total: 62 posibilidades con 2 digitos

Con 1 digito, hay 6! / 6! = 1 posibilidad (claro ;-)

Conclusión: hay que usar 5 digitos!


Segundo mail:
Me quedé pensando en tratar de sacar una relación de recurrencia...
Llamemos "número de Aure" A(k,n) a la cantidad de claves de n digitos que usen exactamente k digitos distintos. Por ejemplo 1,2,2,3,3 es una clave de 5 digitos que usa 3 digitos distintos.

Yo lo pienso como cantidad de claves con k digitos distintos en n lugares.

Los casos faciles:
A(1,n) = 1 para todo n
A(k,n) = 0 si k > n

Ahora la recurrencia: si tengo una clave con n-1 lugares, hay dos opciones: ya se usaban los k digitos en los n-1 lugares, o solo se usaban k-1 digitos distintos y agregué el k-ésimo digito en el ultimo lugar.

Si ya se usaban k digitos en n-1 lugares, tengo k posibilidades para el n-esimo lugar (repetir uno de los k digitos).

Si se usaban exactamente k-1 digitos en n-1 lugares, entonces estoy agregando un digito nuevo en el n-ésimo lugar. Tengo k opciones para el digito nuevo.

Luego A(k,n) = k * A(k,n-1) + k * A(k-1,n-1)

A(k,n) = k * ( A(k,n-1) + A(k-1,n-1) )

Es como un triangulo de Pascal con un multiplicador k.

Las primeras filas dan:

k=1 k=2 k=3 k=4 k=5 k=6
n=1 1 0
n=2 1 2 0
n=3 1 6 6 0
n=4 1 14 36 24 0
n=5 1 30 150 240 120 0
n=6 1 62 540 1560 1800 720 0

Sorprendentemente, volvió a dar los mismos números ;-)

La problema de Aure se puede reformular como "qué k maximiza A(k,6)?"


En fin, el problema parece que dio para mucho :D, y tengo mis propios números con una serie que tiene un sentido y una "aplicación".

Happy hacking,
Aureliano.

2008-11-09

Mirando android

Hoy estuve mirando un toque como hacer una aplicación en Android. Después de hacer el HelloWorld correspondiente, seguir parte del tutorial sobre como hacer una aplicación que sirva para guardar notas y leer bastante de la documentación, hice correr el Lunar Lander. El Lunar Lander es un port del archi-conocido juego al android.
Pensé que iba a salir de toque, pero no :(. Así que puse manos a la obra, toque las 2 tonteras que tenía mal y lo hice andar. Para que no tengan que hacer lo mismo que yo, les dejo mi proyecto de eclipse armado. Es igual al de los ejemplos de Google salvo porque como package uso aure en vez de com.android.examples.
Acá está el tar.gz.
Happy hacking,
Aureliano.

2008-11-04

Proyecto Euler: Problemas 19 a 21

Sigo avanzando con los ejercicios del proyecto Euler. Les cuento los siguientes 3 ejercicios.
EL ejercicio 19 consiste en contar la cantidad de domingos del siglo 20. Primero pensé en hacer un montón de aritmética modular para resolver esto, pero después se me ocurrió una idea mejor. Así que usé la biblioteca de fechas de ruby, iteré por todos los días de enero del siglo 20 y sumé todos los domingos. Acá abajo pongo el código:


require 'date'

sundays = 0
(1901..2000).each do
|year|
Date.civil(year,1,1).upto(Date.civil(year,1,31)) do
|d|
sundays += 1 if d.wday == 0 # is sunday
end
end

puts "# of sundays in the 20th century: #{sundays}"

El resultado de la corrida (casi instantáneo) es:

$ ruby euler19.rb
# of sundays in the 20th century: 443

El ejercicio 20 consiste en encontrar la suma de los dígitos de 100!. Usando las funciones para obtener cada dígito de un número del ejercicio 16, se hace bastante fácil. Este es el código:

class Integer
def digit(pos)
(self / (10 ** pos)) % 10
end
def digit_count
dc = 0
while self - (10 ** dc) > 0
dc += 1
end
dc += 1 if self % (10 ** dc) == 0
dc
end
end

fact100 = (1..100).inject { |prod,n| prod * n }
puts "Digits: #{fact100}"
puts "Sum: #{(0...(fact100.digit_count)).inject {|s,n| s + fact100.digit(n)}}"

Y el resultado de la ejecución (también instantánea) es:

$ ruby euler20.rb
Digits: 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
Sum: 648

El ejercicio 21 es el más complicado de los 3. Consiste en sumar todos los números naturales "amigables" menores a 10000. Un par de números (a,b) es amigable si la suma de los divisores de a (excluyendo a) es b y la suma de los divisores de b (excluyendo b) es a. Un número es amigable si pertenece a un par amigable. Lo que se me ocurrió fue usar el MemoFactorizator (factorizador de números que recuerda factorizaciones previas) del ejercicio 12 para calcular las factorizaciones de todos los números menores a 10000. Usando esto, calculé analíticamente las sumas de los divisores propios (hay una fórmula re-linda que sale de la factorización, pero como no puedo escribir LaTeX facilmente acá, mejor mirenla en el código). Y habiendo calculado todas las sumas de los divisores, buscar en orden n todos los números amigables. Acá está el código:

class OddPrimes
def initialize
@current = 1
@primes = []
end
attr_reader :current
def next
while not prime?(@current += 2)
end
@primes << @current
@current
end

def prime?(n)
i = 0
p = 1
while true
break if i >= @primes.length
p = @primes[i]
break if p * p > n
break if n % p == 0
i += 1
end
return i >= @primes.length || p * p > n
end
end

class Primes < OddPrimes
def initialize
super
@first = true
end
def next
if @first
@first = false
2
else
super
end
end
def current
odd_super = super
odd_super == 1 ? 2 : odd_super
end
end

class MemoFactorizator
attr_reader :factored
def initialize
@factored = {1 => {} }
end
def factorize(n, pg = Primes.new)
return @factored[n] if @factored.include?( n )
p = pg.current
while n % p != 0
p = p.next
end
factorization = factorize( n / p, pg ).dup
factorization[p] = factorization.include?(p) ? factorization[p] + 1 : 1
@factored[n] = factorization
factorization
end
end

def divisor_sum(n, factorization)
acum = 1
factorization.each_pair do
|p,pow|
acum *= (0..pow).inject(0) { |s,i| s + p ** i }
end
acum - n
end

MAX = 10000

mf = MemoFactorizator.new
(2...MAX).each do
|n|
mf.factorize(n)
end

sums = {}
(2...MAX).each do
|n|
sums[n] = divisor_sum(n, mf.factored[n])
end

amicable = []

(2...MAX).each do
|n|
amicable << n if n == sums[sums[n]] and n != sums[n]
end

puts "Amicable numbers: #{amicable.inspect}"
puts "Sum: #{amicable.inject {|s,n| s+n}}"

La ejecución tardó un poquito (unos 4 segundos) y dio este resultado:

$ time ruby euler21.rb
Amicable numbers: [220, 284, 1184, 1210, 2620, 2924, 5020, 5564, 6232, 6368]
Sum: 31626

real 0m4.681s
user 0m4.560s
sys 0m0.108s

Happy hacking,
Aureliano

2008-11-01

Proyecto Euler: Problemas 16 a 18

Update: Reporto la suma en el ejercicio 18.

Sigo con mi maratón euleriana. Ahora resolví los problemas 16 a 18.
El problema 16 consiste en encontrar la suma de los dígitos de 2^1000. Para eso desenpolvé la lógica para obtener la cantidad de dígitos de un número y qué dígito es que había hecho para el problema 4 (pequeño quiz: ¿qué bug le arreglé?) calculé 2^1000 y sumé los dígitos. Este es el código:


class Integer
def digit(pos)
(self / (10 ** pos)) % 10
end
def digit_count
dc = 0
while self - (10 ** dc) > 0
dc += 1
end
dc += 1 if self % (10 ** dc) == 0
dc
end
end

n = 2 ** 1000
digits_sum = (0..(n.digit_count)).inject(0) { |sum, pos| sum += n.digit(pos) }
puts digits_sum

Y este el resultado (que sale instantáneamente):

$ ruby euler16.rb
1366

El problema 17 consiste en calcular cuantas letras (con repeticiones) hay en los números del 1 al mil en inglés. Por ejemplo "one two three" tiene 11 letras. Para eso hice una rutina que genera las palabras asociadas a cada número entre 1 y 1000, eso generó un string. Al string le saqué los espacios y conté la cantidad de caracteres. Mientras lo hacía me acordé de cuando estuve probando unos de los ejercicios que usan en ITA para contratar gente, pero lo escribí todo de 0. Acá va el código:

class Integer
UNITS = ["","one","two","three","four","five","six","seven","eight","nine"]
TEENS = ["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"]
TENS = ["","","twenty","thirty","fourty","fifty","sixty","seventy","eighty","ninety"]
def to_words
raise Exception.new("Don't know how to say in words the number #{self}") unless (1..1000).include? self
return "one thowsand" if self == 1000
words = []
if self >= 100 then
words << UNITS[self / 100]
words << "hundred"
end
words << "and" if (self % 100) != 0
case (self % 100) / 10
when 1
words << TEENS[self%10]
else
words << TENS[(self % 100) / 10]
words << UNITS[self % 10]
end
return words.delete_if{|w| w == ""}.join(" ")
end
end

char_count = (1..1000).inject(0) { |sum,n| sum + n.to_words.gsub(" ","").length }
puts char_count

Y la corrida (también instantánea):

$ ruby euler17.rb
21521

Por último hice el ejercicio 18, que es el más interesante de los 3. El mismo consiste en navegar por un triángulo (que pongo más abajo), encontrando el camino desde arriba de todo a algún numerito de abajo de tal manera que la suma de los nodos del camino sea lo mayor posible.

75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23


Para resolver este problema, calculé las sumas de los mejores caminos posibles para todos los subtriangulos y después, usando eso, elegí en forma greedy el mejor camino. Acá va el código:

data_text = <<DATA_TEXT
75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23
DATA_TEXT

data = data_text.split("\n").map{ |row_text| row_text.split(" ").map{ |cell_text| cell_text.to_i(10) } }
sums = Array.new(data.length)
sums[-1] = data[-1].dup
(2..(data.length)).each do
|row|
sum = []
prev_sum = sums[-row+1]
data[-row].each_with_index do
|v,i|
sum[i] = v + [prev_sum[i],prev_sum[i+1]].max
end
sums[-row] = sum
end

pos = 0
path = []

(1..(data.length - 1)).each do
|row|
if sums[row][pos] > sums[row][pos+1] then
path << :left
else
path << :right
pos += 1
end
end

puts path.inspect
puts "Sum: #{sums[0][0]}"

Y este es el resultado de la ejecución (también instantáneo, ya que se hacen tantas sumas como subtriángulos no triviales (que son menos que la cantidad de números en el triángulo) y tantas elecciones como la altura del triángulo. Acá les muestro el resultado:

$ ruby euler18.rb
[:right, :right, :left, :left, :right, :left, :left, :right, :right, :right, :right, :right, :left, :right]
Sum: 1074

Happy hacking,
Aureliano.

2008-10-29

Proyecto Euler: Problemas 13 a 15

Los problemas 13 a 15 resultaron más faciles que el 12. El 13 consiste en saber los primeros 10 dígitos de una suma de 100 números re-grandes. Como ruby soporta números arbitrariamente grandes, una vez que obtuve los números lo resolví en un one-liner:


data_text = <<DATA_TEXT
37107287533902102798797998220837590246510135740250
46376937677490009712648124896970078050417018260538
74324986199524741059474233309513058123726617309629
91942213363574161572522430563301811072406154908250
23067588207539346171171980310421047513778063246676
89261670696623633820136378418383684178734361726757
28112879812849979408065481931592621691275889832738
44274228917432520321923589422876796487670272189318
47451445736001306439091167216856844588711603153276
70386486105843025439939619828917593665686757934951
62176457141856560629502157223196586755079324193331
64906352462741904929101432445813822663347944758178
92575867718337217661963751590579239728245598838407
58203565325359399008402633568948830189458628227828
80181199384826282014278194139940567587151170094390
35398664372827112653829987240784473053190104293586
86515506006295864861532075273371959191420517255829
71693888707715466499115593487603532921714970056938
54370070576826684624621495650076471787294438377604
53282654108756828443191190634694037855217779295145
36123272525000296071075082563815656710885258350721
45876576172410976447339110607218265236877223636045
17423706905851860660448207621209813287860733969412
81142660418086830619328460811191061556940512689692
51934325451728388641918047049293215058642563049483
62467221648435076201727918039944693004732956340691
15732444386908125794514089057706229429197107928209
55037687525678773091862540744969844508330393682126
18336384825330154686196124348767681297534375946515
80386287592878490201521685554828717201219257766954
78182833757993103614740356856449095527097864797581
16726320100436897842553539920931837441497806860984
48403098129077791799088218795327364475675590848030
87086987551392711854517078544161852424320693150332
59959406895756536782107074926966537676326235447210
69793950679652694742597709739166693763042633987085
41052684708299085211399427365734116182760315001271
65378607361501080857009149939512557028198746004375
35829035317434717326932123578154982629742552737307
94953759765105305946966067683156574377167401875275
88902802571733229619176668713819931811048770190271
25267680276078003013678680992525463401061632866526
36270218540497705585629946580636237993140746255962
24074486908231174977792365466257246923322810917141
91430288197103288597806669760892938638285025333403
34413065578016127815921815005561868836468420090470
23053081172816430487623791969842487255036638784583
11487696932154902810424020138335124462181441773470
63783299490636259666498587618221225225512486764533
67720186971698544312419572409913959008952310058822
95548255300263520781532296796249481641953868218774
76085327132285723110424803456124867697064507995236
37774242535411291684276865538926205024910326572967
23701913275725675285653248258265463092207058596522
29798860272258331913126375147341994889534765745501
18495701454879288984856827726077713721403798879715
38298203783031473527721580348144513491373226651381
34829543829199918180278916522431027392251122869539
40957953066405232632538044100059654939159879593635
29746152185502371307642255121183693803580388584903
41698116222072977186158236678424689157993532961922
62467957194401269043877107275048102390895523597457
23189706772547915061505504953922979530901129967519
86188088225875314529584099251203829009407770775672
11306739708304724483816533873502340845647058077308
82959174767140363198008187129011875491310547126581
97623331044818386269515456334926366572897563400500
42846280183517070527831839425882145521227251250327
55121603546981200581762165212827652751691296897789
32238195734329339946437501907836945765883352399886
75506164965184775180738168837861091527357929701337
62177842752192623401942399639168044983993173312731
32924185707147349566916674687634660915035914677504
99518671430235219628894890102423325116913619626622
73267460800591547471830798392868535206946944540724
76841822524674417161514036427982273348055556214818
97142617910342598647204516893989422179826088076852
87783646182799346313767754307809363333018982642090
10848802521674670883215120185883543223812876952786
71329612474782464538636993009049310363619763878039
62184073572399794223406235393808339651327408011116
66627891981488087797941876876144230030984490851411
60661826293682836764744779239180335110989069790714
85786944089552990653640447425576083659976645795096
66024396409905389607120198219976047599490197230297
64913982680032973156037120041377903785566085089252
16730939319872750275468906903707539413042652315011
94809377245048795150954100921645863754710598436791
78639167021187492431995700641917969777599028300699
15368713711936614952811305876380278410754449733078
40789923115535562561142322423255033685442488917353
44889911501440648020369068063960672322193204149535
41503128880339536053299340368006977710650566631954
81234880673210146739058568557934581403627822703280
82616570773948327592232845941706525094512325230608
22918802058777319719839450180888072429661980811197
77158542502016545090413245809786882778948721859617
72107838435069186155435662884062257473692284509516
20849603980134001723930671666823555245252804609722
53503534226472524250874054075591789781264330331690
DATA_TEXT

puts (data_text.split("\n").map { |n| n.to_i(10) }.inject(0) { |sum,n| sum + n }).to_s[0..9]

El resultado fue (solo por curiosidad):

$ ruby euler13.rb
5537376230

Y la corrida fue casi instantánea.
El problema 14 consiste en buscar el número menor a 1000000 que genera la secuencia de Collatz más grande. La secuencia de Collatz se calcula así:
  • Si n es 1, terminó
  • Si n es par, es n:collatz(n/2)
  • Si n es impar, es n:collatz(n*3 + 1)

Este problema se resuelve con un poco de memoization:

class PathLengthMemoizator
def initialize
@lengths = {1 => 0}
@max_n = 1
end
attr_reader :max_n

def max_length
@lengths[max_n]
end

def calculate_length(n)
return @lengths[n] if @lengths.include?(n)
@lengths[n] = calculate_length( n % 2 == 0 ? n/2 : 3*n + 1 ) + 1
@max_n = n if @lengths[n] > @lengths[@max_n]
@lengths[n]
end
end

plm = PathLengthMemoizator.new
(1...1000000).each do
|n|
plm.calculate_length(n)
end
puts "Longest path number: #{plm.max_n}"
puts "Length: #{plm.max_length}"

Y cuando lo corrí, dio este resultado:

$ time ruby euler14.rb
Longest path number: 837799
Length: 524

real 0m15.058s
user 0m13.609s
sys 0m1.284s

El problema 15 es el más interesante. En un cuadriculado de tamaño 20 * 20, yendo por las rayitas para abajo y a la derecha, ¿cuántas formas hay de ir desde la posición (0,0) hasta la (20,20)? En un principio, parecía un quilombo. Pero, calculando cuantos caminos hay desde (0,0) hasta (X,Y) me di cuenta que en las diagonales se armaba el Triángulo de pascal. Entonces, mirando fijo, me salió la fórmula general para calcular cuantos caminos hay hasta (X,Y) desde (0,0). Es (X*Y)! / (X! * Y!).
Aplicando la fórmula a la coordenada (20,20) queda 40! / (20! * 20!), que corriendo en el irb queda así:

$ irb
irb(main):001:0> class Integer
irb(main):002:1> def fact
irb(main):003:2> (1..self).inject(1) { |f,n| f*n }
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> 40.fact / (20.fact * 20.fact)
=> 137846528820

Habiéndo resuelto el problema en forma elegantísima, me voy a dormir antes que encuentre otro.
Happy hacking,
Aureliano.

2008-10-28

Proyecto Euler: Problema 12

Sigo enfermo con el proyecto Euler. Y ya hice el problema 12. El mismo consiste en encontrar el primer número triangular con más de 500 divisores. Un número triangular es de la forma 1 + 2 + 3 + ... + n. Y, se puede calcular rápidamente como n*(n+1)/2.
Mi primer intento de resolución (que anduvo pero fue lento) consistió en usar el generador de números primos del ejercicio 3 para calcular la cantidad de divisores del número, calculándolo directamente. Acá va el código:


class Integer
def triangle
(self * (self + 1)) / 2
end
def divisor_count
n = self
pg = Primes.new
count = 1
while n != 1
p = pg.next
pow = 0
while n % p == 0
pow += 1
n /= p
end
count *= (pow + 1)
end
count
end
end

class OddPrimes
def initialize
@current = 1
@primes = []
end
attr_reader :current
def next
while not prime?(@current += 2)
end
@primes << @current
@current
end

def prime?(n)
i = 0
p = 1
while true
break if i >= @primes.length
p = @primes[i]
break if p * p > n
break if n % p == 0
i += 1
end
return i >= @primes.length || p * p > n
end
end

class Primes < OddPrimes
def initialize
super
@first = true
end
def next
if @first
@first = false
2
else
super
end
end
def current
odd_super = super
odd_super == 1 ? 2 : odd_super
end
end

n = 1
while n.triangle.divisor_count <= 500
n += 1
end

puts "Triangle #{n} (#{n.triangle}) is the first with over 500 divisors"

Anduvo, pero la corrida tardó como 4 minutos. Este es el resultado:

$ time ruby euler12.rb
Triangle 12375 (76576500) is the first with over 500 divisors

real 4m17.556s
user 3m57.519s
sys 0m16.117s

Así que me puse a pensar como hacer para que ande más rápido. Se me ocurrieron varias cosas:
  • Puedo calcular la factorización de n*(n+1)/2 sabiendo la factorización de n y n+1
  • Puedo calcular la factorización de cada número una sola vez (guardando factorizaciones viejas)
  • La factorización la puedo escribir recursivamente para usar las factorizaciones que ya calculé

Usando todo esto, hice una nueva implementación:

class OddPrimes
def initialize
@current = 1
@primes = []
end
attr_reader :current
def next
while not prime?(@current += 2)
end
@primes << @current
@current
end

def prime?(n)
i = 0
p = 1
while true
break if i >= @primes.length
p = @primes[i]
break if p * p > n
break if n % p == 0
i += 1
end
return i >= @primes.length || p * p > n
end
end

class Primes < OddPrimes
def initialize
super
@first = true
end
def next
if @first
@first = false
2
else
super
end
end
def current
odd_super = super
odd_super == 1 ? 2 : odd_super
end
end

class MemoFactorizator
def initialize
@factored = {1 => {} }
end
def factorize(n, pg = Primes.new)
return @factored[n] if @factored.include?( n )
p = pg.current
while n % p != 0
p = p.next
end
factorization = factorize( n / p, pg ).dup
factorization[p] = factorization.include?(p) ? factorization[p] + 1 : 1
@factored[n] = factorization
factorization
end
end

n = 1
mf = MemoFactorizator.new
while true
fact_n = mf.factorize(n)
fact_nn = mf.factorize(n+1)
triangle_factorization = fact_n.merge(fact_nn) { |k,old,new| old + new }
triangle_factorization[2] -= 1

divisor_count = triangle_factorization.values.inject(1) { |c,pow| c*(pow+1) }
break if divisor_count > 500
n += 1
end

puts "First triangle: #{n} (#{n*(n+1)/2})"
puts "Factorization: #{triangle_factorization.inspect}"
puts "Divisor count: #{divisor_count}"

La nueva implementación tardó 6 segundos en correr.

$ time ruby euler12.rb
First triangle: 12375 (76576500)
Factorization: {5=>3, 11=>1, 17=>1, 7=>1, 13=>1, 2=>2, 3=>2}
Divisor count: 576

real 0m6.690s
user 0m6.568s
sys 0m0.100s

Ahora estoy contento porque dio el mismo resultado que la vez anterior pero mucho más rápido y con código más lindo.

Happy hacking,
Aureliano.

Cumplí 0x20


El 18 cumplí años de nuevo. Parece que es cada vez más rápido.

Un poquito más del proyecto Euler

Sigo enfermo resolviendo ejercicios del proyecto euler. Hoy resolví el ejercicio 11. El mismo consiste en encontrar en una matriz de 20x20 los cuatro números consecutivos en una misma línea (que puede ser horizontal, vertical o diagonal en cualquiera de sus 2 variantes) tal que su multiplicación sea lo más grande posible.
He aquí el código fuente en ruby:


require 'matrix'

RAW_DATA = <<RAW_DATA
08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48
RAW_DATA
data = Matrix[*RAW_DATA.split("\n").map { |row| row.split(" ").map { |n| n.to_i(10) } }]

DIRECTIONS = [[1,0],[0,1],[1,1],[1,-1]]
best_product = -1
best_row = -1
best_col = -1
best_dir = [0,0]

DIRECTIONS.each do
|dir|
(0...20).each do
|row|
(0...20).each do
|col|
product = (0...4).inject(1) do
|prod, pos|
target_row = row + pos * dir[0]
target_col = col + pos * dir[1]
(0...20).include?(target_row) && (0...20).include?(target_col) ? prod * data[target_row, target_col] : 0
end
if product > best_product then
best_product = product
best_row = row
best_col = col
best_dir = dir
end
end
end
end

puts "Product: #{best_product}"
puts "Row: #{best_row}"
puts "Col: #{best_col}"
puts "Dir: #{best_dir.inspect}"

(0...4).each do
|pos|
target_row = best_row + pos * best_dir[0]
target_col = best_col + pos * best_dir[1]
puts "Position: #{target_row}, #{target_col}"
puts "Value: #{data[target_row, target_col]}"
end

Y para que vean cuanto dio, esta es la salida de la corrida

Product: 70600674
Row: 12
Col: 6
Dir: [1, -1]
Position: 12, 6
Value: 89
Position: 13, 5
Value: 94
Position: 14, 4
Value: 97
Position: 15, 3
Value: 87

Me voy a dormir.
Happy hacking,
Aureliano.

2008-10-24

Algunas verdades y mentiras infundadas del desarrollo de software

  • Java es el nuevo Cobol
  • Eclipse es el nuevo emacs
  • Java es lento
  • Ruby es lento
  • C es rápido
  • C++ es rápido
  • PHP es una bosta
  • Las 3 virtudes de un gran programador son la pereza, la impaciencia y la soberbia
  • Haciendo test-first y refactoring se puede mantener la calidad del código
  • Los sistemas en producción tienen código fuente feo
  • Haciendo test-first y refactoring no se hacen sistemas que queden en producción
  • Los programadores tienen pocas habilidades interpersonales
  • Los programadores deben hablar con los clientes para hacer los desarrollos
  • Barato, rápido, bueno: elija 2
  • Programando en Rails un programador es 10 veces más productivo que programando en J2EE
  • Lisp es el mejor lenguaje de programación
  • Smalltalk es el mejor lenguaje de programación
  • A python hay que agregarle closures y sería un lenguaje espectacular
  • Los verdaderos programadores programan en Fortran
  • Los verdaderos programadores programan en C++
  • Los verdaderos programadores programan en Assembler
  • Los verdaderos programadores programan en código máquina
  • Los verdaderos programadores silvan en el teléfono para transmitir datos a un modem, hacer un buffer overflow y reprogramar el server
Happy hacking,
Aureliiano.

2008-10-21

Proyecto Euler - Problemas 5 a 10

Sigo a full con el Proyecto Euler. Y hoy hice 6 problemas. Les cuento como los solucioné:
El problema 5 es bastante fácil. Esencialmente se trata de encontrar el mínimo común múltiplo de los números 1..20. Lo más molesto es que ni el divisor común mayor ni el mínimo común múltiplo están en mi versión de ruby (ruby 1.8.6 patchlevel 111, ubuntu hardy), así que lo implementé:


class Integer
def gcd(other)
values = [self > 0 ? self : -self, other > 0 ? other : -other]
a = values.min
b = values.max
while (a != 0)
a, b = b % a, a
end
b
end
def lcm(other)
self * other / self.gcd(other)
end
end

puts (2..20).inject(1) {|ac, n| ac.lcm(n)}

Sólo por curiosidad, el número me dio 232792560.
El problema 6 es calcular la diferencia entre la suma de los cuadrados y el cuadrado de la suma de una secuencia 1..n (con n=100). Tampoco fue muy difícil.

class Integer
def sum_square
(1..self).inject(0) { |acum,n| acum + n * n }
end
def square_sum
((self * (self+1)) / 2) ** 2
end
end

puts 100.square_sum - 100.sum_square

También por curiosidad, el resultado es 25164150.
El problema 7 es encontrar el primo número 10001. Para eso usé la criba que definí en el post anterior e iteré hasta el primo nro 10001.Acá va el código:

class OddPrimes
def initialize
@current = 1
@primes = []
end
def next
while not prime?(@current += 2)
end
@primes << @current
@current
end
def prime?(n)
i = 0
p = 1
while true
break if i >= @primes.length
p = @primes[i]
break if p * p > n
break if n % p == 0
i += 1
end
return i >= @primes.length || p * p > n
end
end

op = OddPrimes.new
p = 2
(2..10001).each {
p = op.next
}

puts p

El primo que dio es el número 104743.
El problema 8 es medio rebuscado. Lo que me resultó más difícil es entender el enunciado. Te dan un número gigante y tenés que buscar cuáles son los 5 dígitos consecutivos que multiplicados dan el número más grande. Como en el problema 4 había implementado como obtener un dígito arbitrario y la cantidad de dígitos de un número, usé eso y resolví el asunto.

class Integer
def digit(pos)
(self / (10 ** pos)) % 10
end
def digit_count
1 if self == 0
dc = 0
while self - (10 ** dc) > 0
dc += 1
end
dc
end
end

n = 7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450

pos = -1
prod = -1
(0..995).each do
|p|
candidate = n.digit(p) * n.digit(p+1) * n.digit(p+2) * n.digit(p+3) * n.digit(p+4)
if candidate > prod
pos = p
prod = candidate
end
end
puts "Digits: #{n.digit(pos)}, #{n.digit(pos+1)}, #{n.digit(pos+2)}, #{n.digit(pos+3)}, #{n.digit(pos+4)}"
puts "Position: #{pos}"
puts "Product: #{prod}"

El resultado que dio es:

Digits: 9, 7, 8, 9, 9
Position: 631
Product: 40824

El problema 9 consiste en buscar número a,b,c tal que son enteros a+b+c=1000 y a^2+b^2=c^2 (teorema de pitágoras para triangulos rectos). Para hacer eso, busqué una tripleta (a,b,c) tal que la suma divida a 1000 y después multipliqué cada número. La verdad es que este problema me costó un ratito. Acá va el código.

#Find a triplet that fits the pitagoras formula and its components divide 1000
def primogenial_triplet
(3..100).each do
|c|
(2...c).each do
|b|
(1...b).each do
|a|
return [a,b,c] if a ** 2 + b ** 2 == c ** 2 and 1000 % (a+b+c) == 0
end
end
end
end

tr = primogenial_triplet
factor = 1000 / (tr[0] + tr[1] + tr[2])
puts tr.map {|x| x * factor}

Los a,b y c que encontré son: 200, 375 y 425 respectivamente.
El problema 10 es el más interesante hasta ahora. El mismo consiste en sumar los números primos menores a 2000000. Primero usé el generador de primos que tenía, y lo implementé, pero tardó más de un minuto. El código es:

class OddPrimes
def initialize
@current = 1
@primes = []
end
def next
while not prime?(@current += 2)
end
@primes << @current
@current
end
def prime?(n)
i = 0
p = 1
while true
break if i >= @primes.length
p = @primes[i]
break if p * p > n
break if n % p == 0
i += 1
end
return i >= @primes.length || p * p > n
end
end

op = OddPrimes.new
sum = 2
while ( (p = op.next) < 2000000 )
sum += p
end

puts sum

Pero me quedé enojado porque tardaba mucho, así que googlié un toque y encontré este post que mostraba otra forma de generar números primos. Y lo implementé. La idea es ir marcando todos los múltiplos (menores a un número dado) de los números primos que voy encontrando para encontrar los primos sin hacer divisiones. Y anduvo más rápido. Igualmente, hay que notar que gasta bastante más memoria que la versión anterior. Así que tiene un trade-off distinto entre memoria y tiempo de procesamiento. El código está acá:

MAX = 2000000

class OddPrimes
def initialize(max)
@max = max
@prime_matrix = [false,true] * ((max / 2) + 1)
@prime_matrix[1] = false
@current = 1
end
def next
while not prime?(@current += 2)
throw :max_reached if @current >= @max
end
mark
@current
end

def mark
i = @current
step = @current * 2
while (i<@max)
@prime_matrix[i] = false
i += step
end
end

def prime?(n)
@prime_matrix[n]
end
end

op = OddPrimes.new(MAX)
sum = 2
catch :max_reached do
while ( (p = op.next) < MAX )
sum += p
end
end

puts sum

Esta solución corrió en 6 segundos:

$ time ruby euler10.rb
142913828922

real 0m6.179s
user 0m5.780s
sys 0m0.356s


Voy a tratar de seguir resolviendo los problemas del proyecto Euler, así que sigan sintonizados.
Happy hacking,
Aureliano.

2008-10-20

Proyecto Euler - Primeros 4 problemas resueltos en Ruby

Mirando el blog de xkcd encontré un site donde postean un montón de problemas "matematicosos" para ser resueltos mediante programas de computadora que se llama proyecto Euler. Así que me copé, me puse a resolverlos e hice los primeros 4.
El primero es calcular la suma de los múltiplos de 3 o 5 menores a 1000, que lo saqué con un one-liner en ruby:


irb(main):006:0> (1...1000).select {|x| x % 3 == 0 || x % 5 == 0}.inject(0) {|sum,num| sum + num }
=> 233168

El segundo es calcular la suma de los números de fibonacci pares menores a 4000000. Este también lo hice en el irb, pero quizás debería haberlo escrito en un archivo. Lo que escribí en el irb es:

irb(main):007:0> class Fib
irb(main):008:1> def initialize()
irb(main):009:2> @a = 0
irb(main):010:2> @b = 1
irb(main):011:2> end
irb(main):012:1> def next()
irb(main):013:2> @a, @b = @b, @a+@b
irb(main):014:2> @b
irb(main):015:2> end
irb(main):016:1> end
irb(main):027:0> sum = 0
=> 0
irb(main):028:0> f = Fib.new
=> #
irb(main):029:0> while (n = f.next) <= 4000000 irb(main):030:1> sum += n if n% 2 == 0
irb(main):031:1> end
=> nil
irb(main):032:0> sum
=> 4613732

O sea, la suma me dio 4613732, espero no haberme equivocado.
El tercer problema es encontrar el factor primo más grande de un número gigante (n=600851475143) . En este ya me puse a escribirlo en un archivo, porque me llevó más de 2 minutos. Lo que hice fue programar una criba para generar los números primos e ir dividiendo el n por los primos hasta que quede 1. Entonces el último primo por el que dividí es el factor primo más grande. El código en Ruby es así:

class OddPrimes
def initialize
@current = 1
@primes = []
end
def next
while not prime?(@current += 2)
end
@primes << @current
@current
end
def prime?(n)
i = 0
p = 1
while true
break if i >= @primes.length
p = @primes[i]
break if p * p > n
break if n % p == 0
i += 1
end
return i >= @primes.length || p * p > n
end
end

n = 600851475143
max_p = 1
op = OddPrimes.new
while n>1
max_p = op.next
while n % max_p == 0
n /= max_p
end
end

puts max_p

Según mi código, el factor primo más grande es el 6857.
Y, por último porque me tengo que ir a dormir, resolví el cuarto problema. El mismo consiste en calcular el número "capicúa" más grande que resulta de multiplicar 2 números de hasta 3 dígitos. El código es medio bestia, y fue la primera vez que el resultado no se sintió "instantáneo" (tardó como 2 segundos). Pero calculó el resultado. La idea del mismo es trivial. Probar todas las posibles multiplicaciones (que son como 500000) y fijarse si el resultado es capicúa y más grande que el número más grande encontrado. El código en ruby es:

class Numeric
def digit(pos)
(self / (10 ** pos)) % 10
end
def digit_count
1 if self == 0
dc = 0
while self - (10 ** dc) > 0
dc += 1
end
dc
end
def palindromic
digit_count = self.digit_count
(0...(digit_count / 2)).each do
|pos|
return false if self.digit(pos) != self.digit(digit_count - pos - 1)
end
return true
end
end

lp = 0
f1, f2 = 0,0
(1..999).each { |x| (1..x).each { |y|
candidate = x * y
if candidate > lp and candidate.palindromic
lp = candidate
f1 = x
f2 = y
end
}}
puts "Largest Palindromic: #{lp}"
puts "Factors: #{f1}, #{f2}"

El resultado que dio es:

Largest Palindromic: 906609
Factors: 993, 913

Estoy seguro que se puede optimizar "bocha" este código.

Bueno, me voy a dormir.
Espero escribir próximamente más problemas resueltos del proyecto Euler.
Happy hacking,
Aureliano.

2008-10-11

Jugando con un proxy HTTP

El proxy transparente de Telefónica está andando mal y trae versiones viejas de un montón de páginas. Así que estuve pensando la forma de forzarlo a que traiga la última versión. Lo hablé con algunos compañeros de laburo, y lo que me sugirieron es que instale un proxy que agregue headers que fuercen el no-cacheo de las páginas. En particular, sugirieron que instalara privoxy y lo configurara con los headers que quiero agregar.
Pero, ¿para que voy a hacer algo rápida y fácilmente si me puedo meter en un quilombito? El mismísimo whytheluckystiff hizo mousehole, un proxy implementado en ruby puro. Pero mousehole está pensado para transformar las páginas a medida que van bajando, no para cambiar los headers a la ida. Así que tuve que toquetear un poquito.
Por lo que pude ver, mousehole tiene 3 capas. Una primera capa es el handler de mongrel que atiende los pedidos de http, la capa del medio es una capa que maneja los plug-ins del proxy (a los que llama apps) y una tercera capa son las apps propiamente dichas. Para agregar la funcionalidad de cambiar el request tuve que tocar estas 3 capas.
En el handler, agregué un lugar para interceptar la página antes de pedir al host la página. Este es el diff del svn:


Index: lib/mouseHole/proxyhandler.rb
===================================================================
--- lib/mouseHole/proxyhandler.rb (revision 129)
+++ lib/mouseHole/proxyhandler.rb (working copy)
@@ -46,6 +46,8 @@
choose_header(reqh, header)
set_via(header)

+ uri, header = @central.change_request(uri, header)
+
http = Net::HTTP.new(env['server-name'], env['server-port'], @central.proxy_host, @central.proxy_port)
http.open_timeout = 10
http.read_timeout = 20

En la capa que maneja los plugins (que en el código se llama central) hice que cuando llegue un pedido, intente reescribir los headers en cada app. Este es el diff del svn:

Index: lib/mouseHole/central.rb
===================================================================
--- lib/mouseHole/central.rb (revision 129)
+++ lib/mouseHole/central.rb (working copy)
@@ -95,6 +95,13 @@
end
end

+ def change_request(uri, header)
+ @apps.values.each do |app|
+ uri, header = app.change_request(uri, header)
+ end
+ [uri, header]
+ end
+
def rewrite(page, resin)
apps = find_rewrites(page)
return false if apps.empty?

Y por último, en la clase base de las apps hice que por default deje los headers y el URI original, así es todo retrocompatible (o casi). Acá está el diff del svn:

Index: lib/mouseHole/app.rb
===================================================================
--- lib/mouseHole/app.rb (revision 129)
+++ lib/mouseHole/app.rb (working copy)
@@ -58,6 +58,10 @@
end
end

+ def change_request(uri, header)
+ [uri, header]
+ end
+
def do_rewrite(page)
@document = page.document
begin

Con todo esto andando, pude hacer mi cometido, que es hacer un proxy que le agregue el header "Cache-Control: no-cache" a los requests, para que Speedy no me de versiones viejas. El código de la app que hace esto quedó resimple:

class NoCache < MouseHole::App
title "No Cache"
namespace "aure"
description %{
Forces all the request to be non cached.
Sets the Cache-Control header to no-cache.
}
version "0.1"

def change_request(uri, header)
header << ['Cache-Control', 'no-cache']
[uri, header]
end
end


Gracias Speedy y Telefónica por la inspiración y happy hacking,
Aureliano.

PD: si los headers no sirven, voy a cambiar el URI agregando parámetros aleatorios y listo, así que no me provoquen.

2008-10-05

Conferencias al cuadrado

Esta semana estuve a full. En Buenos Aires hubo 2 conferencias de seguridad informática y tuve la suerte de poder ir a las 2.

El martes y miércoles estuve en BA-Con. Ahí conocí un montón de gente interesante y tuve la suerte de que las charlas también fueron re-interesantes.
Algunas charlas que me llamaron la atención y quería comentarles son:

  • En la charla "WPA/WPA2: how long is it gonna make it" Cédric Blancher y Simon Maréchal contaron una forma interesante de generar passwords para brutforcear. La idea es que armás una cadena de markov donde cada caracter es un estado y la probabilidad de pasar de un estado "a" a un estado "b" es el porcentaje de las veces que viene una "b" después de una "a" de los passwords con los que generaste la cadena.
  • La charla "All the Crap Aircrafts Receive and Send" fue divertida. Hendrik Scholz contó cómo se enteran los aeropuertos de los tiempos de arribo de los aviones (entre otras cosas). Y cómo hizo para reversear los mensajes (incluyendo ir con un receptor de radio en el auto al aeropuerto).
  • En "LeakedOut: the Social Networks You Get Caught In", mi amigo y compañero de trabajo José Orlicki presentó sus avances de su trabajo de doctorado, donde estudia las redes sociales que se arman en los sitios Web 2.0 y sus implicancias en seguridad. A mi me pareció que fue la charla más original de las 2 conferencias.
  • En "Pass-the-hash Toolkit for Windows" mi compañero de trabajo Hernán Ochoa mostró una vez más porqué es un groso en el mundo de la seguridad informática. Esta vez contó de su proyecto "Pass-the-hash" y su implementación en Windows. El proyecto sirve para afanar tokens de autenticación de la memoria de un Windows (!) y conectarse a otros hosts. La verdad, una masa. Si quieren usarlo, pueden bajarselo de acá.
El jueves y viernes tocó Ekoparty. Y también fue re-interesante. Les cuento que charlas me llamaron más la atención:
  • En "Code injection on virtual machines", mi compañero de laburo Nicolás Economou se disfrazó de Neo y mostró como escribir memoria usada por un proceso de VM-Ware desde afuera para modificar el comportamiento de un programa corriendo adentro (!).
  • En "Atancando RSA mediante un nuevo métodos de factorización de enteros", Hugo Scolnik mostró el laburo que están haciendo para tratar de romper RSA. La verdad, pareció re-interesante. Transformó el problema de factorización de un número de la forma p*q en un árbol de decisiones y muchas formas de podar el árbol. Y mostró como, si se pudiera elegir correctamente en ese árbol (cosa que todavía no hacen), la factorización del RSA640 tomaría un par de minutos en una Pentium 3.
  • En "Smartphones (in)security" mis compañeros de laburos Alfredo Ortega y Nicolás Economou (que ya se había sacado el traje de Neo), mostraron cómo explotar un stack overflow en iPhone y Android. Fue una charla interesante y entretenida, donde también explicaron re-bien como funciona la técnica "return to libc" para escribir exploits cuándo la memoria que podemos sobreescribir no tiene derechos de ejecución.
  • Y en "Debian's OpenSSL random number generator Bug" Luciano Bello y Maximiliano Bertacchini contaron los detalles técnicos y las consecuencias de comentar una línea de código para que el valgrind no se queje. Esa línea, hacía que la entropía del generador sea tomada solamente del nro. de proceso (o sea, que si repetís el número de proceso, generás los mismos números aleatorios) y cómo usar esto para hacer ataques.
Al final, después de una semana de conferencias quedé con la cabeza que explota de conocimientos nuevos, conocí un montón de gente interesante y tengo un muchas tarjetas personales (que nunca sé que hacer con ellas).

Happy hacking,
Aureliano.

Blog de Nicolás

Cómo les había contado en un post anterior, voy a ser papá. Y hoy, junto con Agus inauguramos su propio blog. Ahí vamos a ir poniendo todas las fotos, videos y etcéteras mientras sea un proyecto o un bebé.

2008-09-28

Guerra con Telefónica

Hace un tiempo les conté que tuve problemas con Telefónica. Los problemas siguieron, y groso. En los últimos 3 meses estuve más de la mitad del tiempo sin teléfono ni ADSL. El jueves pasado, fui y pedí que me dieran un listado de incidencias impreso, al que se negaron. Pero me mostraron el registro de apertura y resolución de tickets. La verdad es que es una vergüenza. Cerraron un montón de tickets diciendo que había problemas en el consorcio pero a mi no me informaron cuál es el problema del consorcio.
Vivo en un edificio de menos de 10 años, los conductos centrales para cables de teléfono son de 20cmx20cm (o más), y el cableado de dentro del edificio lo pusieron ellos. Aparte nunca dijeron que parte del cableado del consorcio sería la que está rota y cuándo un técnico habló personalmente conmigo me dijo que el consorcio y el departamento no tienen ningún problema. En el medio de todos los tickets cerrados por "problemas en el cableado interno del consorcio" hubo uno que lo cerraron por otro motivo porque ya les daba vergüenza. Se ligó mi teléfono con un teléfono de otro edificio. ¿Cómo me enteré? Disqué 115, corté y aparte de sonar mi teléfono sonó en otro lado y atendieron, así que pude charlar con la persona con la que tenía ligado el teléfono. Así que llamé al 114 y lo avisé. Sino quizás a este ticket también lo hubieran cerrado como "problemas en el cableado interno del consorcio".

Bueno, abajo les dejo un detalle de los tickets, fechas de inicio y fin y sus resoluciones y les pregunto. ¿Saben cómo hacer para que les pongan una multa gigante?

  • Nro. de ticket: 2008027000066336
  • Inicio:28/6
  • Fin:7/7
  • Resolución: Cableado interno consorcio
  • Comentarios: ¿No será que el problema está en otro lado?
  • Nro. de ticket: 2008027000068188
  • Inicio: 3/7
  • Fin: 24/7
  • Resolución: Cableado interno consorcio
  • Comentarios: ¿No será que el problema está en otro lado?
  • Nro. de ticket: 2008027000076656
  • Inicio: 21/7
  • Fin: 1/8
  • Resolución: No permitió vigilancia
  • Comentarios: Vienen en cualquier momento, ¿saben que tenemos cosas que hacer aparte de esperar a que vengan a arreglar el TE? No vinieron en los horarios de citas concertados
  • Nro. de ticket: 2008027000080132
  • Inicio: 29/7
  • Fin: 21/8
  • Resolución: Configuración ADSL
  • Comentarios: Era hora que admitan que hay algún problema en su servicio, la configuración mala del ADSL genera interferencias en la línea (aparte de microcortes)
  • Nro. de ticket: 2008027000083451
  • Inicio: 7/8
  • Fin: 21/8
  • Resolución: Configuración ADSL
  • Comentarios: Pasó más de una semana y seguía con los mismos problemas
  • Nro. de ticket: 2008027000089423
  • Inicio: 25/8
  • Fin: 6/9
  • Resolución: Cambio de bajada
  • Comentarios: Al fin hacen algo, para algo pago el abono de mantenimiento
  • Nro. de ticket: 2008027000094576
  • Inicio: 10/9
  • Fin: 17/9
  • Resolución: Cableado interno consorcio
  • Comentarios: Nadie tocó nada, ¿se rompe y arregla solo?
  • Nro. de ticket: 2008027000098212
  • Inicio: 22/9
  • Fin: 24/9
  • Resolución: Se regenera línea ADSL
  • Comentarios: Parece que sigo con quilombo
¿Saben que tengo que hacer para que los multen? ¡Tuve tickets abiertos el 90% del tiempo de los últimos 3 meses!
Happy hacking,
Aureliano.

2008-09-14

Inauguración quilmeña - El ibérico

Quería contarles que Guillermo, el primo de mi mujer, inauguró "El ibérico", una fiambrería-rotisería en Quilmes. Más especificamente en Brandsen e Hipólito Yrigoyen (sobre Hipólito Yrigoyen). Así que si vuelven a Quilmes después de un día de laburo/joda y no saben que comer, pueden pasar por ahí y comprarse un pollo al spiedo, o un buen jamón, un buen queso y pan, y para el postre, un dulce cordobés casero que está super-re-barato ($7 el kilo, aprovechen antes que se avive). También un buen vino y tienen todo resuelto. Y no se olviden de decir que van de parte mía. Y si son unos vagonetas, también pueden llamar por teléfono al 4253-7006 y hacer su pedido.
Happy hacking,
Aureliano.

2008-09-08

¡Se reproducen!


Sí, quiero presentarles a nuestro primogénito. Esta es una imagen de la última ecografía (de cuando tenía 13 semanas de gestación). Vieron, aunque parezca increíble, ¡los geeks se reproducen!
¿Cuánto tardará en aprender a programar?

2008-09-03

Ingenieros contra la naturaleza

Los ingenieros son grosos y le ponen el pecho a las balas. Y hacen cosas grosísimas. Un ejemplo es esta foto de un dique en Nueva Orleans, que esta vez si aguantaron el paso de un huracán. Groso lo que bancan, la foto es increíble (3 metros de agua con tormenta aguantados por una pared).



Si quieren, pueden ver la nota en clarín.

2008-08-26

Jornadas Regionales de Software Libre

La semana pasada se hicieron en Buenos Aires las Jornadas Regionales de Software Libre. Las Jornadas fueron interesantes y tuve la oportunidad de charlar con un montón de gente y hacer mi presentación. Porque estoy a full con el laburo solo pude ir el jueves, que fue el día de mi charla de Rubygame como ya les había contado antes.
La charla salió medio medio porque tuve quilombos con el proyector :(. Después del ¡tercer proyector! logré que salga algo por la salida de VGA de mi notebook, pero solo booteando de un LiveCD. Por lo tanto, no pude mostrar mis jueguitos andando, pero si pude mostrar los slides que tenía preparados. Igual fue divertido, y espero poder presentar algo la próxima (que si puedo me llevo mi propio proyector y listo). Por último les dejo una foto de mi charla, que sacaron Gutes y Tenuki:


Happy hacking,
Aureliano.

2008-08-15

Torre de Agua


Ayer al mediodía, que todavía estaba en Mar del Plata, fuimos con Agus a visitar la Torre de Agua de Mar del Plata. Antes de ir estuvimos buscando el horario de apertura y cierre de la torre y no lo encontramos por ningún lado. Así que les paso horarios y coordenadas.
¿Cuándo? Días hábiles de 8:00 a 14:45hs.
¿Dónde? Falucho y Mendoza.
Si van, van a ver una vista panorámica de Mar del Plata, mirando desde arriba de la torre. Y abajo hay un museíto que no tiene demasiado interés. Pero la vista está bárbara.
Happy hacking,
Aureliano.

2008-08-12

"Vacaciones"

Esta semana estoy paseando por MDQ. Pero siempre algo de laburo tengo encima. Aparte de estar haciendo un trabajo práctico para mi doctorado, mañana miércoles a las 19hs nos encontramos con algunos de los pibes de Ruby Argentina en Piazza, que queda en Alem y la costa, y haremos un "PizzaConf" (o sea, nos juntamos a hablar de cosas de Ruby y comer pizza).
Todo rubista que quiera apropicuarse por allá será bienvenido. Si quieren traigan su notebook o alguna idea de algo que quieran presentar (hasta 1/2 hora, desde 1/2 segundo), o si quieren no traigan nada y vengan y listo. Si pinta, supongo que haré un "preview" de la presentación sobre RubyGame que voy a hacer en las Jornadas Regionales de Software Libre.
Happy hacking,
Aureliano.

2008-08-05

Cobos votó "no"

Update: Efectivamente un moderador sacó el comentario. Suerte que lo agarré a tiempo. Si quieren saquen sus propias conclusiones sobre porqué alguien quitaría un comentario como este.

A veces, y solo a veces, los foros de La Nación tienen algunas joyas increíbles. En particular, el mensaje #511 de esta nota es divertido. Acá abajo lo pongo textual (antes que lo agarre un moderador y lo saque):

COBOS POMPOSO CON PONCHO. COBOS JOCOSO. COMO TROMPO RODO POR OTROS HOYOS. COMO BOROCOTO, TROCO VOTO. COMO ORTODOXO DOCTOR, VOTO NO. VOTO CON LOS OJOS ROJOS, CON SOLLOZOS, CON SOPOR, MONOLOGO SOLO POR NO. ¿ VOTO SOLO POR LOS POROTOS ? ¿ VOTO POR SPONSOR CON FRONDOSO OBOLO ? PROMOTOR CON BOLSOS CON ORO, DOTO CON VOLVO ? COLOCO MONTOS GROSOS POR BOSTON, OSLO, TORONTO O LONDON ? COMO SOMOS !!! NO LO SOPORTO... BOROCOTO: ZORRO DOLOSO, COBOS: BOCHO HONROSO !!! FOR GOD !!! ¿ SOMOS TODOS GROSSOS ? NO. SOMOS ZONZOS, SOMOS LOCOS, SOMOS BOCHORNOSOS COMO COPPOLO. SOCORRO !!!

Mis charlas en las Jornadas Regionales de Software Libre

Hola,
Estoy muy contento de contarles que voy a participar en un par de charlas de las jornadas.
La primera es una charla introductoria a rubygame. Usando como excusa los jueguitos sobre los que estuve posteando a principio de año por acá, voy a pasar por las cosas básicas de la biblioteca. Esencialmente sprites y manejo de eventos.
La otra es una charla de mi amigo Matías Brutti sobre metaprogramación en Ruby, en la que le voy a dar una mano. Todavía no hablamos de como la vamos a organizar pero supongo que tendrá contenido parecido al del post del tema que hice acá a mediados del año pasado.
Las jornadas son el 20, 21 y 22 de agosto (sí, en 2 semanas). Mis 2 charlas son el 21 de agosto. La de Rubygame es a las 10AM (sí, bastante tempranelli) y la de metaprogramación a las 4PM. Las jornadas se hacen en la Universidad de Belgrano (Zabala 1837, Ciudad Autónoma de Buenos Aires, Argentina). Tienen que anotarse antes para poder ir. Hay más información en la página de las jornadas.
Los espero.
Happy hacking,
Aureliano.

2008-07-22

El día que Stallman apareció en infobae

Cada vez más, el software libre se hace mainstream. Y hoy pasó una vez más. Hoy entré a infobae.com y, sorpresa, sorpresa, al lado de los escándalos de la ex-novia de Ronaldo estaba una nota sobre ¡Richard Stallman! Acá les paso el link. ¡Y encima Stallman está peinado!

Hasta el premio Nobel de la Paz no para.

Happy hacking,
Aureliano.

2008-07-12

Criptografía cuántica - explicación simple.

Ayer a la tarde vino al laburo Andrés Rieznik a dar una charla sobre criptografía cuántica. Andrés es doctor en física, es especialista en fibras ópticas y actualmente está haciendo un post-doctorado en el ITBA (sí, donde yo estoy haciendo el doctorado). Y Andrés también iba a la división de al lado en la secundaria.
Bueno, volviendo al tema de la criptografía cuántica, primero nos explicó en que fenómenos de la física cuántica está basada.

Polarización, fotones y otras cosas que solo los físicos entienden


Como todos sabemos, la luz a veces es una onda. Cuando hace de onda, la onda puede oscilar en cualquier ángulo (por ejemplo: verticalmente, horizontalmente o diagonalmente). Y cuando pasa a través de un polarizador queda oscilando en la dirección que el polarizador la deja. Y si después ponemos otro a 90 grados, no pasa nada. Y más aún, si ponemos el segundo polarizador a 45 grados sale un cuarto de la luz original (se queda la mitad en cada polarizador) ¡polarizada en el ángulo del segundo polarizador!
Pero también, la luz a veces es muchas partículas. Cuándo actúa como partículas, decimos que está compuesta por fotones. Entonces ¿qué pasa cuando pasa un solo fotón por un polarizador?.
Según entendí de lo que dijo Andrés, los fotones tienen un ángulo de oscilación. Y pasan o no por un polarizador en función de ese ángulo. En particular, si el polarizador está perfectamente "alineado" con la oscilación del fotón pasa siempre y si está perfectamente cruzado con el fotón no pasa nunca. ¿Y qué pasa si está a 45 grados? Entonces pasa la mitad de las veces y, cuando pasa, cambia el ángulo de su oscilación al del polarizador por el que pasó.
Bueno, usando todo lo que está arriba, hace ya 24 años a unos cráneos se les ocurrió una idea para que 2 partes (digamos, para mantener las costumbres, Alice y Bob) compartan un "stream" de números al azar. O sea, que los 2 sepan los mismos números, pero que estos números no están definidos de antemano. Para los que sepan cripto, el resultado es muy parecido a Diffie-Hellman pero se llega por caminos muy distintos. A ese tipo de algoritmos se los llama de "intercambio secreto de claves". El primer algoritmo que hicieron se llama BB84 (son unos cráneos pero tienen menos onda para los nombres que un fotón).

El BB84


En el algoritmo BB84, cada número lo manda como un único fotón polarizado en un ángulo de 0, 45, 90 o 135 grados. 0 grados se corresponde a un 0 en base A, 45 grados a un 0 en base B, 90 grados a un 1 en base A y 135 grados a un 1 en base b.

El algoritmo consiste en lo siguiente:
Alice elige una base (entre A y B) y un nro (entre 0 y 1) y manda un fotón polarizado en el ángulo correspondiente. Bob elige una base al azar y detecta (en esa base) el número. Si los 2 eligieron la misma base, los 2 conocen el mismo número, sino hay un 50% de chances de que Bob tenga un número distinto del que tiene Alice. Este procedimiento se repite n veces.
Cuando Alice termina de mandar cada uno de los bits, Alice manda por un canal normal (no cuántico) que bases usó para mandar cada bit. Bob, con esa información, le dice cuáles de los bits leyó en la misma base que Alice transmitió (van a ser aproximadamente la mitad).
Para ver que efectivamente nadie cambió los bits en el camino se eligen algunos de los bits efectivamente transmitidos y se los manda por un canal no cuántico para verificar que la transmisión fue correcta.
Después de esto, los bits efectivamente transmitidos que no se publicaron son los que forman la clave conocida por Alice y Bob.

¿Y Eve?


¿Qué pasa si Eve interfiere un fotón? Bueno esta es LA gracia de todo el asunto. En mecánica cuántica existe una cosa que se llama "principio de incertidumbre" (lo siento, va sin link porque no me gustó el artículo de wikipedia). El principio de incertidumbre dice que es imposible obtener una medida en una partícula sin modificarla en otra aledaña. Por ejemplo, si medís la velocidad de una partícula modificás su posición, haciendo imposible saber la posición original.
Bueno, el número (0 o 1) y la base (A o B) que representa el ángulo de polarización de un fotón es como saber la posición y la velocidad. Si medís el número, como tiene que pasar por un polarizador, cambiás potencialmente la base del fotón (el 50% de los casos). O sea, que si Eve escucha un fotón, el 50% de los casos le cambia la base. De ese 50%, el 50% de los casos Bob va a escuchar el número incorrecto. Por lo tanto, en la verificación que se hace al final del algoritmo van a detectar que el 25% de los bits están mal. Entonces se detecta a Eve y no se usa esa clave para transmitir info segura.

Unas cositas más


El BB84 no es el único algoritmo conocido, hay variaciones, pero todas están basados en que para medir si un fotón pasa o no por un polarizador hay que cambiarle el ángulo de polarización.
Por último, las cosas que estén mal explicadas son culpa mía y las que estén bien culpa de Andres.

Happy hacking,
Aureliano.

2008-07-09

Problemas con el DNS de speedy

Update: Gracias a Manuel por llevar a mi atención esta vulnerabilidad del diseño del DNS. No está confirmado, pero es altamente probable que la inestabilidad de los servers de Speedy tengan que ver con esto. Les sugiero que como medida de seguridad usen los servidores DNSs que están mencionados abajo aunque tengan otro proveedor de internet, al menos por un tiempo. Estos servers aparentemente no son vulnerables.

Desde ayer que el DNS de speedy está inestable. Hoy llamé al soporte técnico y me lo confirmaron. Como no me dieron ningún servidor de DNS alternativo, me conecté a google x ip (estaba haciendoles ping para hacer troubleshooting) y leí algunos caches de páginas. Ahí encontré esta página donde sugieren usar Open DNS.

Así que configuré en mi router los servidores de nombres que dice ahí (son 208.67.222.222 y 208.67.220.220) y ahora puedo navegar nuevamente.

Happy hacking,
Aureliano.

2008-07-02

Cómo hacer que Telefónica arregle tu teléfono

Update: Incluye segundo corte de teléfono.

Según mi experiencia en reparaciones de mi línea telefónica, este es el algoritmo apropiado para lograr reparaciones medianamente rápidas:

  1. Llamar al 114 (te atiende el contestador automático)
  2. Llamar al 112. Nadie atiende.
  3. Llamar al 114 de nuevo. El contestador automático te informa que tu teléfono será reparado "en los plazos establecidos".
  4. Llamar al 112. Nadie atiende
  5. Llamar al 114 de nuevo. Poner otro número de teléfono. Te atiende el operador.
  6. El operador te corta antes de admitir que te está boludeando porque no puede abrir el ticket de la reparación del teléfono.
  7. Llamar al 112. Nadie atiende
  8. Llamar de nuevo al 114, poner de nuevo un número equivocado. Quejarse que te cortaron en la mitad de la queja anterior. Te explican que en realidad no pueden abrir tu ticket (operador buena onda, probablemente empezó a trabajar hoy).
  9. Llamar al 112. Atienden (¡milagro!). Abrís un ticket quejandote de que no hay plazos por la reparación y porque te cortaron cuando llamaste al 114.
  10. ????????????????????????????????
  11. A los 10' de la última llamada arreglan la línea.
  12. ????????????????????????????????
  13. A las 12 horas se vuelve a quedar sin tono.
  14. Habla mi suegra con Telefónica, dicen que vienen mañana antes de las 10 AM. Alegan que hay un problema en mi casa (¡pero pusieron a andar el teléfono 12 horas sin venir!).
  15. Me dejaron plantado. Segundo fin de semana sin teléfono ni internet.
  16. Llaman a mi suegra, que no está, y le dicen que van a venir cuando no haya nadie en el depto.
  17. Llamo a reparaciones de larga distancia de telefónica (al servicio técnico habitual no me puedo comunicar porque no te atienden cuando hay un arreglo en curso, seguro que esto es antirreglamentario). Por suerte toman los datos del teléfono de la oficina (o eso me hace creer la operadora.
  18. ?????????????????????????????????
  19. Llego a casa a la noche y anda todo (vamos a ver con cuanto tiempo).
Conclusión: Con Telefónica todo mal. Igual, llamen al 112 y rompan las bolas. Puntos extra si van o se quejan en Defensa del Consumidor.

Aureliano.

PD para la gente de codear: En cuanto tenga tiempo quiero hacer un post sobre mónadas en Haskell. Tenganme paciencia.

2008-06-27

Busco hogar para Amón

Estoy buscandole un nuevo hogar a Amón, mi gato. Es macho, super-juguetón, tiene 2 años y está castrado. Vivió siempre en depto, así que está acostumbrado a la compañía humana. A quién le interese ser su nuevo "papá" o "mamá" por favor comuníquese x acá o a aurelianocalvo at gmail.com (cambiando at por @).

Acá abajo pongo una foto así se van encariñando.



Muchas gracias,
Aureliano.

2008-06-22

Grandes ideas

Odio cuando se me ocurren ideas que creo "geniales" y "únicas" para después descubrir que alguien ya lo hizo antes. Esto me pasa todo el tiempo :(. Y hoy pasó de nuevo.
Cuando volvía caminando del fútbol del domingo al medio día se me ocurrió "estaría buenísima una página web que sirva para organizar los partidos de fútbol". Los quilombos de los mails solucionados. Nadie haría mucho laburo (después de todo, la página haría sola toda la coordinación). Así que me puse a hacerla, y mientras la hago chequeo el correo y veo otra página web que es para esto.
Bueno, parece que voy a tener más tiempo para estudiar para el doctorado.

Happy hacking,
Aureliano.

2008-06-18

El lock-out

A veces uno trata de explicar cómo son las cosas, pero viene un humorista y deja todo claro.

2008-06-12

Estudio de noche y Google Analytics

Ayer a la noche (u hoy a la madrugada, en función de lo que quieran pensar) fui como invitado al programa Estudio de Noche en la Radio de la Ciudad. Ahí estuvimos hablando con mi amigo Gustavo Fernandez Walker(el conductor) y mi amigo Martín Gonzalez(el otro invitado) sobre varias cosas, pero principalmente sobre el impacto de la Internet sobre la forma de generar eso a los que los abogados llaman propiedad intelectual. En particular estuvimos hablando del impacto en obras artísticas (y aún más en particular, de obras musicales). También tocamos tangencialmente el tema de los wikis y del desarrollo de software libre. Me divertí haciendo radio, así que probablemente vaya como invitado alguna otra vez.
También le mostré a Gustavo Google Analytics, que le gustó y lo puso en el blog del programa. Gustavo es filósofo y estas instrucciones le sirvieron, así que supongo que le servirán a cualquiera que tenga un blog en blogspot (y cambiandolas un poquito, cualquier página). Acá pego las instrucciones textuales:


Tenés que ir al panel de tu blog -> diseño -> Añadir un elemento de
página -> HTML/Javascript
y poner adentro de eso el cacho de código que te dicen que pongas en
google analytics. En mi caso es:
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "ACA VA UN CODIGO TUYO";
urchinTracker();
</script>
Seguramente debe ser algo parecido en el tuyo. Una vez que tenés eso
agregado, cada vez que alguien se conecte a tu blog se va a bajar ese
código, que es el que hace que google pueda saber quienes se conectan
a tu página (así es en la mía). Es solo copiar el cacho de código en
blog como dice arriba y activar el trackeo en google (si no me acuerdo
mal).

Happy hacking,
Aureliano.

2008-06-08

Buscando hosting para mis proyectos personales

Hola a tod@s,
Estoy buscando un hosting para mi. La idea es poner ahí las cosas que haga para el doctorado (así tengo acceso desde todos lados, versionado y un backup en otro lado) y algún que otro prototipo de página web que se me ocurra (tenía algunos pensados que no hice porque no tenía dónde ponerlos).

Mis requerimientos principales son que tenga svn por ssh y https (para lo del doctorado) y que pueda correr ruby para hacer la página (y seguramente alguna base de datos).

También estaría bueno acceso por ssh para poder tocar todo, que sea barato y que si me paso del ancho de banda diario/ semanal/ mensual corten el site y me manden un mail para ver que hacer (para evitar la quiebra por slashdot). No creo que, al menos al principio, tenga mucho tráfico.

Por favor, cuéntenme cómo les fue con sus proveedores de hosting. Escucho sugerencias.

Happy hacking,
Aureliano.

2008-05-24

Wireless@home

Update: Más configuración por seguridad

Hoy configuré el modem ADSL/router/access point wireless (AKA: "el coso wireless") que me compré en la semana y lo hice andar completo (wireless con WAP2). Es un hermoso ZyXEL PRESTIGE 660R-61C.
Ahora les cuento que cosas tuve que hacer para hacerlo hacer andar.

Paso 1: conexión a speedy


Conecté una notebook con fluxbuntu al "coso wireless" con el cable ethernet que viene en la caja y enchufé el cable de teléfono (que también viene en la caja) entre el coso y la boca de teléfono. Configuré eth0 para que esté en 192.168.1.23 (creo que casi cualquier IP en la red 192.168.1.x debe andar) y me dirigí con un browser a 192.168.1.1. Por suerte le pegué (de re-pedo) y entré al menú de configuración del coso vía web.
Intenté setear de una la conexión pero no funcó :(. ¿Por qué? Los seteos de VPI y VCI correctos para speedy (8 y 35 respectivamente) no los aceptaba. Después de un rato de googleo, encontré la página de speedy donde explica como configurar el coso wireless. Lo único que hice distinto es que cómo ya había cambiado el password de administración desde la parte web, puse mi password nuevo. Seguí las instrucciones, setié como name servers a200.51.211.7 y 200.51.212.7 y ¡se conectó a speedy!

Paso 2: Compartir la conexión entre varias PCs


Con esto andando, el siguiente paso fue compartir la conexión entre 2 PCs. Esto fue re-fácil. Configuré mis 2 notebooks x DHCP y las enchufé al coso wireless con sendos cables ethernet. Una vez que las PCs estuvieron andando, ya tenía conexión a internet :D

Paso 3: El coso wireless es wireless (y se supone que yo sé de seguridad informática)


Bueno, esto ya estaba andando. Entonces me puse a configurar el acceso wireless. En cuanto miré, algún vecino "extra rápido" ya se había conectado a mi coso wireless y estaba leecheando internet. Así que empecé a cerrarlo. Mirando rápido el menú me pareció que había solo WEP, y "this is unacceptable" para un investigador en seguridad informática como yo (chiste). Así que apagué el wireless para que no leecheen más y empecé a mirar la configuración. Al final encontré la parte de WAP, que está en otro menú (parece que la amigabilidad no es parte de los objetivos de diseño del menú administrativo por web del coso wireless). Y empecé a probar.
Para hacer andar la parte wireless tuve un desafío extra, que es que lo hice andar en un Kubuntu Hardy (8.04). Por suerte, cuando compré mi notebook tuve la delicadeza de comprar una placa Intel, por lo que se me hizo más fácil. Después de varias vueltas descubrí que lo mejor era configurar el coso wireless en WPA2 con PSK (pre-shared key). Y solo queda explicar que hice para hacer que ande el Kubuntu. Probé un montón de cosas que no andaban hasta que encontré por internet (no tengo el link acá) que a veces reinstalando algunos paquetes la integración de las placas wireless. Así que hice
sudo aptitude reinstall knetworkmanager wpasupplicant network-manager
y santo remedio. Cargué el KNetwork Manager y cliquié el botón derecho, me conecté a mi red, puse el password que había configurado en el párrafo anterior y anduvo todo joya.

Paso 4: Ajustes finales


Por último, quiero seguir pudiendo conectarme a bit torrent bien para "bajar ISOs de Linux". Así que configuré que la MAC de mi notebook tenga IP fija (está en la parte de LAN del menú avanzado) y forwardié los puertos correspondientes (TCP y UDP) que tengo configurados en el KTorrent.
Y para que sea más seguro (mirando los ataques que postulan en GNU Citizen) le configuré distinto el SNMP. Me conecte x telnet al coso y cambié las opciones del menú 22, que quedó algo así:

SNMP:
Get Community= [passwd nuevo]
Set Community= [otro passwd nuevo]
Trusted Host= 0.0.0.0
Trap:
Community= [y otro pass más]
Destination= 0.0.0.0

En una mirada rápida, me pareció que el resto de los ataques requieren que el atacante esté conectado en la red interna, así que los dejo para después (supongo que requerirán un cambio de firmware).

Así llegué a la configuración que quería y me dediqué a escribir este post.
Happy hacking,
Aureliano.

2008-05-03

El último pelo

Este hilarante blog habla de los calvos. Sus problemas y soluciones. Envidias y resentimientos. Gracias y tristezas. Recomiendo ampliamente a este blog, que es el único que conozco que tiene su propia canción. En fin, tómense unos minutos y ríanse un rato. Yo, como buen calvo que soy, lo recomiendo.

Happy hacking,
Aureliano.

2008-05-02

Load balancer minimalista en ruby (parte 2)

En el post anterior mostré un load balancer que implementé en ruby. Ahora le agregué algo de manejo de errores y algo de fail-over para que sea más estable.

Este es el código nuevo:


#!/usr/bin/env ruby
require 'socket'

def usage
puts "Very simple balancer"
puts "balancer.rb port host1:port1 [host2:port2 ....]"
exit
end

def build_targets(argv)
argv.map do
|param|
result = param.split(":")
result[1] = result[1].to_i
result
end
end

class Balancer
def initialize(port, *targets)
@descriptors = []
@next_step = {}

@serverSocket = TCPServer.new( "", port )
@serverSocket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
puts("Balancer started on port #{port}")

@descriptors << @serverSocket

@targets = targets
@current_out = 0
end

def next_out
out_socket = nil
until out_socket
begin
@current_out = (@current_out + 1) % @targets.length
descr = @targets[@current_out]
out_socket = TCPSocket.new(descr[0], descr[1])
rescue
# do nothing on purpose
end
end
out_socket
end

def accept_new_connection

incoming = @serverSocket.accept
outgoing = next_out

@next_step[incoming] = outgoing
@next_step[outgoing] = incoming

@descriptors += [ incoming, outgoing ]
end

def propagate(sock)
next_sock = @next_step[sock]
next_sock.write(sock.read_nonblock(1000 * 1000))
end

def finish_connection(sock)
next_sock = @next_step[sock]
[sock, next_sock].each do
|s|
begin
s.close
rescue Object => e
puts "Error closing socket: #{e.inspect}"
end
@descriptors.delete(s)
@next_step.delete(s)
end
end

def run
loop do
connections = select( @descriptors )
if connections
connections[0].each do
|sock|
if sock == @serverSocket then
accept_new_connection
else
begin
if sock.eof? then
finish_connection(sock)
else
propagate(sock)
end
rescue
finish_connection(sock)
end
end
end
end
end
end
end

trap("SIGINT") do
exit
end

usage if ARGV.length < 2
port = ARGV.shift.to_i
targets = build_targets(ARGV)

Balancer.new(port, *targets).run


Me parece que ahora pueden llegar a usarlo.

Happy hacking,
Aureliano.

2008-05-01

Load balancer minimalista en ruby

Update: Puse la versión más nueva en este post.

Ayer estuve inspirado y dediqué un poquito de mi tiempo a aprender un poco más sobre cómo manejar sockets. Hacía mucho tiempo que no programaba sockets en bajo nivel y nunca había necesitado usar select para manejar muchos al mismo tiempo. De hecho, la última vez que tuve que hacer algo parecido a un server que acepte muchas conexiones TCP lo hice en java 1.3, y en java 1.3 no hay select (agregaron algo parecido en java.nio, que apareció después).
Así que puse manos a la obra e hice un minicloncito de un load balancer en ruby.
Por supuesto que no hice todo. En particular, me comí el manejo de errores. Pero si hace round robin de conexiones y tiene menos de 100 líneas de código, lo que creo que lo hace un buen ejemplo de juguete sobre como manejar sockets en ruby.
Bueno, basta de cháchara, acá va el código completo:


#!/usr/bin/env ruby
require 'socket'

def usage
puts "Very simple balancer"
puts "balancer.rb port host1:port1 [host2:port2 ....]"
exit
end

def build_targets(argv)
argv.map do
|param|
result = param.split(":")
result[1] = result[1].to_i
result
end
end

class Balancer
def initialize(port, *targets)
@descriptors = []
@next_step = {}

@serverSocket = TCPServer.new( "", port )
@serverSocket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
puts("Balancer started on port #{port}")

@descriptors << @serverSocket

@targets = targets
@current_out = 0
end

def next_out
@current_out = (@current_out + 1) % @targets.length
@targets[@current_out]
end

def accept_new_connection
out_descriptor = next_out

incoming = @serverSocket.accept
outgoing = TCPSocket.new(out_descriptor[0], out_descriptor[1])

@next_step[incoming] = outgoing
@next_step[outgoing] = incoming

@descriptors += [ incoming, outgoing ]
end

def propagate(sock)
next_sock = @next_step[sock]
next_sock.write(sock.read_nonblock(1000 * 1000))
end

def finish_connection(sock)
next_sock = @next_step[sock]
[sock, next_sock].each do
|s|
s.close
@descriptors.delete(s)
@next_step.delete(s)
end
end

def run
loop do
connections = select( @descriptors )
if connections
connections[0].each do
|sock|
if sock == @serverSocket then
accept_new_connection
else
if sock.eof? then
finish_connection(sock)
else
propagate(sock)
end
end
end
end
end
end
end

trap("SIGINT") do
exit
end

usage if ARGV.length < 2
port = ARGV.shift.to_i
targets = build_targets(ARGV)

Balancer.new(port, *targets).run

Para usarlo, copien todo el codigo a un archivo (como balancer.rb) y fijense en el cartel que explica los parámetros. El primer parámetro es el puerto donde escucha y los otros son pares host:port a los que tiene que conectarse alternativamente.

Bueno, eso es todo por ahora.

Happy hacking,
Aureliano.

2008-04-20

Reconstrucción del ligamento cruzado anterior de la rodilla derecha - Epílogo

Como estuve contando hace un tiempo por acá hace un poco más de un año y medio me operaron para reconstruir el ligamento cruzado anterior de mi rodilla derecha, que fue obliterado en una de mis tantas caídas que tengo cuando esquío (en realidad fue la última). Ahora dejé de esquiar, más que nada por falta de $$$$, y me reincorporé al resto de las actividades usuales de mi vida (incluyendo volver a jugar al "fútbol" los domingos).

Aunque hace muuuucho tiempo que no escribo nada de esto, sigue habiendo gente que se interesa en estos posts de mi blog y, extrañamente, si buscan reconstrucción de ligamentos en google este blog aparece en la primera hoja, me pareció pertinente contarles como fue mi recuperación y como llegué a estar 100% activo deportivamente nuevamente.

Así que les cuento.

Septiembre 2006


Me operaron, pueden ver los detalles en este blog.

Octubre 2006-Marzo 2007


A los 20 días de la operación (aprox) el traumatólogo me dijo "ahora tenés que hacer rehabilitación". La rehabilitación es larga (dura unos 6 meses) y laboriosa (hay que ir 3 veces x semana como 2 horas) pero si hacen las cosas bien lo más probable es que queden bien. Así que me puse las pilas.
Al principio era la típica kinesiología que pensás que es medio al dope donde te ponen esos aparatos que hacen "magnetoterapia", que son como unos cilindros por donde pasas la pierna y están enchufados a un aparato con un timer, que hace ruido cuando termina. También te torturan un poquito haciendo estimulación eléctrica de los cuádriceps de la pierna operada. Media hora y despachás.
A las 2 semanas decidieron que era hora que hiciera pesas y estuve haciendo diversos ejercicios pero solo con la pierna rota, previa magnetoterapia. Todo el chiste me llevaba como 2 horas cada vez, un embole. Y encima en el gimnasio no había una mina que estuviera buena. Solamente un montón de tipos recuperandose de roturas de ligamento cruzado anterior y viejos con dolencias variadas :(.
Mientras pasaba el tiempo me fueron agregando ejercicios y subiendo el peso de los mismos hasta que idearon otra forma de torturarme: estimulación eléctrica del cuádriceps mientras hacía pesas.

¡A la mierda!

Eso si que es feo. Aparte te tiran voltajes grosos. La máquina la usaban entre ¡80 y 120 volts! (posta) y parecía que me iban a electrocutar toda la pierna. Pero cumplió su objetivo.
Al final, me agregaron ejercicios de correr de costado y haciendo secuencias raras de paso y después de un verano agotador, me dieron el alta.

Abril 2007-Presente


Me dieron el alta pero todavía no estaba bien, bien. Me faltaban los últimos detallecitos. Así que todavía me dolía la rodilla de vez en cuando y se me inflamaba cada vez que hacía movimientos raros. Aparte mi sobrepeso tampoco ayudó mucho.
En mayo entré a laburar a mi laburo actual y hubo 2 actividades que me hicieron mierda la rodilla:

  1. Día de actividades "corporativas" de la empresa. Donde nos llevaron al Club de Amigos a que nos lavaran el cerebro haciéndonos creer que la empresa es lo más importante del universo. La actividad fue una mierda, pero había un castillo inflable que estaba buenísimo, pero que fue la perdición de mi rodilla.

  2. Basket. Esa misma semana fui a jugar al basket en cancha de cemento.


El combo fue letal. Estuve rengo por 3 semanas, me asusté, fui al traumatólogo. Me dijo que me haga una radiografía y lo vea de nuevo. Cuando fui a verlo de nuevo con la radiografía, empezó a sacarle fotos porque le parecía que había hecho una obra de arte con mi rodilla y estaba muy orgulloso, me dijo que no haga más boludeces y que estaba bien y se fue a un congreso con las fotos de mi rodilla.
Así que esperé a que se terminara de desinflamar la rodilla y empecé a hacer "deporte de bajo impacto". O sea, volví a nadar. La natación me vino bárbaro y mi rodilla se terminó de mejorar. A mitad de año pusieron clases de yoga en el laburo y empecé a ir (y sigo yendo). Así estoy terminando de recuperar la flexibilidad.
Por último, hará un mes que volví a las canchas de fútbol 5. No se nota ninguna secuela de la lesión, salvo que ahora juego con una rodillera puesta. Mi habilidad está intacta y hasta estoy jugando un poquito mejor que antes. Igual eso no quiere decir mucho, era desastroso y lo sigo siendo.
O sea, si se rompieron la rodilla, no sean boludos, opérense. Es mucho laburo pero pueden volver a las cosas que hacían antes. Una cosita más. Lo que me dijeron los médicos es que si no se operan y tienen los ligamentos rotos, van a tener una artitris grosa en unos años.
Espero que les sea de utilidad a todos los googleros que llegan por acá buscando información sobre la reconstrucción de los ligamentos.

Happy hacking,
Aureliano.

2008-04-02

Si PHP es KK, ¿por qué lo usan tanto?

Hace unos meses dije que PHP es KK en un post de este blog que generó bastante polémica (al menos en los comentarios del post). Y esa discusión me puso a pensar "¿qué cosas buenas tiene PHP?". Claramente, mucha gente lo usa y sirve para hacer un montón de cosas. Bueno, estas son algunas de las cosas que hacen que php y su entorno sean buenos (sin ningún orden en particular):

  • Está en todos lados. Es muy fácil conseguir hosting con php y gente que sepa programar en php. También hace que haya millones de soluciones para pequeños problemas disponibles. Y un montón de gente muy inteligente haciendo soluciones para problemas de php como ésta o ésta.
  • La documentación de php es buenísima. Siempre que busqué cosas ahí las encontré de toque.
  • El entorno es fácilmente instalable. En linux, generalmente viene con la distribución y en Windows es solo cuestión de bajar e instalar un WAMP.
  • El ciclo de desarrollo es muy rápido. Nada de compilar. Nada de reiniciar ningún server. Cambiás un toque un script y ves el resultado inmediatamente.
Estos son todos buenos motivos. Algunos de estos solo valen porque php ya es popular, pero otras no. En particular, el manejo de la documentación de php es algo que es buenísimo.

Happy hacking,
Aureliano.

2008-03-31

Mañana empiezo el doctorado

Mañana a las 9:30 empiezo a cursar mi primera materia del doctorado en ingeniería informática en el ITBA, que es "Teoría de Tipos". Así que voy a estar más ocupado. De todas maneras voy a tratar de mantenerme escribiendo algunas cositas en el blog de vez en cuando.

Deseenme éxitos,
Aureliano.

2008-03-09

Clases abiertas - Ejemplo práctico

Estoy mirando una biblioteca para acceder a bases de datos muy copada que se llama sequel. En la misma no hay que declarar nada y el mapeador solo sabe como hacer para acceder a todo (como debería ser). Y quise ver como hace el sql que va a la base, así que prendí el irb e hice un poquito de "aspect programming alla ruby". Primero importé la biblioteca y el driver para postgres y les agregué datos (todo usando la biblioteca sequel desde adentro del irb y sin escribir una línea de SQL).

Pero después me agarró la duda de cómo se generaba el SQL. ¿Tendría quizás alguna forma de hacer un SQL injection si uso la biblioteca para hacer una página web? Lo que siempre me gustó de bibliotecas como ActiveRecord e Hibernate, es que me quitan la responsabilidad por estas cosas. Así que puse manos a la obra.

Una mirada rápida por la documentación y el código no me llevó a la opción de mostrar todo el sql que se ejecuta, así que tuve que hacer un toque de magia.


irb(main):042:0> class Sequel::Postgres::Dataset
irb(main):043:1> alias_method :select_sql_old, :select_sql
irb(main):044:1> def select_sql(*p)
irb(main):045:2> sql = select_sql_old(*p)
irb(main):046:2> puts(sql)
irb(main):047:2> sql
irb(main):048:2> end
irb(main):049:1> end


¿Qué hice? Hice "aspect programming alla ruby". Cambié cómo se hace para generar el sql de las consultas en postgres para que lo muestre por pantalla (solo en mi sesión de irb).

A partir de este momento (y hasta que lo vuelva atrás o salga del irb) me muestra el SQL de cada SELECT que haga a la base.

Así que ahora puedo mirar fácil que pasa si trato de inyectar SQL:

irb(main):053:0> user_name = "aaaa ' or 1=1"
=> "aaaa ' or 1=1"
irb(main):054:0> users.where(:name => user_name).all
SELECT * FROM users WHERE ("name" = 'aaaa '' or 1=1')
=> []


Soy feliz, y veo que la biblioteca parece estar bien pensada (al menos para esto).

Happy hacking,
Aureliano.

2008-03-08

Nueva versión del bottle invaders

Estoy muy contento de anunciar la nueva versión del "Bottle invaders", en la cual el Señor Destapador (AKA: Mr Opener) defiende su planeta de una invasión de botellas extraterrestres rechochas de vino tinto.

En este release, le bajé los fps a 5 para que se parezca al space invaders original un poco más (y porque me pareció que queda más divertido) y cambié la lógica de colisiones del misil para que sea consistente con esa cantidad de fps.

Si quieren bajarselo está disponible acá.

Happy hacking,
Aureliano.

2008-03-07

Requisitos renovación pasaporte

Hoy fui a renovar mi pasaporte y hacer mi cédula nueva (la anterior la perdí). No pude hacerlo porque me faltó un papel (/$%/#/$&%***+++###). Eso no impidió que me coma como 2 horas de cola al dope, así que les voy a contar cuáles son mis requisitos, que no tienen porqué ser los de ustedes (la burocracia suele ser así).
Cosas para llevar:

  • Pasaporte viejo (si no tenés eso podés declararlo perdido, creo)
    • Fotocopias de la 1a y 2a hoja)
  • DNI
    • Fotocopias 1a, 2a, 3a hojas y donde están tus votaciones.
  • Libreta de casamiento si estás casado (esto es lo que yo no llevé y no me pidieron hasta 2 horas después de iniciar el trámite).
Las fotocopias las podés sacar durante el tiempo de espera, pero los documentos los tenés que llevar sí o sí.

Lo único bueno es que durante la espera pude scriptear con rake para que se armen el .tar.gz y el .zip del Bottle invaders.

Happy hacking,
Aureliano.

2008-03-04

Bottle invaders - primer release

Hola,
acabo de subir a rubyforge el primer release del Bottle Invaders. En el mismo muestro al Señor Destapador luchando contra unas botellas rechonchas de vino que le disparan corchos. Este juego también lo hice usando la biblioteca Rubygame. Si quieren bajarlo pueden ir acá

Y por último, como una imagen vale más que mil palabras, les dejo un screenshot del juego en pleno juego.



Happy hacking,
Aureliano.

2008-03-03

Problemitas con speedy

Empecé a notar que speedy está andando muy "lento", pero que las descargas largas andan bien. Así que dejé un pinga google corriendo un rato y estos son los resultados:


$ ping www.google.com

(un monton de carteles con los round-trips)

--- www.l.google.com ping statistics ---
1927 packets transmitted, 1679 received, 12% packet loss, time 4652040ms
rtt min/avg/max/mdev = 346.690/969.515/1856.932/147.656 ms, pipe 2


Aparentemente está algo muy saturado porque se pierden el 12% de los paquetes y hay round trips de 2 segundos (¡y de promedio es casi un segundo!)

¿Les está pasando lo mismo?
¿Tengo que unirme a speedy apesta?

Happy hacking,
Aureliano.

2008-03-01

Un adelantito

Como ustedes ya saben, empecé a hacer jueguitos usando la biblioteca Rubygame. Como el primer juego ya está (SEUO), empecé el segundo. Es un clon del space invaders, que muestra al Señor Destapador defendiendonos de una forma muy particular de unos invasores que son como botellas de vino.
Como una imagen vale más que mil palabras, acá les muestro un snapshot:

Happy hacking,
Aureliano.

2008-02-25

Compilando LaTeX con rake

En el laburo estoy escribiendo un paper (es parte de las cosas que tengo que hacer como investigador en seguridad informática) y, como soy un geek que se precia, lo estoy escribiendo en LaTeX.

El temita es que las figuras que tiene el paper las hice en MS Visio y que quiero tener un script automático para generar el .dvi a partir del .tex, el .bib y los archivos de Visio. Pero LaTeX no acepta archivos de Visio como imágenes, solo .eps (encapsulated postscript) y Visio a lo sumo escupe .wmf. Así que instalé el wmf2eps y entonces tuve una idea, ¿por qué no generar todo usando rake y uso win32ole para hacer automation con el Visio?.

Este es el resultado (que anda) del rakefile que uso para generar el paper:


require 'rake/clean'
require 'win32ole'

VIS = FileList['*.vsd']
EPS = VIS.ext('eps')
MAIN = "paper.tex"
DVI = "paper.dvi"
BIB = "paper.bib"

CLEAN.include(FileList["*.wmf", "*.aux", "*.bbl", "*.blg", "*.log"] )
CLOBBER.include(FileList[DVI], VIS.ext("eps"))

def windows_name(file)
File.expand_path(file).gsub("/","\\").gsub(" ", "\\ ")
end

def visio()
begin
return WIN32OLE.connect("visio.application"), false
rescue WIN32OLERuntimeError # Visio is not running
return WIN32OLE.new("visio.application"), true
end
end

def open_doc(visio, filename)
begin
return visio.Documents(filename), false
rescue WIN32OLERuntimeError # file not open
return visio.Documents.open(filename), true
end
end

def latex
sh "latex", "-halt-on-error", MAIN do
|ok, res|
unless ok
puts "Error compiling LaTeX file (status=#{res.exitstatus})\n"
exit
end
end
end

def bibtex
sh "bibtex", "paper" do
|ok, res|
unless ok
puts "Error compiling bibtex file (status=#{res.exitstatus})\n"
exit
end
end
end

file DVI => EPS + [MAIN, BIB] do
latex
bibtex
latex
latex
end

rule '.wmf' => '.vsd' do
|t|
visio_filename = windows_name(t.source)
wmf_filename = windows_name(t.name)
visio, close_visio = visio()
document, close_document = open_doc(visio, visio_filename)
document.Pages(1).Export(wmf_filename)
document.Close if close_document
visio.Quit if close_visio
end

rule '.eps' => '.wmf' do
|t|
wmf_filename = windows_name(t.source)
dirname = windows_name( File.dirname(t.source) )
sh "wmf2eps\\wmf2eps -o #{dirname} #{wmf_filename}"
end

desc "Generates dvi for paper"
task :dvi => DVI

desc "Generates images for paper from Visio files"
task :images => EPS

desc "Generates images for paper from Visio files"
task :default => :images


Espero que les sirva esto, y que ruby se vaya acercando a la academia.

Happy hacking,
Aureliano.

2008-02-24

Mr Opener en rubyforge

Estoy muy contento de anunciar que el código de Mr Opener (anunciado en el post anterior) está disponible para todos los que lo quieran en rubyforge.

La página del proyecto es http://rubyforge.org/projects/mropener/.

Espero a futuro:

  • Hacer un gem con este programa
  • Hacer otro programita, que va a ser un clon del space invaders pero usando al Señor Destapador como personaje principal.
  • Seguir con más programas con el Señor Destapador como personaje principal.

Happy hacking,
Aureliano.

Mr Opener

Ultimamente estuve instalando Rubygame en mi PC.

Una vez que lo logré, puse manos a la obra. Así que me copé y en menos de una semana escribí un jueguito para mi sobrina. El mismo es una copia del Potato Guy, que viene con el KDE.

Como protagonista del juego puse al Señor Destapador. Al señor destapador hay que vestirlo, armarle la cara, y darle cosas para que tenga.

Acá les muestro un snapshot del programa andando.



Estoy tramitando un proyecto en rubyforge para hostear el proyecto y lo voy a licenciar con la GPL, ya que Rubygame es LGPL y no quiero tener quilombos de licencia al dope.

Les cuento que el código al final quedó bastante chiquito y fácil de leer, y que lo que más me costó fue editar las imágenes para que encajaran (más o menos) en el juego.

Hasta la próxima y happy hacking.

PD: Me olvidaba, si a alguno de ustedes les interesa tener el código fuente antes de que lo publique en ruyforge, no tienen más que pedirmelo.

2008-02-17

Rubygame en ubuntu, no hay 2 sin 3

Todavía hay más quilombos para hacer correr rubygame en kubuntu gutsy. La demo de sonido no anda y da este error:


$ ./demo_music.rb
ALSA lib pcm_dmix.c:864:(snd_pcm_dmix_open) unable to open slave
./demo_music.rb:19:in `open_audio': Error initializing SDL_mixer:
No available audio device (Rubygame::SDLError)
from ./demo_music.rb:73:in `join'
from ./demo_music.rb:73

Para arreglarlo hay tuve que desinstalar la biblioteca libpulse0, como sugieren en este post.

Happy hacking.

Rubygame en ubuntu, más soluciones

En el post anterior mostré como solucioné un problema que me impedía usar la biblioteca rubygame en mi kubuntu. Bueno, ese no es el único problema. Cuando traté de correr la demo de rubygame me daba otro error:


$ ./demo_rubygame.rb
./demo_rubygame.rb:28: SDL_gfx is not available. Bailing out. (RuntimeError)


Así que puse manos a la obra. El problema es que la versión de la biblioteca SDL_gfx que viene con ubuntu es vieja. En ubuntu gutsy viene la versión 1.2 y rubygame requiere la 2.0. Así que desinstalé la versión que viene con ubuntu, me bajé la última versión de acá y la instalé. La instalación es medio "tricky" porque la típica ./configure;make;make install no anda. Así que tuve que mirar el archivo INSTALL. Para ahorrarles el laburo de mirarlo ustedes, les cuento acá. Para instalarlo tienen que hacer:

$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
$ sudo ldconfig
$ make distclean


Una vez que todo esto funcó, desinstalé y reinstalé el gem de rubygame y me anduvo la demo.

Bueno, espero que no haya más quilombos.

Happy hacking.

Rubygame en ubuntu

Estoy con ganas de probar rubygame porque tengo ganas de programar un poco en ruby (en el laburo me obligan a programar en python y php). Rubygame es la versión para ruby de pygame.

Pero me encontré con un problemita con el que quizás se encuentren ustedes también. Tengo kubuntu 7.10 (gutsy) y la instalación de ruby de ubuntu (al menos en esta versión) hace que el shared object de ruby tenga un nombre distinto del standard. Por eso la librería de rubygame no lo encuentra.

Este es un ejemplo del problema:


$ irb -rubygems -rrubygame
/usr/lib/ruby/gems/1.8/gems/rubygame-2.2.0-x86-linux/ext/rubygame/rubygame_core.so:
libruby18.so.1.8: cannot open shared object file: No such file or directory -
/usr/lib/ruby/gems/1.8/gems/rubygame-2.2.0-x86-linux/ext/rubygame/rubygame_core.so (LoadError)
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from /usr/lib/ruby/gems/1.8/gems/rubygame-2.2.0-x86-linux/lib/rubygame.rb:25
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:32:in `gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:32:in `require'
from /usr/lib/ruby/1.8/irb/init.rb:252:in `load_modules'
from /usr/lib/ruby/1.8/irb/init.rb:250:in `each'
from /usr/lib/ruby/1.8/irb/init.rb:250:in `load_modules'
from /usr/lib/ruby/1.8/irb/init.rb:21:in `setup'
from /usr/lib/ruby/1.8/irb.rb:54:in `start'
from /usr/bin/irb:13


Para arreglarlo, hay que hacer un link más en /usr/lib así: $ sudo ln -s libruby1.8.so libruby18.so.1.8.

La pista de como emparchar mi sistema me lo dio este post en la lista de ruby.

Aparentemente, es un bugcito de rubygame, pero no estoy del todo seguro.

Bueno, hasta la próxima y happy hacking.

2008-02-13

Un hecho, dos noticias

Esto lo encontró Natalia, una gran amiga mía.

La noticia, agarraron a un represor de la última dictadura militar, que boleteó a 19 personas. Comparen por ustedes mismos y miren.
Esta es la versión de La Nación y esta la de Página 12.

La verdad, indignante.

2008-02-10

No vayan al Hostal Azul (Humahuaca)

Hola,
Les cuento porqué. La habitación que me dieron tenía un olor a cigarrillo insoportable, impregnado por todos lados. Se notaba que no lavaron bien las sábanas ni la colcha y no me cambiaron el colchón aunque se los pedí explícitamente. Así que en plena temporada alta tuve que salir en una gira desesperada por Humahuaca para conseguir otra habitación en otro hotel. Por suerte conseguí una sin ese olor asqueroso que tenía la del hotel. Otro motivo para no ir es que tardan 45 minutos (aproximadamente) en servir el desayuno (medido con reloj, promediado en los 3 desayunos que estuvimos ahí).

Repito. No vayan al Hostal Azul en Humahuaca, Jujuy, Argentina.

Igual vayan a Humahuaca que está buenísimo, y si pueden haganse una escapada a Iruya.

2008-01-04

Nuevo blog

José (AKA: Termo) presenta su nuevo blog. En su segundo artículo (el primero es un test del blog) habla sobre como calcular el page rank en python. Espero que les guste.

2007-12-30

Dual head en mi notebook

Estoy muy contento de como quedó la configuración de la pantalla de mi Inspiron 1520.

Después de un toque de googleo, encontré un post donde explican cómo configurar una notebook con placa Intel para que soporte 2 monitores (el de la notebook y otro más) en KUbuntu Gutsy. Así que seguí los pasos (que son bastante simples) y los retoqué un bati-toque (santas redundancias batman) para que se configure solo.

Primero agregué a la subsección "Screen"/"Display" del archivo la entrada:

virtual         2560    1024

La onda de esta entrada es que el virtual desktop alcance para los 2 monitores.

Después me agregué en $HOME/bin 2 scripts. Uno para activar el segundo monitor y configurarlo bien y otro para desactivarlo. El que activa y configura el segundo monitor (dual-head) es:
#!/usr/bin/env bash

xrandr --output VGA --auto
xrandr --output VGA --right-of LVDS


El que la apaga es:
#!/usr/bin/env bash

xrandr --output VGA --off


Por último, linkié en ~/.kde/Autostart el script dual-head, así se setea automágicamente la dualidad onda-monitor. Si el monitor no está enchufado, el script no hace nada, así que lo dejé así:
ln -s ../../bin/dual-head


Así que ahora tengo todo listo y cuando estoy en casa puedo usar mi otro monitor también.

2007-12-24

Papá Noel

Ayer a la noche hice de Papá Noel en el complejo de departamentos en el que vivo. Fue un quilombo de niñ@s y adult@s. Impresionante el efecto que genera el disfraz de Papá Noel en la gente. L@s chic@s se me quedaban mirando (se quedarían preguntándose, ¿es de verdad?). En el evento repartí bolsitas con regalitos tontos para l@s chic@s y recibí algunas cartitas. Entre las cartas que recibí está la de mi sobrina.


Y acá está una foto con ella (que nunca supo que Papá Noel era yo) y de fondo el batifondo (santas redundancias batman) del evento.

La verdad, lo disfruté mucho.

2007-12-17

PHP es KK. Un ejemplo práctico

Hoy mi amigo Ernesto Alvarez me mostró un pequeñísimo ejemplo de porqué PHP es una mierda. La semántica del lenguaje deja muuuucho que desear.

El programa:

<?php
echo count == 0;# . "\n";
?>


Muestra "1" en la salida por standard output.

En cambio si dejamos el enter comentado no muestra nada. Este es el código que no muestra nada:

<?php
echo count == 0 . "\n";
?>


Bueno, una cosa más para hacer leña del árbol caído.
Hasta la próxima, amigos.

Update: Escribí una segunda parte de esto en mi artículo Si php es KK, ¿por qué lo usan tanto?.

2007-11-28

51 x 10^9 (parte 2)

En mi mi último post, mostré un problema que usan en ITA para reclutar gente. Parece que anda mal porque el resultado que encuentra no coincide con el fin de un número, como dice en el enunciado. Pero como me quedé desvelado una noche haciendolo, les mandé un mail con mi solución. Mientras tanto aprovecho y les pregunto a ustedes.

¿Le ven algún quilombo al código de abajo?


#!/usr/bin/env ruby

class Result
attr_accessor :letters_advanced
attr_accessor :letters_missing
attr_accessor :sum
attr_accessor :number
attr_accessor :name

def initialize(letters_missing=0)
self.letters_missing = letters_missing
self.sum = 0
self.letters_advanced = 0
self.number = -1 # not set
end

def << (other_result)
self.letters_advanced += other_result.letters_advanced
self.sum += other_result.sum
self.letters_missing -= other_result.letters_advanced
self.number = other_result.number
self.name = other_result.name
end

def to_s
string = "name:#{name} - number:#{number} - " +
"letters_advanced:#{letters_advanced} - " +
"letters_missing:#{letters_missing} - sum:#{sum}"
string += " - letter:#{name[name.length-1+letters_missing,1]}" if letters_missing <= 0
end
end

class Block

attr_reader :value
attr_reader :name
attr_accessor :next_block
attr_reader :position # 0 for ones, 1 for thousands, 2 for millions

def initialize( name, number, position )
@name = name
@value = number * (1000 ** position)
@position = position
end

# Inserts a block somewhere after this block, hopefully close (if not, takes a long time)
def insert_block(new_block)
before = self
after = self.next_block
while( after != nil && new_block.name > after.name )
before = after
after = before.next_block
end
before.next_block = new_block
new_block.next_block = after
end

def advance(min_position, max_position)
current = self.next_block
while !current.nil? && !current.position.between?(min_position, max_position)
current = current.next_block
end
current
end

ONES = [ "", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" ]
TEENS = ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
"seventeen", "eighteen", "nineteen" ]
ORIG_TENS = [ "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" ]

tens = Array.new
ONES.each { |n| tens << n }
TEENS.each { |n| tens << n }
ORIG_TENS.each do
|ten|
ONES.each do
|one|
tens << ten + one
end
end

TENS = tens

HUNDREDS = ONES.map do
|hundred|
root = hundred == "" ? "" : hundred + "hundred"
numbers = tens.map do
|ten|
root + ten
end
numbers
end.flatten

number = 0
NAMED_HUNDREDS = HUNDREDS.map do
|name|
value = { :number => number, :name => name }
number += 1
value
end.sort_by { |pair| pair[:name] }.reverse

last = nil
NAMED_HUNDREDS.each do
|pair|
new = Block.new(pair[:name], pair[:number], 0)
new.next_block = last
last = new
end

@last = last.next_block # Ignore zero

#Add higher level blocks (thousand and millions)
current = last
while( current != nil )
if current.position == 0 then
current.insert_block( Block.new(current.name + "thowsand", current.value, 1) )
current.insert_block( Block.new(current.name + "million", current.value, 2) )
end

current = current.next_block
end

def self.first( position=0 )
first = @last
while (first.position != position)
first = first.next
end
first
end
end

class Number

def initialize( pos2, pos1, pos0 )
@blocks = [pos0, pos1, pos2]
end

def name
@blocks.reverse.inject("") do
|acum, block|
acum += block ? block.name : ""
end
end

def value
@blocks.inject(0) do
|acum, block|
acum += block ? block.value : 0
end
end

def description
r = Result.new
r.letters_advanced = self.name.length
r.name = self.name
r.number = r.sum = self.value
r
end

def next_number(min_pos=0)
max_pos = min_pos + 1
while @blocks[max_pos].nil? && max_pos < 3
max_pos += 1
end
max_pos -= 1
new_block = @blocks[min_pos] ? @blocks[min_pos].advance(min_pos, max_pos) : Block.first(min_pos)
@blocks[min_pos] = nil
if (new_block) then
@blocks[new_block.position] = new_block
else #carry
self.next_number(min_pos + 1)
end
self
end

unit_letter_count = 0
unit_accumulated_values = 0
block = Block.first(0)
while( block )
unit_letter_count += block.name.length
unit_accumulated_values += block.value
block = block.advance(0,0)
end
UNIT_LETTER_COUNT = unit_letter_count
UNIT_ACCUMULATED_VALUES = unit_accumulated_values

def step(result)

if (@blocks[1] && !@blocks[0])
big_step_char_count = self.name.length * 1000 + UNIT_LETTER_COUNT
if big_step_char_count < result.letters_missing then
step_result = Result.new
step_result.letters_advanced = big_step_char_count
step_result.number = step_result.sum = self.value * 1000 + UNIT_ACCUMULATED_VALUES
result << step_result
self.next_number(1)
return
end
end
# Big step is not taken
result << self.description
self.next_number
end

def self.first
self.new( nil, nil, Block.first )
end

def self.result( letter_position)
result = Result.new(position)
number = Number.first
while( result.letters_missing > 0 )
result << number.description
number=number.next_number
end
result
end

end


position = ARGV[0].to_i
result = Result.new(position)
number = Number.first
count = 0
while( result.letters_missing > 0 )
number.step(result)

count += 1
if (count % 100000) == 0 then
puts "Partial result"
p result
end
end

puts "Final result"
puts result

2007-11-25

51 x 10^9

Como estuve con un poquito de tiempo libre me puse a programar un toque este sábado a la noche (sí que soy divertido :p). Y encontré un problemita intereseante en el blog de una .com. El problema es el mismo que proponen en ITA para tomarte como programador.

El mismo consiste en saber cuál es la letra número 51 x 10^9 (o sea 51000000000) de la secuencia de agarrar todos los números entre 1 y 1000000000 y transformarlos en letras, saber a que número pertenece y cuanto vale la suma de todos los números por los que pasaste para llegar a esa letra desde el principio. La solución del problema es un bardo, así que no lo hice todo, pero escribí un programa para obtener la letra en la posición x en la secuencia de números que va del 1 al 1000, a qué número pertenece y cuál fue la suma acumulada hasta ahí.

Acá les pongo a continuación el enunciado del problema:

"If the integers from 1 to 999,999,999 are written as words, sorted alphabetically, and concatenated, what is the 51 billionth letter?"

To be precise: if the integers from 1 to 999,999,999 are expressed in words (omitting spaces, 'and', and punctuation[1]), and sorted alphabetically so that the first six integers are

  • eight
  • eighteen
  • eighteenmillion
  • eighteenmillioneight
  • eighteenmillioneighteen
  • eighteenmillioneighteenthousand
and the last is
  • twothousandtwohundredtwo
then reading top to bottom, left to right, the 28th letter completes the spelling of the integer "eighteenmillion".

The 51 billionth letter also completes the spelling of an integer. Which one, and what is the sum of all the integers to that point?

[1] For example, 911,610,034 is written "ninehundredelevenmillionsixhundredtenthousandthirtyfour"; 500,000,000 is written "fivehundredmillion"; 1,709 is written "onethousandsevenhundrednine".

Y este es mi programa en ruby. Una generalización trivial para correrlo con los números que vayan desde 1 a 1000000000 requeriría (según estimo) unos 100GB de espacio libre en el disco rígido, varios días de tiempo de procesamiento para correr e implementar una rutina de external sorting. Pero me parece que los números tienen suficiente estructura como para cortar camino, así que si se me ocurre algo mejor lo postiaré por acá también.



#!/usr/bin/env ruby

class Result
attr_accessor :letters_advanced
attr_accessor :letters_missing
attr_accessor :sum
attr_accessor :number

def initialize(letters_missing=0)
self.letters_missing = letters_missing
self.sum = 0
self.letters_advanced = 0
self.number = -1 # not set
end

def << (other_result)
self.letters_advanced += other_result.letters_advanced
self.sum += other_result.sum
self.letters_missing -= other_result.letters_advanced
self.number = other_result.number
end
end

class Hundred

attr_reader :value
attr_reader :name
attr_reader :next_number

def initialize( name, value, next_number )
@name = name
@value = value
@next_number = next_number
end

def description
r = Result.new
r.letters_advanced = self.name.length
r.sum = self.value
r.number = self.value
r
end

ONES = [ "", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" ]
TEENS = ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" ]
ORIG_TENS = [ "twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" ]

tens = Array.new
ONES.each { |n| tens << n }
TEENS.each { |n| tens << n }
ORIG_TENS.each do
|ten|
ONES.each do
|one|
tens << ten + one
end
end

TENS = tens

HUNDREDS = ONES.map do
|hundred|
root = hundred == "" ? "" : hundred + "hundred"
numbers = tens.map do
|ten|
root + ten
end
numbers
end.flatten

number = 0
NAMED_HUNDREDS = HUNDREDS.map do
|name|
value = { :number => number, :name => name }
number += 1
value
end.sort_by { |pair| pair[:name] }.reverse

last = nil
NAMED_HUNDREDS.each do
|pair|
last = Hundred.new(pair[:name], pair[:number], last)
end

@last = last

def self.first
@last
end
end

position = ARGV[0].to_i
result = Result.new(position)
number = Hundred.first
while( result.letters_missing > 0 )
result << number.description
number=number.next_number
end

p result

2007-11-18

Viaje boliviano

Mi cuñada está de viaje (tipo mochilera) por Bolivia desde junio. Éstas son las consecuencias del viaje, tal como lo relata en un mail:

"cerca de alli esta el ojo del inca, que es una fuente de aguas termales segun dicen con grandes propiedades curativas.... y creo que es lo uqe estoy necesitando en este momento porque no paro de tener problemas de salud.... nada grave no te preocupes, pero la verda es que me siento unav ieja tomando antibioticos, poniendome pomadas n diversos lugares del cuerpo, tomando toda clase de hierbas y yendo de medico en medico, a ver si alguna vez alguno la pega..... por suerte la quemadura ya cicatizo, pero tendre una enorme mancha en la pierna por el resto de mis dias.... de cualquier manera estoy probando con aloe vera y mayonesa, a ver si se borra un poco, pero por ahora no veo mucho resultado....

despues esta el tema del quiste, que creo que te habia contado, que me salio hace unos meses en el gluteo y me esta molestando por demas, ahora estoy probando con un nuevo antibiotico y si no funciona tendre que ir a un especialista porque eso si me dijeron que no lo deje pasar....
y de la selva me traje algo que parece ser un excema, cosa que nadie supo explicarme que es, pero todavia ono encuentro cura y no solo me duele sno que tengo la piel que pareciera pudrirse, justo detras de la rodilla....
por si fuera poco tengo parasitos que me hacen descomponerme cada dos por tres.... ahora estoy probando con gotitas de ajenjo y bardana que me regalo un español, el problema es que deberia continuar el tratamiento pero no encuentro esas hierbas por aqui......"

¿Sobrevivirá para traerme el charango que le pedí?

2007-11-08

¿Qué estudio?

Estoy con ganas de estudiar algo el año que viene (y quizás algunos más, o no). Mis opciones son bastante amplias, así que quería preguntarles que les parecen, si se les ocurre alguna otra, si alguna les parece que no vale la pena (por ejemplo, porque lo estudiaron y no les parece que lo vale) o si hay alguna de éstas que sea la posta. Es claro que no puedo estudiar todo esto, así que tengo que elegir.

Estas son las opciones que se me ocurrieron (sin ningún orden particular) y algunos pros y contras:

  • Lenguaje chino (¿mandarín?)
    • Pros: 1/6 del mundo lo habla. Tiene una forma re conceptual de definir las ideas.
    • Cons: mucha gente lo estudia. ¿No me puedo comunicar en inglés? Aparentemente es re-dificil
  • Lenguaje coreano
    • Pros: menos gente lo estudia. La comunidad coreana argentina es bastante grande.
    • Cons: menos gente lo habla.
  • Matemática
    • Pros: es perenne.
    • Cons: descolgado del mundo. Carrera exigente y larga.
  • Física
    • Pros: el mejor ejemplo de modelado que hay.
    • Cons: Carrera exigente y larga. ¿Hay gente más pedante que los físicos?
  • Algo relacionado con el diseño gráfico
    • Pros: Una de mis falencias más evidentes es la falta de criterio estético.
    • Cons: Una de mis falencias más evidentes es la falta de criterio estético.
  • Doctorado en computación
    • Pros: Seguir avanzando en el mismo camino.
    • Cons: Los doctorados son algo demasiado intensivo y no tengo forma de aguarlo (en las carreras de grado cursaría menos materias y listo).
Bueno, eso es todo.
Escucho ideas, sugerencias y críticas.

2007-10-26

El voto geek

El voto geek está complicado en estas elecciones.

Por un lado están los pingüinos K, que en su plataforma tienen Linux con KDE.

Por otro lado está UNA, representante de una gran tradición, el acrónimo recursivo.

Por lo tanto, el voto geek estará fuertemente polarizado. ¿A quién apoyaremos en estas elecciones?

PD: Si ya sé, estoy rompiendo la veda. Si quieren denúncienme al COMFER.
PD2: El voto geek puede o no estar correlacionado con el voto del resto de la gente.

2007-10-24

El botellón del dispenser

Hace un tiempo que estuve pensando en cómo explicar cuál es el mejor momento para hacer un refactoring, y cómo explicar cuál es el momento para hacer el refactoring, y cómo explicar cómo explicar cuál es el momento para hacer el refactoring....
Bueno, ustedes captan la idea.
Y esta es la explicación que elaboré.
Hacer un refactoring es como cambiar el botellón del dispenser de agua de la oficina.
¿Por qué?

  • No vas a ganar más plata por cambiar el bidón.
  • Generalmente cambiar el bidón es un dolor de huevos.
  • Pero peor es no cambiarlo (y quedar sediento).

Bueno, ¿y cuándo conviene cambiar un bidón del dispenser?
Definitivamente no cuando todavía queda agua en el mismo.

Tampoco es una buena idea cambiarlo después de que deje de salir agua del dispenser, porque cuando quieras sacar agua caliente va a estar tibia y cuando quieras sacar agua fría va a estar tibia.

¿Entonces cuándo hay que cambiarlo?
Cuando se vació el bidón, pero queda agua en el tanque que está adentro del dispenser (y las cañerías). Entonces no desperdiciamos agua y el agua sale fría y caliente cuando lo queremos.

Hacer refactorings es algo parecido. Hacer un refactoring antes de tiempo es hacer "boiler-plate", y por lo tanto una pérdida de tiempo. Hacer un refactoring tarde (o no hacerlo) que suframos un diseño feo y que apenas lo hagamos tengamos que arreglar todas las cosas que se hicieron mal porque el diseño anterior no lo permitía de otra manera. En cambio, hacer el refactoring justo antes de necesitar esa flexibilidad en el código por primera vez, es como cambiar el botellón cuando queda agua dentro del dispenser pero no en el botellón.
¿Y cómo nos damos cuenta cuándo estamos por necesitar una flexibilidad en el diseño? La verdad es que soluciones mágicas no conozco (si alguien sabe alguna, por favor que avise). Pero en mi experiencia programar test-first ayuda mucho.

Eso es todo, y espero que hayan disfrutado de las ilustraciones,
Aureliano.

2007-09-17

El cambio recién empieza




¿Siguen las cirujías? ¿Cómo va a terminar? ¿Como Moria Casán? ¿Cómo Zulema Yoma? ¿Como Michael Jackson?

2007-09-10

Un poquitín de Opera

Ayer a la noche fui a ver "Iphigénie en Tauride", de Gluck, interpretada por mi querida Compañía de las Luces. Como siempre (y aún más desde que dejé de cantar ahí) hicieron un espectáculo espectacular (redundantemente). Y están de suerte. Todavía quedan 2 funciones más, así que vayan.

Las dos funciones que quedan son el próximo sábado 15 y domingo 16 de septiembre a las 19:20hs en el Museo Nacional de Arte Decorativo. El mismo está en Libertador y Pereyra Lucena. Las entradas salen solamente $30.

2007-09-05

PHP GRASP

En mi laburo, Core Security, acaban de relesear un proyecto open source (licencia: Apache 2) que sirve para detectar SQL injections en PHP 5.2. El proyecto se llama GRASP y lo que hicieron mis compañeros de laburo es que se puede taintear cada caracter de un string y cuando se hace la llamada a la base de datos chequea las marcas en el string del query y controla si hubo un SQL injection o no.

En la versión actual chequea injections en mysql.

Me parece que se podría hacer algo para Rails. Así que si insisten lo suficiente (¡suscribanse a la lista y pidanlo!) en una de esas me dejan hacerlo. ¡Pidanlo ahora!

El site de GRASP es: http://grasp.coresecurity.com.

2007-08-31

Bug en rubygems 0.9.4

Hoy me crucé con un bug en la versión 0.9.4 de rubygems que hace que no tome de la línea de comando el proxy http, lo que hacía que no ande desde el laburo. Este bug está reportado y, por lo que dice en el bug tracker ya está arreglado en el repositorio de svn.

Pero si no quieren esperar a un nuevo release, se arregla muy fácil.

Busquen el archivo config_file.rb y pongan el siguiente código dentro de la clase ConfigFile (arriba de donde dice private):


def []=(key,value)
@hash[key.to_s] = value
end


Enjoy!
Aureliano.

2007-08-29

Fotos del eclipse

¡Miren que lindas estas fotos del eclipse de luna del lunes!

2007-08-24

Pequeña alegría

Es bueno cuando el universo confirma algunas cosas, como que ruby es "1337".

Esto apareció hoy cuando miré mi gmail a la mañana:

2007-07-24

Una nación avanzada

Ayer a la noche caí que el partido de Lavagna tiene un acrónimo
recursivo. Siguiendo la tradición informática de "Joe's own editor",
"Sine is not emacs", "LAME Ain't an MP3 Encoder", "Wine Is Not an
Emulator" y "GNU's not UNIX", se viene "Una Nación Avanzada".

¿Lo habrán hecho apropósito?

¿Están buscando el voto nerd?

2007-07-07

Nitro

Como algunos de ustedes saben (y otros no), a mi me gusta Ruby como lenguaje de programación. Y, aparte, se me ocurrió (de nuevo) una idea para hacer una página web y llenarme de $$$$. Por supuesto que no les voy a contar la idea ;), porque no me gusta programar apurado.

Pero rails no me termina de gustar. Es medio fascista, como python. Tiene un montón de cosas que están buenas, como python, pero no me gusta que sea tan estructurado, como python. Tampoco me da hacerla en PHP (eso sí que es feo). Y como es algo que hago porque tengo ganas y con mis propios tiempos decidí probar algo nuevo. Así que me puse a revisar un framework web que viene creciendo hace rato, a la sombra de Rails.

El framework se llama nitro y en gems va por la versión 0.43 (o sea, tiene 43 releases, creo). Como hace 6 meses que no releasean nada, pero están laburando a full decidí agarrar la última versión del repositorio de desarrollo. Así que me instalé darcs e hice un "get" del repositorio. Después de un rato, encontrar un bug, reportarlo y que lo arreglen y con ayuda por IRC de uno de los tipos que lo desarrollan, hice andar el ejemplo que viene con nitro, un blog.

Por lo que estuve leyendo, el framework es más desestructurado que rails y te deja poner las cosas donde quieras y aparte el mapping relacional-objeto que tienen es copado (googleen "ruby og").

Les cuento que las gems de las que depende son: facets, xml-simple, RedCloth y uuidtools.

Bueno, por último les dejo algunos links:
* http://www.arnebrasseur.net/2007/07/01/the-daily-nitro/en/ (explica como "setupear" nitro para hacerlo funcar y de que gems depende, aunque les faltó incluir una dependencia, uuidtools)
* http://rubyforge.org/pipermail/nitro-general/2006-August/005832.html (en el medio del post explica cómo agarrar nitro del repositorio darcs en el que está la última versión "oficial").
* http://www.nitroproject.org/ (home page de nitro, todavía no está terminada).
* http://oxyliquit.de/ (site con tutoriales y preguntas y respuestas sobre nitro y og).
* http://blog.interlinked.org/tutorials/darcs.html (tutorial sobre como usar darcs).

Eso es todo amigos, me voy a hacer lo que todo geek que se precie hace un sábado a la noche, programar.

Aureliano.

2007-06-11

Charla de metaprogramación en Ruby

El viernes pasado di una charla de metaprogramación en Ruby en mi laburo. La misma duró algo así como una hora y media y me faltó mostrar algunas cosas (tenía que tardar una hora) y estuvo interesante (creo). Una hora y media a puro irb, sin slides y casi sin usar el pizarrón.

Acá pongo la planificación de la misma y algunos comentarios:

A continuación está la preparación de la charla:

Historia
Creado por Yukiro Matsumoto (AKA:Matz) en 1994 porque Perl no soportaba Kanji (encoding japonés). Se usó solo en Japón hasta que en el 2000 se publicó el libro: "Programming Ruby", primer libro de Ruby en inglés. Después lo agarró DHH e hizo Rails y se hizo famoso.

Comparaciones
Ruby tiene muchas de las cosas buenas de Perl, como soporte nativo con sintaxis propia para regex.
/\d+(\.\d+)?/ reconoce números (por ejemplo). Y como a Matz le gustaba Smalltalk, todo es un objeto.
También, como en Smalltalk, hay metaclases, las clases son objetos, todas las variables son referencias (no hay tipos primitivos) y se pueden pasar closures a los métodos.

Hands on
(las líneas que empiezan con >> las tipeo en el irb, las que empiezan con => son lo que escribe el irb solo, las que están en corchetes las pongo en un archivo)
En ruby podés hacer cuentas:

>> 1+1
=> 2

asignar a variables, usar strings:

>> var = "hola"
=> "hola"
>> var
=> "hola"

usar expresiones regulares para hacer cosas:

>> "5 mas 95 es 100".gsub( /\d+/, "numerito" )
=> "numerito mas numerito es numerito"

tambien hay una cosa que se llaman símbolos (que son como los símbolos de Lisp) y se escriben así:

>> :mi_simbolo

a muchos objectos (incluidos los simbolos) les puedo pedir que se muestren como strings:

>> :mi_simbolo.to_s
=> "mi_simbolo"

la diferencia entre los símbolos y los strings es que puede haber muchos strings con el
mismo texto pero solo un symbol con ese texto (object_id es un número único del objecto,
o sea que si dos objectos tienen el mismo número son el mismo objeto):

>> :mi_simbolo.object_id
=> 359538
>> :mi_simbolo.object_id
=> 359538

el object id es el mismo.

>> "mi_simbolo".object_id
=> 24163140
>> "mi_simbolo".object_id
=> 24160510

el object id es distinto.

También, como en Perl, hay interpolación de strings

>> "1 mas 1 es #{1+1}"
=> "1 mas 1 es 2"

Y como lo que devuelve es un string posta:

>> "1 mas 1 es #{1+1}".gsub( /\d+/, "numerito" )
=> "numerito mas numerito es numerito"

podés aplicarle todas las operaciones de string.

Arrays:

>> [1,2,3]
=> [1, 2, 3]

Hashes:

>> h = { :a => "aa", :b => "bb" }
=> {:b=>"bb", :a=>"aa"}
>> h[:a]
=> "aa"

Bueno ahora vamos a definir una clase. Edito en el gvim:

[
class A
def initialize( param )
puts "Inicializando A con #{param}"
@inst_var = param
end

def hace_algo( param )
"#{@inst_var} #{param}"
end
end
]

mostrar que no tengo que pasar self como en python
y uso la clase en el irb (después de cargarla):

>> a = A.new( "toto" )
Inicializando A con toto
=> #

esto guarda "toto" en la variable de instancia

>> a.hace_algo( "parametro" )
=> "toto parametro"

y acá lo muestra.

La herencia es herencia simple igual a otros lenguajes, así que no la muestro.

Otra cosa medianamente distintiva de Ruby es el uso de módulos como mixin:
Mostrar todos los métodos de instancia que tienen "algo" en su nombre:

>> A.instance_methods.select { |m| m.include? "algo" }
=> ["hace_algo"]

escribo en un archivo:

[
module M
def otro_algo( param )
"otro " + hace_algo( param )
end
end
]

lo cargo y después:

>> class A; include M; end
=> A

eso pone los métodos definidos en M en la clase A

>> A.instance_methods.select { |m| m.include? "algo" }
=> ["hace_algo", "otro_algo"]

Esto inclusive modifica a los métodos de las instancias vivas:

>> a.methods.select { |m| m.include? "algo" }
=> ["hace_algo", "otro_algo"]

Y los ejecuto para mostrar como llamar desde el módulo a la clase:

>> a.otro_algo( "aaaaaaaaaaa" )
=> "otro inicial aaaaaaaaaaa"

Como acaban de ver cuando hice el include, las clases de ruby son abiertas. Alguien se acuerda de VB? Algo que siempre me gustó es que en vez de escribir "self" o "this" usamos "me".

Abro un archivo y escribo:

[
class Object
def me
self
end
end
]

Lo cargo (load ....) y lo uso:

>> me
=> main
>> me.class
=> Object

También lo puedo usar dentro de otras clases:

>> class A; def klass; me.class; end; end
=> nil
>> a.klass
=> A

Otro feature interesante es el de los bloques de código (AKA: closures)
Ya vimos un ejemplo del uso cuando miramos los nombres de los métodos definidos en A y a.
Mirando un toque más en detalle:

>> [1,2,3,4,5].each { |n| puts "Numero #{n}" }
Numero 1
Numero 2
Numero 3
Numero 4
Numero 5
=> [1, 2, 3, 4, 5]

También puedo hacer mis propios métodos que reciban bloques de código, recordando que las clases son abiertas, modifico Integer para iterar. (Acá nos desviamos un toque de la planificación y mostramos también como usar parámetros del método definido dentro del closure. Dejo la planificación original):

>> class Integer; def veces( &block ); (1..self).each { |n| block[n] }; me; end; end
=> nil
>> 3.veces{ |n| puts n }
1
2
3
=> 3

Los métodos se pueden definir en clases y en objetos, por ejemplo:

>> b = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> def b.sarasa(toto); "sarasa #{toto}";end
=> nil
>> b.sarasa
ArgumentError: wrong number of arguments (0 for 1)
from (irb):71:in `sarasa'
from (irb):71
from :0

(ups, me olvidé de pasarle el parámetro).

>> b.sarasa( "aaaaaaaaaaaa" )
=> "sarasa aaaaaaaaaaaa"

En ruby puedo definir properties "tipo Delphi" (o C#). Para esto, hay varios métodos
definidos en la clase Module. Acá muestro uno:

>> class A; attr_accessor :sarasa; end
=> nil
>> a.sarasa = "SARASASASA"
=> "SARASASASA"
>> a.sarasa
=> "SARASASASA"

Ahora bien, attr_accessor es un método del lenguaje (no una palabra reservada) así que voy
a implementarlo de nuevo.

[
class Module
def my_attr_accessor( symbol )
instance_var = "@#{symbol}"
define_method( symbol ) { instance_variable_get instance_var }
define_method( symbol.to_s + "=" ) { |value|
instance_variable_set instance_var, value }
end
end
]


Hago load y lo uso:

>> class A; my_attr_accessor :toto; end
=> #
>> a.toto = 5
=> 5
>> a.toto
=> 5

Salvo por cuestiones de performance my_attr_accessor (y que le falta funcionalidad) es equivalente
al original. Toda la funcionalidad se puede implementar, pero tardaría más y no tengo tiempo ni ganas.

También se pueden hacer métodos de clase que son solo para una clase específica, usando la metaclase.

>> class A; def self.klass_method; "klass_method";end;end
=> nil
>> class A; klass_method; end
=> "klass_method"
>> class B; klass_method; end
NameError: undefined local variable or method `klass_method' for B:Class
from (irb):75
from :0

Notar que lo que hice es definir un método para una sola instancia de la clase Class.

>> class C < A; klass_method; end
=> "klass_method"

También vale para las clases que heredan de la misma. Esto es lo que hacen en Rails para poder hacer:

class Entity < ActiveRecord::Base
has_many :other_entity
end

Como en Smalltalk, se puede hacer override de method_missing para hacer proxies dinámicos y esas cosas (en la charla tuve que saltear esta parte porque venía atrasado de tiempo, así que se perdieron mi DSL de diálogos de pelis porno):

[
class A
def method_missing( method_id, *args, &block )
action = "action_#{method_id}"
(respond_to? action) ? send(action) : super( method_id,*args, &block )
end

def action_a
puts "AAAAAAhhhh"
end

def action_o
puts "OOooohhhh"
end
end
]

Y lo uso:

>> load "bdlv.rb"
=> true
>> a.a
AAAAAAhhhh
=> nil
>> a.o
OOooohhhh
=> nil
>> a.metodo_que_no_existe
NoMethodError: undefined method `metodo_que_no_existe' for #
from ./bdlv.rb:17:in `method_missing'
from (irb):107
from :0


Por último, mostré markaby:

>> require 'markaby'
=> true
>> mab = Markaby::Builder.new
=> #<Markaby::Builder:0x2ef2160 @auto_validation=true, @assigns={}, @output_meta
_tag=true, @builder=<inspect/>, @indent=0, @streams=[["<inspect", "/", ">"]], @h
elpers=nil, @tagset=Markaby::XHTMLTransitional, @output_xml_instruction=true, @o
utput_helpers=true, @elements={}>

>> mab.html do
?> head { title "Core se la banca" }
>> body do
?> h1 "Productos de Core"
>> ul do
?> li "Sarasa $500000000"
>> li "Sarasa2 $100000000"
>> li "Consulting priceless"
>> end
>> end
>> end
=> "<html><head><meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-
Type\"/><title>Core se la banca</title></head><body><h1>Productos de Core</h1><u
l><li>Sarasa $500000000</li><li>Sarasa2 $100000000\n</li><li>Consulti
ng priceless</li></ul></body></html>"

Notar que es código ruby! Por ejemplo, puedo iterar:

>> mab = Markaby::Builder.new
=> #<Markaby::Builder:0x2ecec9c @auto_validation=true, @assigns={}, @output_meta
_tag=true, @builder=<inspect/>, @indent=0, @streams=[["<inspect", "/", ">"]], @h
elpers=nil, @tagset=Markaby::XHTMLTransitional, @output_xml_instruction=true, @o
utput_helpers=true, @elements={}>
>> mab.html {
?> body {
?> ul {
?> [ "Sarasa $500000", "Sarasa2 $100000", "Consulting priceless" ].each do
?> |item| li item
>> end
>> }
>> }
>> }
=> "<html><body><ul><li>Sarasa $500000</li><li>Sarasa2 $100000</li><li>Co
nsulting priceless</li></ul></body></html>"


Por supuesto, este html es básico pero esto es CÓDIGO. Por ejemplo, haciendo un form:

>> mab.html { body { form( :target => "http://sarasa" ) { input( :type => :submit ) } } }
=> "<html><body><form target="http://sarasa"><input type="submit"/></form></body></html>"


También mostré como yapa el ejemplo del tutorial de rake.

Y me faltó el ejemplo de como manipular Excel, pero lo dejo acá:

require 'win32ole'
# -4100 is the value for the Excel constant xl3DColumn.
ChartTypeVal = -4100;

# Creates OLE object to Excel
excel = WIN32OLE.new("excel.application")

# Create and rotate the chart

excel['Visible'] = TRUE;
workbook = excel.Workbooks.Add();
excel.Range("a1")['Value'] = 3;
excel.Range("a2")['Value'] = 2;
excel.Range("a3")['Value'] = 1;
excel.Range("a1:a3").Select();
excelchart = workbook.Charts.Add();
excelchart['Type'] = ChartTypeVal;

30.step(180, 10) do |rot|
excelchart['Rotation'] = rot
end

excelchart2 = workbook.Charts.Add();
excelchart3 = workbook.Charts.Add();

charts = workbook.Charts
charts.each { |i| puts i }

excel.ActiveWorkbook.Close(0);
excel.Quit();

2007-05-31

Cómo ser un pirata

2007-05-03

Buenísimo este video

Miren el video en http://www.antimult.ru/antimults/antitoons/001smokekills/view.htm. Está buenísimo.

2007-04-17

Mirando el largo plazo

Encontré este comentario en slashdot que me pareció buenísimo, así que quería compartirlo. Lo que leerán a continuación es mi traducción del mismo.


Las características que nuestra sociedad capitalista premia (la excelencia en algunos campos da una gran riqueza y la competencia en ciertas habilidades en el trato interpersonal son más importantes que la competencia en cuestiones técnicas, etc.) son, aparentemente, lo que queremos como sociedad. Si la paga es mejor, la oferta debería incrementar. Sin embargo, las cosas que están correlacionadas con una mayor tasa de nacimientos son: religión (más => más hijos), región del país, etc. Características que están negativamente correlacionadas son educación (una mayor educación, especialmente para las mujeres, reduce la natalidad), ingreso, etc.

Aún más interesante es el nivel de diversidad genética. Los más despreciados por la gente que destaca los valores "familiares" (madre soltera con muchos hijos de diferentes padres) son los que generan más diversidad genética. Caballeros como K-Fed producen múltiple descendencia con múltiples mujeres, asegurando la diversidad genética de su prole.

Esto es interesante porque después de varias generaciones con decreciente religiosidad, creciente educación y gente sana viviendo más, parece que estas mismas fuerzas biológicas están achicando estas características. Hace unos años salió un divertido editorial sugiriendo que Roe vs. Wade destruyó el partido demócrata (N. de A.: de EEUU) no porque los abortos fueran impopulares sino por todo lo contrario. Debido a la alta correlación de la opinión política de la gente con la de sus padres, y la correlación entre ser demócrata con abortar (es 2 o 3 veces más probable tener un aborto si la mujer es demócrata que si es republicana), hacen que 18 años después de Roe vs. Wade haya una reducción en el pool genétcio de los demócratas.

También, las mayores tasas de natalidad de los fundamentalistas de todas las religiones está causando una lenta regresión de las reformas y cambios de la "post-iluminación" en las comunidades religiosas. Las "iglesias protestantes tradicionales" están perdiendo gente, los católicos se mantienen, pero el crecimiento es en Sudamérica y Africa (N. de A.: espero que no tenga razón) mientras que su presencia en Europa se achica y los católicos americanos son porcentualmente cada vez más latinos, en el judaísmo está creciendo su ala ortodoxa (desde el 8% hace 20 años hasta el 15% ahora) que se está tirando a la derecha y las tasas de crecimiento de los musulmanes están ganándoles a todo lo demás.

Basicamente, nuestra cultura secular ateísta encontró tal grado de autoindulgencia y libertad que puede llegar a achicarse a si misma. La izquierda norteamericana constantemente acusa a Bush pero los cambios culturales en EEUU muestran que la demografía, y no la demagogia, es lo que causa este empuej político reaccionario.

Es sumamente interesante, pero yo lo encuentro en más alto grado de ironía (N. de A.: yo también), que las alas "bíblicas" "anti-evolucionarias" de todas las religiones, que eran marginales hace una generación, de golpe están ganando, mientras que las seculares, culturalmente basadas en la ciencia y la razón, están en retirada. Y la razón es que la gente "anti-evolucionaria" está exparciendo su material genético y haciendo crías para hacer avanzar su agenda, y la gente pro-ciencia pro-evolución está acotando su material genético con familias de 0 a 2 hijos.

De hecho, más molesto es que los hombres que se manejan de las forma socialmente más irresponsable -- engaños permanentes, divorcio, etc., generalmente son padres de muchos más niños que aquellos que "juegan respetando las reglas". Así que si el material genético influencia el comportamiento, vamos a encontrar que cada generación es un poquito más adúltera.....

Si está tendencia continúa, que por supuesto no va a pasar, las cosas se vana ir para el otro lado, pero ironicamente en 4 o 5 generaciones, vamos a tener una población general no-blanca, muy religiosa que va a la iglesia/sinagoga/mesquita regularmente mientras tienen relaciones adúlteras durante la semana.

:) Pero seriamente, ¿piensan que la derecha religiosa es mala ahora? En 50 o 60 años, si siguen duplicando en su tasa de natalidad al EEUU secular... van a ser la mayoría.

Divertidas observaciones demográficas amateurs...
Alex



A mi me asusta que no veo ningún motivo para pensar que esto no pueda ser así. Como la gente que me conoce sabe, me preocupa mucho más el crecimiento demográfico de los religiosos que de los adúlteros. ¿Estamos por entrar en otra Edad Media? ¿Nos salvarán los adúlteros? ¿Cómo evitarían este futuro?

2007-03-12

Experiencias con GMail

Hace unas semanas que estoy usando GMail como mi mail de cabecera. Este es el balance que hago del mismo.

Cosas buenas:

  • Siempre anduvo y rápido
  • Tags (AKA: labels)
  • Las conversations (están buenísimas para dar contexto a los mails).
  • Me gusta usar el search para los mails (de hecho ya lo hacía de antes).
Cosas malas:
  • No puedo abrir tabs en el Firefox con los mails (clickear un mail con el botón del medio del mouse no sirve para nada).
  • No hay API para manejar GMail.
  • La versión no-ajaxificada está incompleta y no se pueden agregar filtros ni tags nuevos.
En particular, los últimos 2 puntos hicieron imposible hacer un programita para traer los mails que tengo en Yahoo y ponerles etiquetas para matchear con las carpetas en las que tengo los mails clasificados allá. De hecho, con la rubygem que usaría para acceder a GMail (gmailer) no me anduvo crear tags nuevos (al menos cuando la probé desde el irb). Y, la verdad, no veo como hacer screen scrapping de la versión con AJAX de GMail.

Ya sé que puedo importar los mails por POP3, pero cuando tenés como 5000 mails, es mejor mantener la clasificación de los mismos. Igualmente, probar el traspaso de mails de Yahoo a GMail (que quedó abortado) sirvió para que aprenda a mandar mails con attachments y acentos en ruby via SMTP.

2007-03-09

Auto retrato technicolor


Solo lápices de colores. Nada de edición por computadora.

Auto retrato

Resultado del curso de dibujo

El martes pasado terminé el curso de dibujo que hice en la Academia Da Vinci. Tuve la suerte de tener unos compañeros copados y muy talentosos y un muy buen profesor. Ahí aprendí que es una paleta, algunos efectos ópticos que se generan con los colores (por ejemplo, en una imagen parece que los colores cálidos, como el rojo, están más adelante que los colores fríos, como el azul). Como centrar un dibujo, y varios etcéteras, todos muy interesantes.

Pero lo más importante es que estoy empezando (muy de a poco) a ver algunas cosas de otra manera. Empecé a ver como se combinan los colores, que paleta usaría para pintar las cosas, cuando algo está sin equilibrio, etcétera. Igual este es el primer paso de una maratón, así que no esperen que me vuelva en un artista plástico en 5".

Por último les muestro el resultado práctico del curso:

2007-03-07

Compositor de colores

Para los insensibles del color, como yo, les comento de una página web que está re-buena. En http://www.colorsontheweb.com/ tiene una compositor de colores. Le das el color base y te arma las paletas (monocromático, complementario, tríadas, etc.).

¡Una masa!

Encontré esta página mirando esta entrada de blog: http://www.glanzani.com.ar/?p=132

Enjoy!
Aureliano.

2007-03-05

Business card

Aprovecho el blog para hacerles una consulta,
Estoy considerando hacerme a mano las tarjetas para repartir en eventos. Mi idea es hacerlas en rojo, azul y amarillo con lápices aquarelables. Me pareció que le darían una onda más "tierna" y que se destacarían del resto. Pero no sé si quedarían mal. Así que escucho sus opiniones.

Así que las opciones que se me ocurren son:
1) Es la mejor idea que haya escuchado, voy a hacerlo yo también.
2) Puede ser que esté bueno.
3) Me parece medio tonto.
4) Das una imagen poco profesional.
5) Yo no contrataría a alguien que hace sus tarjetas así ni por casualidad.
6) Va a parecer que torturás una familia de niños tailandeses para hacer hacer tus tarjetas (y eso sólo está bien para las zapatillas).

Los escucho atentamente,
Aureliano.

2007-03-04

Me pasé a GMail

Les cuento que mi nueva dirección de correo es aurelianocalvo at gmail.com. La dirección de Yahoo está forwardeada a la nueva, así que por el momento si me mandan mails a Yahoo los seguiré viendo (aunque con una pequeña demora).

El motivo del pase es que Yahoo! estuvo inestable como 3 días seguidos y no podía acceder a mi mail. Ahora me voy a tener que adaptar a usar labels en vez de folders (en ppio no parece malo).

Éxitos,
Aureliano.

2007-02-19

Jugando con greasemonkey

Estuve probando un plugin re-groso para Firefox que se llama greasemonkey. El mismo permite ejecutar JavaScript arbitrario después de que se cargue cada página.

Usandolo, hice un pequeño script para sacar la molesta propaganda que ponen en el nuevo Yahoo Mail Beta. El script tiene 10 líneas y lo puse acá. Pero para que no tengan que cliquear, se los posteo acá mismo.


// ==UserScript==
// @name Yahoo Beta Ads Blocker
// @namespace http://mywebsite.com/myscripts
// @description Yahoo Beta Ads Blocker
// @include *.mail.yahoo.com
// ==/UserScript==

function $(id) {
return document.getElementById(id);
}

function remove(id) {
var elem = $(id)
if (elem) {
elem.parentNode.removeChild(elem)
}
}

window.addEventListener(
'load',
function() {
remove('largePane')
remove('emptyFolderFrame')
remove('nwPane')
remove('swPane')
},
true
);


Enjoy!
Aureliano.

Si quieren ver más detalles sobre el greasemonkey vayan a http://greasemonkey.mozdev.org/

2007-02-16

Los lenguajes de la web

Para programar páginas web hay que aprender unas cuantas cosas. En particular es llamativa la cantidad de lenguajes que hay que aprender:

  1. HTML
  2. JavaScript
  3. CSS
  4. XML
  5. XPath
  6. CSS selectors
  7. Javascript
  8. Lenguaje y bibliotecas server side de tu preferencia (Ruby on Rails, PHP, Perl, Java, JSP, EJBs, ASP.NET, etc)
  9. SQL?
Sin embargo, la mayoría de estas cosas son carga cognitiva al dope. Es mi opinión que el desarrollo web se simplificaría bastante ordenando este quilombo.

En primer lugar, en vez de usar HTML, se podría usar una biblioteca en JavaScript para generarlo (algo como markaby pero hecho en JavaScript). Esto haría que sea más fácil generar dinamismo en el cliente.

Los CSSs deberían ser una biblioteca que encaja con la biblioteca anterior. Si quieren separar los datos para que los maneje un diseñador, podría perfectamente usarse JSON.

El XML es 100% reemplazable por JSON (que, es más lindo para escribir datos y todo). JSON (y javascript) es lo que usan varias bibliotecas de AJAX (ATLAS, prototype) para comunicar cambios en la página.

XPath puede llegar a ser algo práctico, pero solamente para navegar por árboles de objectos (no XML). Quizás una biblioteca para buscar cosas en JSON con sintáxis parecida estaría buena.

Los CSS selectors son una creación abominable. No aportan nada al XPATH, pero como están en los archivos de estilo, no queda otra que usarlos.

Con respecto al server, estoy esperando que alguien haga JavaScript on Rails (la capacidad de metaprogramación está ahí) y lo enchufe con algún intérprete de Javascript. "¿Harías Rails en el cliente?", se preguntarán. Nop. La idea es usar el mismo lenguaje en el cliente y en el server. Netscape lo intentó hace un tiempo pero se le acabo la plata. El mundo sería mejor si Sun hubiera apoyado esto en vez de impulsar Java a diestra y siniestra. Tener el mismo lenguaje en cliente y server permitiría hacer algunas cosas que ahora no se puede, como compartir el código de validación formato de los datos entre cliente y server sin repetir nada.

Y, por último, SQL es lo que intentamos sacarnos desde hace años de encima. Cada ORM (Hibernate, ActiveRecord) que hay es un intento fallido para escaparle.

¿Y por qué no lo ven así? Lo que hay que darse cuenta es que programar una página web dinámica es un ejercicio de metaprogramación. Un escribe un programa (que está en el server) que cuando se ejecuta genera un programa que se transmite al cliente (HTML + Javascript + CSS, etc) que cuando se ejecuta se renderea y muestra la página. Cuando se hace AJAX, en realidad hacés un hot fix en un sistema productivo. El resultado de una llamada AJAX son instrucciones de como cambiar el programa que está corriendo.

Pensado de esta manera, es razonable ver que conviene unir lenguajes en uno solo. JavaScript, a pesar de no ser mi lenguaje favorito (que es Ruby), para mi tiene todas las de ganar.

Tiene:
  • Soporte en el cliente.
  • Es conocido por los desarrolladores de páginas web.
  • Es razonablemente metaprogramable.
Para mi, el mayor problema es que para pasar cachos de código hay que escribir demasiado. Un bloque de código, pasado a una función, se ve así:
foo( c, function(a, b) { return a+b })


En cambio, el mismo código en Ruby es:
foo(c) { |a,b| a+b }


De todas maneras, JavaScript ya está por todos lados y es razonablemente bueno. Lo más importante es que tiene soporte en el cliente, que es la parte más dificil de cambiar en una página web. Si no me creen, traten de hacer páginas que anden solamente en FireFox y diganles a sus usuarios que se lo instalen.

Por último, quería agradecerle a frinfrunfranfrun haberse bancado chatear conmigo de este tema, lo que hizo que me caiga la ficha sobre que escribir.

Éxitos,
Aureliano.

2007-02-15

Si no puedes con ellos, úneteles

En una nota anterior, dije que había demasiados egresados de la Escuela da Vinci. Bueno, mañana empiezo un curso de dibujo allí mismo.

Espero poder contrarrestar mi falta de sensibilidad estética, y poder hacer programas (y/o páginas web) que no sean muy fe@s, sin ayuda de un diseñador. Sip, sólo no muy fe@s, hacer cosas lindas es realmente difícil y se los dejo a ellos.

Aureliano.

2007-02-14

Xara Extreme

Hoy estuve probando el editor vectorial de imágenes para Linux Xara (está en http://www.xaraxtreme.org/). Y me gustó. Está por la versión 0.7 y es un port de un programa de Windows. Aunque tiene algunos bugs, ya está usable y ¡estuve haciendo un dibujo!.

Les presento al "Señor Destapador":


¿Sugieren algún otro programa para reemplazar al Illustrator en Linux?

Aureliano.

PD: pueden usar esa imagen como quieran mientras me atribuyan la creación de la misma.

2007-02-13

Se termina kinesio

Si dios, la patria y los santos evangelios están conmigo; me quedan solamente 9 sesiones de kinesiología para la rodilla. Pero todavía no terminé :-(. Ahora me toca ir a un gimnasio a hacer pesas. Pesas y natación.

Tengo por lo menos 45 días de pesas. Aparentemente, todavía no tengo suficiente fuerza en el cuádriceps para que me den el alta. Se supone que puedo volver a las canchas (de paddle) en 2 meses, si no hay inconvenientes.

Éxitos,
Aureliano.

2007-02-05

Filtrado en el MSN

Están filtrando el envío de algunos URLs en MSN.

Acabo de confirmar que el URL http://www.scribemedia.org/2006/07/09/dhh/ ¡genera que la conexión del MSN se cierre! Parece que decidieron que Rails es competencia y lo empezaron a boicotear.

La verdad es que no me arrepiento en lo más mínimo de haber instalado KUbuntu en casa.

Saquen sus propias conclusiones,
Aureliano.

Update: en realidad parece que no dejan pasar mensajes que contengan el string ".scr" para evitar virus! ¡Qué patético!

2007-02-01

Maldito flash

Anteayer a la noche fuimos con Agustina a cenar a la casa de un amigo. Como nos tocó llevar el postre, hacía calor, Agus estuvo ocupada con un proyecto laboral, y yo tenía rehabilitación (de la rodilla, ¡malpensados!), decidimos comprar helado.
Entonces nos pusimos a buscar las heladerías en Google. Googleamos "sucursales Freddo" y nada. Googleamos "sucursales Persicco" y nada. Googleamos "sucursales Munchis" y nada.
¡Que raro! ¿Cómo puede ser?
¿No tendrán página web?
El problema es que hay demasiados egresados de la Escuela Da Vinci por ahí haciendo páginas web. Y hacen todo en Flash. Por lo tanto no podés linkear adentro de la página. Y aparte google no los indexa.

¡Basta de páginas web hechas en flash!

¡Quiero un google que ande!

&