Archivo

Entradas Etiquetadas ‘bc’

Números grandes en C usando GMP. Resolución del primer reto de #tuentiContest (Super Hard Sum)

Viernes, 24 de Junio de 2011 Gaspar Fernández Sin comentarios

Aquí llega mi primera aportación a las soluciones de los retos del I concurso de programación de Tuenti. La utilización de números grandes es algo que siempre me llamó la atención, y normalmente utilizo bc cuando necesito algún cálculo. Este reto se podía resolver con bash/sed/bc y, aunque varios lenguajes permiten la utilización de números de precisión arbitraria “de serie”  como python y Java, yo decidí hacerlo en C, utilizando la biblioteca GMP.

En un lenguaje como C, es normal que no podamos utilizar números de precisión arbitraria con los operadores normales (+, -, *,…), en este caso tendremos que hacer llamadas a funciones de la biblioteca para realizar las operaciones, tampoco podremos utilizar un printf() normal para mostrarlos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
*************************************************************
* @file hardsum.c
* @brief Tuenti Contest Test Phase
* Sum numbers separated by space line by line
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @version 0.0.2
* @date 12 jun 2011
*
* http://totaki.com/poesiabinaria
*
*************************************************************/


#include <stdio.h>
#include <string.h>
#include <gmp.h>

/* Huge string */
#define STRSIZE 1000

int main()
{
  char bigstr[STRSIZE];
  mpz_t tmp, sum;
  char * token;

  /* Initialize gmp numbers */
  mpz_init(tmp);
  mpz_init(sum);
 
  /* Read until EOF in stdin */
  while (fgets(bigstr, STRSIZE, stdin)!=NULL)
    {
      /* reset sum */
      mpz_set_si(sum, 0);
      token=strtok(bigstr, " \n");
      while (token!=NULL)
        {
          gmp_sscanf(token, "%Zd", &tmp); /* Extract number from token string */
          mpz_add(sum, sum, tmp);         /* Adds numbers */
          token=strtok(NULL, " \n");
        }
      gmp_printf("%Zd\n", sum);
    }

   return 0;
}

Para compilar, debemos incluir gmp:

$ gcc -o hardsum hardsum.c -lgmp

Para utilizar este tipo de variables numéricas, primero tendremos que inicializarlas con mpz_init(), otras funciones interesantes son:

  • mpz_set_si(): inicializa el número con un valor entero.
  • gmp_sscanf(): igual que scanf(), pero con la posibilidad de utilizar estas nuevas variables.
  • mpz_add(): realiza la suma de dos números
  • gmp_printf(): igual que printf(), pero con la posibilidad de utilizar estas nuevas variables.

GMP tiene muchísimas funciones para realizar gran cantidad de operaciones, se puede consultar la documentación aquí.

Bucles for en BASH

Martes, 1 de Junio de 2010 Gaspar Fernández 2 comentarios

loooop

No por ser un lenguaje de script enfocado a la línea de comando vamos a dejar de poder hacer un bucle. Es más, si los archivos batch pueden, estos scripts no van a ser menos. Es común ver un bucle for en bash de este modo:

1
2
3
4
for i in $lista;
do
   echo $i;
done

Donde lista puede ser:

lista=”Una serie de cosas separadas por un espacio normalmente. Es un carácter del $IFS

El IFS es el Internal Field Separator (Separador Interno de Campo) y se usa para saber cuándo parar un read; por defecto se usa el espacio/tabulador/nueva línea. En nuestro caso será el separador de los objetos de la lista.

Por ejemplo con este bucle for podemos listar las posibles frecuencias de nuestro procesador (sólo si es compatible con cpufreq y tenemos el módulo del kernel cargado):

1
2
3
4
for j in `cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies`
do
echo "Frecuencia: "$j" Hz"
done

Es decir, hacemos que nuestra lista sea el resultado de la ejecución de “cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

Este método se parece mucho al for each que tienen muchos lenguajes.

Pero, ¿ qué tal el bucle for de toda la vida? ¿ Ése en el que contábamos de un número hasta otro número ? Podemos hacerlo de dos formas:

for i in ….

1
2
3
4
for i in `seq 4 90`
do
   echo $i
done

Donde en seq podemos colocar el número de inicio y el de fin, uno detrás de otro, si ejecutamos seq en el terminal generará una salida con un número en cada línea (aunque se puede cambiar con -s). Aunque también podremos modificar el incremento (2, 3, 4…). Por ejemplo:

$ seq 1 10 1000

que contará de 1 a 1000 de 10 en 10: 1..11..21..31…….991 (hasta 1000 no puede llegar porque se pasa)
Si queremos contar hacia atrás debemos especificar este incremento negativo:

$ seq 10 -1 0

que contará de 10 a 0: 10..9..8..7…….0

El método tradicional

1
2
3
4
for (( i=0; i<1000; i++ ))
do
   echo $i
done

¡ Anda ! Igual que en C, pero con dos paréntesis… además, éste método lo podemos extender un poco más. Lo mismo que podemos hacer:

for (( i=0; i<1000; i=i+5 ))

Para contar de 5 en 5

for ((i=1000; i>0; i=i-5))

Para contar de -5 en -5

podemos hacer lo siguiente:

for (( i=1000; i>0; i=`expr $i / 2`))

Para ir dividiendo el número entre dos, o:

for (( i=1; i<100; i=`expr $i \* 2`))

para ir multiplicando entre dos

En definitiva, cualquier operación que podamos hacer desde la línea de comando, por ejemplo con expr, aunque si se nos queda corto siempre podemos usar bc (haré un algún post sobre bc).

Con expr podemos hacer algunas operaciones matemáticas simples (suma, resta, multiplicación, división, resto, and y or) con los siguientes símbolos respectivamente (+, -, *, /, %, &, |), aunque *, & y | deben ser escapados con una \ (contra barra).
Cada elemento en la expresión debe ir con un espacio, dejo como ejemplo las siguientes expresiones:

$ expr 100 \& 0
0
$ expr 100 \& 1
100
$ expr 0 \& 3
0
$ expr 0 \| 3
3
$ expr 23 \* 45 + 20 - 4 % 3
1054
$ expr 3 % 2
1
$ a=3
$ expr $a + 1
4

Es cierto que expr hace muchas más cosas: podemos poner paréntesis para dar preferencia a algunas operaciones, y hay palabras clave para operar con cadenas de caracteres.

Foto: Spookuygonk (Flickr)

Uso de llaves en BASH

Sábado, 15 de Mayo de 2010 Gaspar Fernández 1 comentario

llaves
Leo en el blog de Thalskarth (proveniente de Tux Files, que a su vez venía de Slice of Linux) un truco para hacer copias de seguridad de un archivo con bash de la siguiente forma:

cp archivo{,.bk}

Lo que hacemos es parecido a escribir esto otro:

cp archivo archivo.bk

Por lo que podemos intuir fácilmente para qué valen las llaves en este contexto: replicar alternativas. Es decir escribiremos lo que hay antes de la llave, y lo terminaremos con cada una de las opciones de dentro de las llaves que están separadas por comas. Y lo más fácil para entender esto es utilizar echo.
Probaremos lo siguiente:

$ echo “Voy a pintar mi casa de “{verde,azul,rojo,amarillo}
Voy a pintar mi casa de verde Voy a pintar mi casa de azul Voy a pintar mi casa de rojo Voy a pintar mi casa de amarillo

Bien, el mensaje se replica con un espacio entre réplicas. Podemos ahora probar algo más:

$ echo -e “Voy a pintar mi casa de “{verde,azul,rojo,amarillo}”.\n”
Voy a pintar mi casa de verde.
Voy a pintar mi casa de azul.
Voy a pintar mi casa de rojo.
Voy a pintar mi casa de amarillo.

Además, podemos ver que la llave no tiene por qué estar al final del parámetro, podemos ponerla en mitad y sigue funcionando. Hay un espacio al principio de la línea, como mencioné antes, las frases irán separadas por un espacio (es normal, las llaves nos sirven para introducir nuevos parámetros). Podremos solucionarlo con \b (backspace).

echo -e “\bVoy a pintar mi casa de “{verde,azul,rojo,amarillo}”.\n”
Voy a pintar mi casa de verde.
Voy a pintar mi casa de azul.
Voy a pintar mi casa de rojo.
Voy a pintar mi casa de amarillo.

Antes de nada un apunte, no debemos dejar espacios dentro de las llaves, ni entre las comas, ni dentro de cada opción, no olvidemos que son parámetros, aunque sí que podríamos poner un espacio si éste va entre comillas (igual que ocurre con los parámetros).

Crear PDFs

Y a partir de aquí, sólo necesitamos imaginación, por ejemplo podemos ver cómo lo usamos para crear un pdf con imágenes en jpeg (de una carpeta llamadas test-N.jpg). Lo creamos con ImageMagick y sólo queremos de la 1 a la 12:

$ convert test-{?.,10.,11.,12.}jpg todos.pdf

Aunque hay un método mejor, {} (las llaves) soportan rangos, por lo que podemos hacer:

$ convert test-{1..12}.jpg todos.pdf

Comprimir directorios

Podemos, por ejemplo crear un archivo comprimido de un directorio con el mismo nombre de la siguiente manera:

$ tar cvjf test{.tar.bz2,/}

como sustitución a:

$ tar cvjf test.tar.bz2 test/

Diferencias

Encontrar la diferencia de un archivo con su backup (suponemos que el backup es el mismo nombre terminado en ~ (tilde de la ñ):

$ diff test{,~}

O para diferencias rutas (recordemos que podemos poner llaves entre parámetros:

$ diff ~/proyectos/www/{proyecto1,proyecto2}/www/lib/my_lib.h

Que es lo mismo que:

$ diff ~/proyectos/www/proyecto1/www/lib/my_lib.h ~/proyectos/www/proyecto2/www/lib/my_lib.h

Lectura de datos en scripts

Para leer desde la entrada estandar tenemos read, y si queremos introducir cada palabra en una variable podemos usar read palabra1 palabra2 palabra3, y si queremos que estas variables tengan un prefijo común:

$ read palabra{A,B,C}

Combinaciones y juegos

Vamos a hacer múltiples sumas con bc:

$ echo -e {1..4}”+”{4..1}”\n” | bc

Esto pondrá en pantalla el resultado de: 1+4, 1+3, 1+2, 1+1, …, 4+3, 4+2, 4+1.
Con esto vemos que los rangos no sólo van en incremento sino también en decremento.
Pero aún más si hacemos:

echo test{a..z}

Nos completará con testa testb testc…testx, testy, testz

Por último, un ejemplo más complicado, y que, aunque pocas veces nos sirva (más que nada por no acordarnos), ahí queda, llaves anidadas:

$ echo test-{1{10..12},3{9..1}}
test-110 test-111 test-112 test-39 test-38 test-37 test-36 test-35 test-34 test-33 test-32 test-31

Tenemos la posibilidad de introducir opciones dentro de otras opciones y todas se representarán seguidas. ¿Tal vez nos sirva alguna vez para crear un fichero de texto con datos de prueba? Para crear un archivo con temperaturas de varias ciudades:

$ echo -e “\b”{”Madrid 1″{1..4},”Barcelona 1″{3..5},”Malaga “{19..22},”Sevilla 2″{3..4}}”\n” > test

Esto creará un fichero llamado test con el siguiente contenido:

Madrid 11
Madrid 12
Madrid 13
Madrid 14
Barcelona 13
Barcelona 14
Barcelona 15
Malaga 19
Malaga 20
Malaga 21
Malaga 22
Sevilla 23
Sevilla 24

Foto: bohman (Flickr)

Visita otras webs de la red