MV Coders on fire

Nucklear

He estado modificando un poco el código que puse en #53 y me ha surgido una duda, es mi segundo día con python y en algunas cosas me pierdo:

Para controlar los casos donde se escribiesen letras, y valores fuera de rango hice lo siguiente:

def inputNum():
    #Capturo errores de tipo (Input solo acepta int)
    try:
        num = input("Introduzca un numero mayor que 0 e inferior a 4000: ")
    except NameError,TypeError:
        print "¡Huy! No es un número. Prueba de nuevo..."
        inputNum() #Vuelvo a inicio
    #Compruebo valores dentro de rango
    if num <= 0 or num >= 4000:
        print "El número debe ser mayor que 0 y menor que 4000"
        inputNum() #Vuelvo a inicio
    return num

El problema es que una vez que fallo y vuelvo a meter un valor a través del input num pasa a ser una lista y tiene el número anterior mas el nuevo. He probado del(num), num = 0, etc antes de volver al inicio y no me hace caso :no:

2 respuestas
Khanser

#61 Y inicializar num antes de hacer num = input() ?

1 respuesta
eisenfaust

#59 El código está bien. Algunos apuntes de estilo:

  • Usa t en lugar de T. Lo mismo para nil.
  • Cuando utilices una función helper para hacer tail-call, mejor llámala con -recur o -tail. Y si es posible, métela dentro del scope de la función, definiéndola con #'labels.
  • Los programadores de CL favorecen la iteración con respecto a recursividad. La herramienta loop es potentísima y más mantenible a largo plazo. Además de que al contrario de Scheme, una implementación de CL no tiene por qué contemplar TCO para respetar el estandar (pese a que la gran mayoría de éstas lo soporten).
  • Podrías haber utilizado #'case.
  • En el ejemplo de CL con símbolos, cuando utilices a-lists, mejor con este formato (k . v). Aunque viendo tu ejemplo mejor haber utilizado una p-list. Puedes invocar los valores con #'getf.
1 respuesta
scumah

En ruby :P

spoiler
Nucklear

#62 Noup ni antes del input no como global ...

Scottie

pero ver una posibilidad de iteracion y crearla, es sumamente complicado, el menos para mi... :(

2 respuestas
Meleagant

#66 Hay mecanismos directos para pasar de recursivo a iterativo.

La recursividad es más sencilla de programar, pero consume recursos de forma exponencial.

2 respuestas
B

#67: Con las funciones tail-recursive no se obtiene un rendimiento similar?

1 respuesta
B

En C, tomad tochana guarra xD:

main
función escribir

Hace muchísimo que no programo a parte de minicódigos para sensores wireless así que sed duros y crueles, machacad mi código.

1 respuesta
Nucklear

Bueno....¿vamos pensando en la siguiente? ¿Que tal un cliente IRC?

Ya de paso si alguien echa un ojo a #61 lo agradeceria porque me tiene mosca y seguro que es una chorrada.

1 respuesta
Khanser

#70 después de que en 3 días aperas votara ni el tato, deja que pasen al menos otros 2 días antes de zanjar esto. Es posible que haya gente menos experimentada que esté intentándolo y todavía no haya acabado.

B

#69 comentarios del tipo "if(N>0){ //si el dígito es mayor que 0" sobran, los comentarios están para aclarar partes de código complejo.

2 respuestas
B

#72 gracias xd, lo escribo para mi cuando lo dejo a medias y como en clase me lo pedían ya no lo quito. Luego en el pc lo cambio!

ItNaS

edit: he hecho la funcion inversa. esto pasa por no leer con atencion xd

Haskell

import Data.Maybe (fromJust)
roman2int :: String -> Int
roman2int num = sum $ zipWith (\a b -> if a < b then -a else a) (init x) (tail x)
    where x = (map (fromJust . flip lookup (zip "IVXLCDM" [1,5,10,50,100,500,1000])) num)++[0]
Explicación
1 respuesta
elkaoD

#61 no necesitarás flushear la entrada quizá?

#63 thanks, uso T y NIL porque LispWorks es el output que utiliza. Lo del tail call, por eso se llamaba -r aunque es cierto que -recur es más obvio.

¿Por qué CL favorece la iteración? ¿Es sólo porque no tiene por qué tener TCO? Es que me resulta mucho más obvio (tanto de programar como de leer) y mantenible cualquier algoritmo recursivo.

#66 eso es porque no lo has usado. Yo ahora ya no veo dónde usar whiles xD Todo lo iterativo es posible implementarlo como recursivo.

#67 como dice #68, con tail-call optimization es indistinguible de un while en cuanto a rendimiento y MUCHO más legible. Yo desde que programo en Scala ya no me imagino usando whiles, y fors lo menos posible :P

2 respuestas
Nucklear

#75 Tampoco vale, si el primer numero que pongo es válido p.e. 400 funciona, pero si lo primero que pongo es "asdafd" y despues "400" ya no va, aunque si imprimo num dice que solo tiene guardado "400" ...

eisenfaust

#75 &#65279;El problema es que hay mil maneras de hacer un algoritmo recursivo. Con LOOP, en cambio, al ser una herramienta estándar se fuerza un consenso entre programadores.

¿Qué preferirías mantener?

(defun fact (n)
  "Computes the factorial of the given integer."
  (labels ((fact-recur (acc num)
             (if (zerop num)
                 acc
                 (fact-recur (* acc num) (1- num)))))
    (fact-recur 1 n)))

(defun fact (n)
  "Computes the factorial of the given integer."
  (reduce #'* (loop for i from 1 to n collect i)))

Si te gusta la primera opción quizás lo que buscas sea Scheme/Racket.

Con respecto a T y NIL. El output del REPL por defecto en cualquier implementación de CL será siempre en mayúsculas, pero se puede cambiar y no es algo a lo que le prestaría mucha atención.

1 respuesta
elkaoD

#77 prefiero esto:

(defun from-to (from to)
  (if (<= from to)
    (cons from (from-to (+ from 1) to))))

(defun fact (n) (reduce #'* (from-to 1 n)))

Es un caso un poco extremo y aquí sí veo razonable el loop (sobre todo si no genera toda la lista antes de pasarla al reduce), pero creo que se entiende a qué me refiero: reusabilidad y expresividad. Si es posible "lazyficar" mi solución de arriba (y el for no es lazy) me quedo con ella.

De todas formas creo que tienes razón y en muchos casos el loop es más expresivo. A aprender a sacarle jugo tocan.

EDIT: Por cierto, ¿el collect cómo lo hace internamente el loop? ¿Va haciendo cons y luego reverse al final? Espero que sí, porque si no es O(N2) (creo...)
EDIT2: ¿La evaluación del for es lazy? Quiero decir, ¿el reduce se ejecuta con cada collect? Porque si no es así, es más eficiente con recursión (porque no genera una lista completa si no que va "reduciendo" a la que genera valores.)

2 respuestas
eisenfaust

#78 &#65279;Desconozco cómo funciona internamente el macro LOOP, pero la experiencia me dice que es más efectivo que utilizar recursión en cola.

Puedes hacer benchmarks con TIME.

La mayoría de las veces en términos de eficiencia se suele utilizar PUSH en lugar de CONS.

code
(dolist (x (list 1 2 3 4 5 6 7 8 9 10))
(push x result))
(nreverse result))[/code]

Y si modificas una variable léxica como es el caso puedes utilizar NREVERSE sin miedo, ya que al ser (potencialmente) destructiva también es más eficiente.

El tema es programar de forma funcional pero de cara al usuario. Lo que ocurra a nivel interno en las funciones ya es cosa tuya...

2 respuestas
B

#72 corregido.

#79 #78 estoy flipando con el LISP este :O el Haskell también tela #74. realmente parece casi magia para los profanos xD.

1 respuesta
elkaoD

#79 entiendo lo que quieres decir y lo veo muy razonable. Me apunto el concepto.

De todas formas me mantengo en que para casos en los que necesites streams gigantescos es mejor recursión + evaluación lazy. En Scala es muy fácil de hacer, el Lisp ni idea.

Ejemplo de factorial en Scala con streams (100000 elementos):

Stream range(BigInt(1), BigInt(100001)) reduce(_*_)

Mantén una lista de 100000 elementos durante toda la computación en LISP, a ver qué pasa xD Con streams por muy grande que lo hagas, sólo quedan evaluados los dos elementos que se están multiplicando en este momento. Por eso mi insistencia en la recursión, porque puedes hacer este tipo de operaciones de forma lazy y no mantienes toda la lista en memoria.

Por cierto, ¿por qué push y no cons?

#80 Lisp es más fácil de lo que parece, son cuatro cosas:

  • Un programa es una lista. Una operación es una lista. Una función es una lista (nombre + argumento + función.) Todo son listas (excepto los literales que las componen, obviamente.) De ahí que se llame Lisp (LISt Processing)

  • Para llamar a las funciones se usa (nombre-funcion argumento1 argumento2... argumentoN) Por ejemplo (> 2 0) es el equivalente de 2 > 0 en C. (+ 1 2 3 4 5 6) es como 1+2+3+4+5+6 en C.

  • Las funciones son un valor más, como podría ser un 1, nil (null) o 'a (la comilla ' significa "esto es un literal, no lo evalúes".) Por tanto las puedes pasar como argumento a otras funciones (el #'* que usamos para reduce significa "la función *", es decir, le estamos diciendo a reduce que use *, pasándoselo como argumento)

  • defun es una función predefinida que permite definir funciones tal que así:

    (defun nombre-funcion (argumento1 ... argumentoN) (operaciones))

A partir de aquí se trata de conocer las funciones predefinidas e ir "apilándolas" unas sobre otras pasando el resultado de unas al argumento de otras (yo me lo imagino un poco como "circuitos" en el sentido eléctrico pero con datos en lugar de electrones.)

Por ejemplo, reduce lo que recibe son dos argumentos, una función y una lista. Ej.:

(reduce #'+ '(1 2 3 4 5))

Como ves uso comilla por lo que comentaba antes, no quiero que evalúe la función 1 con los argumentos 2 3 4 5 si no que le digo "hey, esto es LITERALMENTE una lista."

Luego reduce simplemente ejecuta la función que le has pasado (en este caso + pero podría ser una función definida por nosotros) sobre los dos primeros elementos de la lista, el resultado de esto lo opera con el tercer elemento,eEl resultado de esto con el cuarto, etc.

En definitiva, hace algo como: ((((1 + 2) + 3) + 4) + 5)

Este estilo de programación es el pepino porque te permite definir pequeños "bloquecitos" que vas apilando para formar un conjunto mayor.

Como ventaja además añadiría que es más fácil de mantener porque tienes más "pequeños bloquecitos" y por tanto realizar pruebas unitarias es mucho más fácil que con clases complejas donde hay un montón de datos encapsulados, necesitas hacer mocks, etc.

3 respuestas
B

#81: A mi este tipo de lenguajes me parecen técnicamente bonitos, pero no me veo desarrollando un sistema con ellos.

Quedan normalmente relegados a funciones específicas? O hay grandes implementaciones con ellos?

#83: A mi lo del proyecto no me pasa. Pero yo soy ingeniero técnico y lo poco (o casi nada, aunque en unos meses espero que sea más) que sé de funcional lo aprendí por mi cuenta. En I.I. hay solo un par de asignaturas.

No te ralles por eso: píllate un libro!

2 respuestas
Nucklear

#81 Os escucho hablar y muchas cosas me quedo como un noob total. ¿Hay alguna documentación buena sobre algoritmos y demás cosas aplicadas a programación?

En estos casos me arrepiento mucho no haber hecho la carrera en lugar de DAI porque salí del ciclo con unos conocimientos muy muy básicos y ahora mismo cada vez que me pongo a intentar hacer algún proyecto algo grande me quedo bloqueado casi al empezar por falta de conocimientos...

#82 Escucho propuestas de libros xD

3 respuestas
elkaoD

#82 depende de a qué te refieras con "ese tipo de lenguajes." Con respecto a lenguajes puramente funcionales no creo que haya muchas grandes implementaciones. Casos semi-híbridos como LISP seguramente haya alguno y, si no, se usan para pequeños bloques donde convenga. En lenguajes híbridos completos (véase Scala) hay mil y un grandes desarrollos (no olvidemos que Scala va sobre una plataforma consolidada como la JVM.)

Si Scala entra dentro de la definición, Twitter, LikedIn y otros tantos lo usan. Twitter de hecho migró porque con Ruby les petaba seriously.

Lo bueno de los lenguajes funcionales es que ESCALAN y creo que en el momento que vivimos que es web masiva es muy importante. Escalan en ambos sentidos: en código (es fácil hacer "crecer" el código sobre tu base actual por lo que comento de los bloquecitos) y en ejecución.

A un lenguaje funcional le da igual 8 que 80, nunca mejor dicho. Se comportan igual y no tienes que tener el cuenta casos especiales. El rendimiento que pierdes es directamente proporcional a la complejidad de tu algoritmo mientras que en lenguajes imperativos la cosa se complica tanto que influyen miles de factores no inherentes a la solución del problema.

Además, en un lenguaje funcional la filosofía es que no hay estados mutables: tú tienes funciones que reciben parámetros y devuelven cosas. FIN. ¿Qué significa esto? Que un cálculo que dura 1 minuto lo puedo distribuir en mil máquinas y mil de esos cálculos duran un minuto. ¿Te imaginas hacer lo mismo en un lenguaje que use estado mutable? MUERTE. Tienes que andar sincronizando y pasando mierda para acá y para allá, y cuando más grande más mierda tienes que pasar y por tanto peor rendimiento (en resumen: no escalan.)

Y es que encima ES FÁCIL DE PROGRAMAR. A mí me parece más intuitivo que el factorial de un número es la múltiplicación de todos los números desde 2 hasta él. ¡Es obvio! Y en Scala así se escribe:

(2 to n) reduce(_*_)

¡Si es que lo puedes hasta leer! Desde 1 hasta n, multiplícalos. Ni más ni menos. ¿Cómo haces lo mismo en imperativo? Bucles, contadores y demás mierdas que NADA tienen que ver con el problema de fondo. Los lenguajes imperativos sólo complican.

¿Que resulta que ahora quiero factoriales gigantescos? No problemo, uso BigInt.

(BigInt(2) to BigInt(n)) reduce(_*_)

Inmejorable, ¿verdad? Pues no. Cuando ejecutas eso, se crea una lista en memoria de n elementos. Yo quiero que se vaya calculando par a par. ¿Cómo lo consigo? Con streams:

Stream range(BigInt(2), BigInt(n+1)) reduce(_*_)

Sigue pudiéndose leer literalmente: un stream de un rango entre 1 y n+1 (aquí es n+1 porque el rango es exclusivo) que es reducido en base a multiplicar sus elementos. ¿Alguien da más?

Uy, que resulta que ahora le quiero añadir soporte multicore. ¿Cómo lo haces en un lenguaje imperativo? No me lo quiero ni imaginar... pero flipa en Scala cómo se hace:

Stream.range(BigInt(2), BigInt(n+1)).par.reduce(_*_)

¡Sólo le he añadido par! ¿Es o no es para flipar?

A eso me refiero con los pequeños "bloquecitos". Todo esto que he usado viene en la librería estándar de Scala, pero es que lo que no viene lo puedo hacer yo y usar esos nuevos bloquecitos. No sólo eso, Scala además ayuda a que los nuevos constructos que vas creando se integren en el lenguaje de forma transparente.

Por ejemplo, me voy a definir un bloque de control que, todo lo que ejecute dentro, mida el tiempo que tarda en ejecutarse:

def time(f: => Unit) = {
  val s = System.nanoTime
  f
  System.nanoTime - s
}

In your face! Así de simple. Ahora cuando quiero medir el tiempo de algo sólo tengo que hacer:

time {
  instrucción_1
  instrucción_2
  instrucción_3
}

Igual que una puta estructura de control del lenguaje. IN-ME-JO-RA-BLE

Yo creo que si no se usan en grandes desarrollos es porque la peña sólo sabe pensar en Java, que es un lenguaje CUADRICULADO y pensado para las empresas. Tiene cuatro cosas contadas, por lo que modelar para Java es "easy as pie". Modelar para Scala no es tan fácil...

De ahí que la gente ni se complique. Es una mezcla entre medio empresarial (¿cosas nuevas? ¿estás loco?), desconocimiento del personal (¿cosas nuevas? ¿estás loco?) y estandarización (¿y esto como lo hago en UML?)

#83 yo por suerte en mi carrera tengo un par de profesores que son unos frikazos matemáticos así que el rollo algorítmico y funcional no me es desconocido. Además he tocado en 3 asignaturas movidas funcionales y en otras tantas algoritmia. Aún así, como oip yo también me lo he mirado por mi cuenta que es como se aprende de verdad.

De libros sobre algoritmia... pues no sabría decirte. ¿Wikipedia? ¿Google?

Sobre funcional "Progamming in Scala", del propio autor de Scala (Martin Odersky) Fácil, simple y para toda la familia. Literalmente, es MUY fácil de entender. No he encontrado un libro así de bien redactado en mi vida. En inglés, eso sí... Lo malo es que mientras que te enseña a "pensar en funcional" también te enseña Scala, que es un lenguaje con muchos "recovecos" así que acabas perdido entre Scala funcional y tal...

Lo mejor para aprender es mancharte los dedos. Bájate cualquier intérprete de Common LISP, el IDE oficial de Scala y a toquetear.

2 respuestas
B

#84: No, si yo estoy de acuerdo contigo. De hecho una de las cosas que estoy aprendiendo es Erlang (y el tema de OTP) para hacer mis servidores.

Pero yo estoy muy acostumbrado al diseño OO y no me imagino haciendo el backend de acceso a datos en Erlang, p.e.

O sea, sé que se puede, pero es que en Java da gusto oye xDD

#86: Yo reconozco que se me está atragantando mucho el tema. Estoy con esto y entiendo todo perfectamente, pero me cuesta un montón cambiar el chip.

Es decir, los típicos ejercicios con listas y tal ya los he hecho, pero cuando es algo más complejo... uf

1 respuesta
elkaoD

#85 pero es que nadie ha dicho que funcional y OO colisionen. Usa Scala y quédate con lo mejor de ambos. Repito: ten en cuenta que Scala va sobre la JVM y tiene clases, objetos, etc. De hecho a mí, tema a parte del funcional, me ha solucionado muchas de las cosas negativas que tenía Java a mi gusto y de forma elegante: mixins para "simular" herencia múltiple, interfaces semi-implementadas, interfaces con atributos no-final,, no distingue entre función y valor (puedes redefinir una función con una variable y viceversa), inferencia de tipos, funciones de primera clase, funciones de primer nivel (en la base del package) y así mil cosas...

Todos estos "drawbacks" en Java son solucionables pero a base de cientos de líneas de código. Me he visto negro para solucionar muchas de estas cosas. En Scala sale natural.

Mírate los edits que he hecho a mi post de antes y si no te convence Scala háztelo mirar xD

Si dices que en Java da gusto es que no conoces otra cosa. Java es horror. HORROR. Y no es un tópico. Es el lenguaje más sota-caballo-rey que existe. No te intentes salir de la vereda que estás jodido.

Incluso aunque te quedes con Java a secas, puedes usarlo como motor de persistencia y usar LISP u otro lenguaje funcional para la lógica de negocio.

3 respuestas
Nucklear

#84 Pues ahora en verano me pondré a mirarlo porque me da vergüenza no controlar eso y no dominar algún paradigma de desarrollo. Tengo una falta de tiempo increíble entre cursos y el curro xD

B

Estoy aprendiendo un puñao, gracias #86. Creo que intentaré aprender LISP o Scala de estos a ver si tengo tiempo...

Por cierto, perdón por salirme aún más del tema del hilo, pero me cago en rvm, en ruby, en rails y en ajax...

B

#86:

Stream.range(BigInt(2), BigInt(n+1)).par.reduce(_*_)

Esto es la hostia.

Yo a Java lo tengo en mi corazoncito precisamente porque, comparado con todo lo demás, lo domino muy bien (hice mi PFC en él, ahora estoy mucho con Android...) y realmente estoy de acuerdo contigo en lo de cuadriculado. Pero es que en parte también me viene bien esto para no hacer locuras muchas veces.

De todas formas seguiré con Erlang, ya que me interesa de cara a entrar en una empresa muy interesante (de estas nuevas, donde se hacen cosas guays y no aplicaciones internas de administración de X mierda).

1 respuesta
elkaoD

#89 en efecto es la hostia. A mí lo que más me mola es que parece tipado dinámico pero es tan estático como Java y sobre todo su elegancia, potencia y su ENORME librería estándar. Otro ejemplo:

val cadenas = List("cadena1", "esta es otra cadena", "más cadenas")
cadenas.reduce(
  (a, b) => if (a.length > b.length) a else b
)

Devuelve la cadena más larga, "esta es otra cadena".

¿Y si además de la cadena más larga quiero una tupla del estilo (longitud, "cadena" )? (además usando sintaxis abreviada con operadores custom)

((0, "") /: cadenas)( (a, b) =>
  if (a._1 > b.length) a
  else (b.length, b)
)

¡Boom! Devuelve (19,"esta es otra cadena" )


Eso mismo hablaba con un compi de clase. Me decía que Scala era una mierda porque le salía absolutamente todo mal, pero claro, no se había ni mirado el libro y estaba progamando Java con sintaxis Scala.

Me decía que estaba enamorado de Java, pero como él mismo decía... "¿es amor o simplemente me gusta porque ya lo conozco de tanto usarlo?"

No seré yo el que diga que Java es una mierda en realidad (por mucho que eche pestes xD) Tiene sus cosas buenas, pero...

1 respuesta