Mostrando entradas con la etiqueta php. Mostrar todas las entradas
Mostrando entradas con la etiqueta php. Mostrar todas las entradas

lunes, 15 de abril de 2019

Cuando unos leguajes tratan los arrays por referencia y otros por copia

¿Qué ocurre cuando asignamos un array a otro? En la mayor parte de los lenguajes, lo habitual es que la nueva variable sea un puntero (referencia) al primer array. Por ejemplo, en Python:

array2 = array1 = {'uno': 1, 'dos': 2}

print('array1 antes:', array1)
print('array2 antes:', array2)

array2['dos'] = 22
array1['tres'] = 3

print('array1 después:', array1)
print('array2 después:', array2)


La salida de este script sería la siguiente:

~ $ python3 testarray.py 

array1 antes: {'uno': 1, 'dos': 2}
array2 antes: {'uno': 1, 'dos': 2}
array1 después: {'uno': 1, 'dos': 22, 'tres': 3}
array2 después: {'uno': 1, 'dos': 22, 'tres': 3}


Como era previsible, al cambiar algo en array1 o en array2, el otro array se modifica, porque las dos variables son una referencia al mismo array. (En Python a este tipo de datos se le llama diccionario, pero podemos asumir que es equivalente al array asociativo de PHP)

Este sencillo ejemplo es muy adecuado para explicar a alguien lo que son las referencias. Se ve claramente que cambiar algo en una variable tiene efectos colaterales.

Sin embargo, podemos encontrarnos sorpresas en otros lenguajes. PHP, por ejemplo, copia el array en la nueva variable y a partir de ese momento, son dos referencias independientes. Mucho cuidado si nuestro array contiene muchos datos, se duplica el consumo de memoria:

<?php 
$array1 = $array2 = array("uno" => 1, "dos" => 2);

echo "array1 antes:"; var_dump($array1); echo "\n";
echo "array2 antes:"; var_dump($array2); echo "\n";

$array1["tres"] = 3;
$array2["dos"] = 22;

echo "array1 después:"; var_dump($array1); echo "\n";
echo "array2 después:"; var_dump($array2); echo "\n";


La salida de este script es la siguiente:

~ $ php testarray.php 

array1 antes:array(2) {["uno"]=> int(1), ["dos"]=> int(2)}
array2 antes:array(2) {["uno"]=> int(1), ["dos"]=> int(2)}

array1 después:array(3) {["uno"]=> int(1), ["dos"]=> int(2), ["tres"]=> int(3)}
array2 después:array(2) {["uno"]=>, int(1), ["dos"]=> int(22)}

¿Qué ha pasado en este caso? Las dos variables son independientes, los cambios en una no afectan a la otra. Cada una apunta a una región de memoria diferente. En PHP se puede conseguir el mismo comportamiento que en otros lenguajes, esto es, que asignar una variable a otra por referencia:

<?php 
$array1 = array("uno" => 1, "dos" => 2);
$array2 = &$array1;

echo "array1 antes:"; var_dump($array1); echo "\n";
echo "array2 antes:"; var_dump($array2); echo "\n";

$array1["tres"] = 3;
$array2["dos"] = 22;

echo "array1 después:"; var_dump($array1); echo "\n";
echo "array2 después:"; var_dump($array2); echo "\n";

En este caso el comportamiento es el mismo que en el primer script en Python.
La expresión $array2 = &$array1asigna por referencia la variable 1 a la variable 2.

¿Qué comportamiento es deseable o más conveniente en un lenguaje de programación? La aproximación de PHP puede parecer más ineficiente, pero si reflexionamos un momento, tiene algo a su favor: es consistente.

<?php
$a = $b = 4;
$a = 5;
var_dump($a); var_dump($b);

La salida es:

int(5)
int(4)

Cada variable es una copia.

En Python, los tipos de datos simples sí que se asignan por copia:

Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
>>> a = b = 4
>>> a = 5
>>> a
5
>>> b
4
>>>

También se ve cómo son variables independientes. 

Todos los tipos de datos, simples y compuestos, se comportan igual PHP, sin embargo, en Python los tipos de datos compuestos (diccionarios, listas,... se asignan por referencia y los tipos de datos simples (enteros,...), se asignan por copia.

Esto no es bueno ni malo... pero hay que estar atento.

miércoles, 8 de agosto de 2018

Clean Code

Lectura recomendada:



Hay veces que repasando código ajeno uno se puede encontras cosas muy farragosas. Este método simplemente pretende buscar entre una lista de rangos cuál corresponde a un valor en concreto.
Por ejemplo: "A", para valores entre 100 y 51; "B" para valores entre 50 y 31; "C" para valores por debajo de 30.

Partíamos de este código, que prácticamente da miedo verlo:

  • El nombre del método es excesivamente largo y para un lector angloparlante, está mal escrito.
  • Los nombres de las variables también son muy largos y con elementos redundantes: "nameNosequé" es innecesario: toda variable se usa precisamente para nombrar algo.
  • La iteración es poco natural (con una condición negada como primer control del bucle) y usa construcciones del lenguaje (en este caso, la función "each" de PHP) que están "deprecated".
    Con nombres tan largos y esta asignación metida en el control del bucle, queda realmente ilegible:

    while (!isset($nameOfMatchedTransparencyRanking) && (list($transparencyRankingName, $arrayTransparencyRankingInterval) = each($arrayTransparencyRankings))) {
    // Cuerpo del bucle
    }
Para recorrer una estructura de datos de este tipo (array asociativo) es más natural y legible -y posiblemente, más eficiente- usar un bucle foreach.


No es la mejor implementación del mundo, pero al menos ya es más legible y se entiende un poco mejor qué hace este método. Si quisiéramos que fuese más genérico, y no estuviese atado a una aplicación en concreto, los nombres de variables podrían ser aún más genéricos (p.ej, $value en vez de $transparency).

Hacer un return entre medias del bucle no es muy académico ni formal, pero permite un código compacto y sencillo. Esta forma de escribir la utilizaban los mismísimos creadores de C, y en funciones cortas como esta, es perfectamente aceptable. En un método más largo, más allá de 10 líneas, personalmente, ya no lo utilizaría porque tendríamos un punto de salida descontrolado que no se ve claramente.



(Capturas del libro "The C Programming Language" de D. Ritchie y B. Kernighan. Si no sabes quiénes son estos señores, no sé que haces leyendo este blog ;-)

jueves, 20 de octubre de 2016

Euler #13, #14, #15 y #16

https://projecteuler.net/problem=13

Problema trivial, se resuelve en una sola línea Python:
   1 input = [37107287533902102798797998220837590246510135740250,
2 46376937677490009712648124896970078050417018260538,
3 74324986199524741059474233309513058123726617309629,
4 91942213363574161572522430563301811072406154908250,
5 23067588207539346171171980310421047513778063246676,
6 89261670696623633820136378418383684178734361726757,
7 28112879812849979408065481931592621691275889832738,

...
100 53503534226472524250874054075591789781264330331690]
101
102 print str(sum(input))[0:10]






https://projecteuler.net/problem=14

En PHP:
   1 <?php
2
3 function generateCollatz($num) {
4 $ret = array($num);
5 $next = end($ret);
6 while (true) {
7 $next = $next % 2 == 0 ? $next / 2 : 3*$next + 1;
8 $ret []= $next;
9 if ($next == 1) break;
10 }
11 return $ret;
12 }
13
14
15 $longest = 1;
16 $maxCount = 0;
17 for ($x = 1; $x < 1000000; $x++) {
18 $collatz = generateCollatz($x);
19 $nCollatz = count($collatz);
20 if ($nCollatz > $maxCount) {
21 $longest = $x;
22 $maxCount = $nCollatz;
23 echo "El número $x tiene $nCollatz términos\n";
24 }
25
26 }
27





https://projecteuler.net/problem=15

En Python, implementación recursiva. No es muy buena para valores muy grandes. De hecho, tras varias horas de ejecución, el script no termina.
   1 nPaths = 0;
2 def move_point(point, limit):
3 global nPaths
4 if point == limit:
5 # print("Finished!")
6 nPaths += 1
7 if point[1] < limit[1]:
8 # Continue down...
9 new_point = (point[0], point[1]+1)
10 # print("From", point, "to", new_point)
11 move_point(new_point, limit)
12 # And lets go right too
13 if point[0] < limit[0]:
14 new_point = (point[0]+1, point[1])
15 # print("From", point, "to", new_point)
16 move_point(new_point, limit)
17 elif point[0] < limit[0]:
18 new_point = (point[0]+1, point[1])
19 # print("From", point, "to", new_point)
20 move_point(new_point, limit)
21
22 move_point((0, 0), (20, 20))
23 print("N Paths", nPaths)
24

Probamos el mismo algoritmo en C:
   1 #include <stdio.h>
2
3 unsigned long n_paths = 0;
4 void move_point(int x, int y, int xmax, int ymax) {
5 if (x == xmax && y == ymax) {
6 n_paths++;
7 } else if (y < ymax) {
8 move_point(x, y + 1, xmax, ymax);
9 if (x < xmax) {
10 move_point(x + 1, y, xmax, ymax);
11 }
12 } else if (x < xmax) {
13 move_point(x + 1, y, xmax, ymax);
14 }
15 }
16
17 int main() {
18 move_point(0, 0, 20, 20);
19 printf("n_paths: %lu\n", n_paths);
20 return (0);
21 }
22

Ahora sí. En una hora el problema está resuelto. Se sabe que Python no se lleva especialmente con la recursividad.




https://projecteuler.net/problem=16

El problema 16 son tres líneas de código, se puede hacer el el mismo intérprete.
Python 2.7.11 (default, Aug  9 2016, 15:45:42) 
[GCC 5.3.1 20160406 (Red Hat 5.3.1-6)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> sum = 0
>>> for a in str(2**1000):
... sum += int(a)
...
>>> print sum
1366
>>>

martes, 11 de octubre de 2016

Euler #5, #6 y #7

https://projecteuler.net/problem=5
   1 #!/usr/bin/env python3
2
3 divisors = range(1, 20);
4 test = divisors[-1]
5 control = True
6 while control:
7 divisible = True
8 for d in divisors:
9 if test % d != 0:
10 divisible = False
11 break
12
13 if divisible:
14 control = False
15 else:
16 test += 1
17
18 print (test)
19





https://projecteuler.net/problem=6

JavaScript:
   1 
2 var sum = 0;
3 var squares = 0;
4 for (var i = 0; i <= 100; i++) {
5 sum += i;
6 squares += i*i;
7 }
8 console.log(sum * sum - squares);
9





https://projecteuler.net/problem=7

PHP:
   1 <?php
2 // https://projecteuler.net/problem=7
3
4 function isPrime($num) {
5 if ($num == 2) return true;
6 if ($num < 2 || $num % 2 == 0) return false;
7 $max = (int) sqrt($num);
8 for ($i = 3; $i <= $max; $i +=2) {
9 if ($num % $i == 0) {
10 return false;
11 }
12 }
13 return true;
14 }
15
16 $counter = 0;
17 $num = 2;
18 $stop = false;
19 while (true) {
20 if (isPrime($num)) {
21 $counter++;
22 if ($counter == 10001) {
23 echo "$num\n";
24 break;
25 }
26 }
27 $num++;
28 }
29

domingo, 9 de octubre de 2016

Euler #1 y #2

Hay que mantenerse en forma...

https://projecteuler.net/problem=1

Python:
   1 #!/usr/bin/env python3
2
3 sum = 0
4 for n in range(1, 1000):
5 if n % 3 == 0 or n % 5 == 0:
6 sum += n
7
8 print (sum)
9




JavaScript:
   1 var sum = 0;
2 for (var i=1; i<1000; i++) {
3 if (i % 3 == 0 || i % 5 == 0) {
4 sum += i;
5 }
6 }
7 console.log(sum)
8





https://projecteuler.net/problem=2

PHP (iterativo):
   1 <?php
2
3 function fib($max) {
4 $ret = array(1, 2);
5 $x = 3;
6 while (true) {
7 $new = $ret[$x-2] + $ret[$x-3];
8 if ($new <= $max) {
9 $ret []= $new;
10 $x++;
11 } else {
12 break;
13 }
14 }
15 return $ret;
16 }
17
18
19 $sum = 0;
20 foreach (fib(4000000) as $n) {
21 if ($n % 2 == 0) {
22 $sum += $n;
23 }
24 }
25
26 echo "$sum\n";
27