« NetLogo: Conjuntos de… « || Inicio || » NetLogo: Interfaz Grá… »

NetLogo: Procedimientos Anónimos, Iteración en Listas

Última modificación: 9 de Noviembre de 2017, y ha tenido 7 vistas

Etiquetas utilizadas: || || ||

Procedimientos Anónimos

Los procedimientos anónimos permiten almacenar código que se ejecutará más tarde (o definir pequeños comandos que no requieren de un nombre para su uso inmediato, como veremos un poco más adelante). Al igual que los procedimientos normales de NetLogo, un procedimiento anónimo puede ser una acción o un reporte.

Los procedimientos anónimos son valores, lo que significa que pueden transmitirse como entrada, notificarse como resultado o almacenarse en una variable y, en consecuencia, no se pueden aplicar directamente sobre datos de entrada (necesitaremos un artificio para ello). Esta característica hace que esta capacidad de crear procedimientos anónimos y tratarlos como datos aproxima a NetLogo un paso más a los lenguajes funcionales, y permite (junto con algunas primitivas que veremos después) reescribir costosos procedimientos de una forma más directa, ahorradora, y segura.

Un procedimiento anónimo, al ser únicamente el cuerpo de un procedimiento, puede ejecutarse una vez, varias veces o no hacerlo en absoluto. En otros lenguajes de programación, los procedimientos anónimos se conocen como funciones de primera clase, cierres o lambda.

Primitivas de Procedimientos Anónimos

Las primitivas específicas relacionadas con los procedimientos anónimos son:

 -> , is-anonymous-command?,  is-anonymous-reporter?

-> permite crear un procedimiento anónimo (acción o reporte). Por ejemplo,

[ -> fd 2 ] 

genera una acción anónima, porque fd es una acción, mientras que

[ -> count turtles ] 

devuelve un reporte anónimo, porque count es un reporte.

Hay primitivas de NetLogo que requieren procedimientos anónimos como entrada:

foreach, map, reduce, filter, n-values, sort-by

Sin embargo, y como veremos los ejemplos iniciales que trabajan sobre listas en el siguiente apartado, puede no ser necesario usar -> si el procedimiento anónimo contiene una primitiva simple que solo requiere los datos directos que le pasa la primitiva correspondiente. Por ejemplo, podemos escribir:

foreach lista show

en vez de:

foreach lista [ [x] -> show x ]

aunque esta última opción también es correcta.

Para ejecutar un procedimiento anónimo sobre datos de entrada se pueden usar las primitivas run (para el caso de acciones anónimas) y run-result (para el caso de reportes anónimos), y ambos pueden recibir los datos de entrada que el procedimiento anónimo espera. Su forma general es:

(run accion-anonimo par1 ... parN) 
(run-result reporte-anonimo par1 ... parN)

Si no se pasan parámetros adicionales, porque el procedimiento anónimo no los necesita, entonces se pueden evitar los paréntesis exteriores.

Parámetros de entrada para los Procedimientos Anónimos

  • Si el procedimiento anónimo no recibe datos de entrada:
[ -> comandos ]
  • Si el procedimiento anónimo recibe un único dato de entrada:
[ x -> comandos (que usan x) ]
  • Si el procedimiento anónimo usa 2 o más datos de entrada:
[ [x1 ... xn] -> comandos (que usan x1 ... xn) ]

Por ejemplo:

[ -> show "Anuncio" ] 
[ x -> 2 * (x + 1) ]
[ [x y] -> ask x [ rt y ] ]

Cuando se usan procedimientos simples en las primitivas anteriores se puede usar una sintaxis más concisa que facilita la comprensión del código, por ejemplo:

map abs [1 -2 3 -4] = map [x -> abs x] [1 -2 3 -4] => [1 2 3 4] 
reduce + [1 2 3 4] = reduce [[ x y] -> x + y] [1 2 3 4] => 10
filter is-number? [1 "x" 3] = filter [x -> is-number? x] [1 "x" 3] => [1 3]
foreach [1 2 3 4] print = foreach [1 2 3 4] [x -> print x] => imprime desde 1 hasta 4

Se deben tener en cuenta algunas limitaciones actuales (que podrán ser eliminadas en versiones posteriores):

  • Los procedimientos anónimos no pueden aceptar una cantidad variable de entradas.
  • Los reportes anónimos solo pueden contener expresiones simples "de una línea". Por ejemplo, se debe usar ifelse-valuet, y no if, y no se debe usar la primitiva report. Si el código que se desea usar es demasiado complejo, se recomienda (o puede ser imprescindible) usar un reporte definido para tal fin, y llamarlo desde el reporte anónimo.
  • Solo las primitivas referenciadas anteriormente pueden admitir procedimientos anónimos, por lo que no sustituyen a bloques de comandos en otros contextos.
  • La sintaxis concisa explicitada anteriormente solo es válida para esas primitivas (o para primitivas de extensiones que se hayan definido de forma adecuada para ello), pero no para procedimientos normales desarrollados por el usuario.

Listas

Iteraciones sobre listas

Debido a la estructura de las listas, una de las operaciones más comunes que se realiza sobre ellas es la recursión o iteración. NetLogo incluye algunas funciones que permiten realizar diversos tipos de iteración sobre esta estructura:

  • foreach se usa para ejecutar una secuencia de comandos sobre cada elemento de la lista.
    foreach ls [proc-anonimo]

    Por ejemplo:

    foreach [2 4 6]
                [ x ->
    crt x
    show x + " tortugas creadas"]

    Una forma más compleja de este comando permite usar varias listas de entrada, L_1,..., L_n, no solo una, todas del mismo tamaño, y de forma que en la  i-ésima iteración del procedimiento anónimo, que debe tomar n datos de entrada, considera el conjunto de los n elementos i-ésimos de cada lista en el orden dado. Es decir:

    (foreach [a_11 ... a_1k] ...[a_n1 ... a_nk] 
                [ [x1 ... xn] -> comandos ] )

    Por ejemplo:

    (foreach [1 2] [3 4]
    [[x y] -> print (x * y)])
  • map es similar a foreach, pero no devuelve una acción, sino un resultado:
    map f [a_0 ... a_n] --> [f(a_0) ... f(a_n)]

    Por ejemplo:

    map [x -> x + 1] [1 2 3 4] = [2 3 4 5]
    map round [1.2 2.4 4.6] = [1 2 5]

    Al igual que ocurría en el caso anterior, esta función también admite el uso de varias listas de entrada, en este caso:

    (map f [a_11 ... a_1k] ...[a_n1 ... a_nk]) = [f(a_11 ... a_n1) ... f(a_1k ... a_nk)]

    Por ejemplo:

    (map [[x y] -> x * y] [1 2] [3 4]) = [3 8]

    Otras funciones que hacen uso de la recursión sobre listas son:

  • reduce: Itera el proceso de reducir los dos primeros elementos de la lista en uno por medio de la función f, hasta que devuelve un solo elemento. Es decir:
    reduce f [a_0 ... a_n] = f(...f(f(a_0,a_1),a_2),...,a_n)

    Por ejemplo:

    reduce + [1 2 3 4] = 10
    reduce and [True False True True] = False
  • filter: devuelve la lista formada por los elementos de ls que verifican el predicado.
    filter predicado ls

    Por ejemplo:

    filter [ x -> x > 0 ] [-1 -2 4 5] = [4 5]
  • n-values: genera una lista a partir de un procedimiento anónimo
    n-values n f = map f [0 ... n-1]

    Por ejemplo:

    n-values 5 [x -> 2 * x] = [0 2 4 6 8]
  • sort-by: ordena la lista haciendo uso del predicado binario que se da como procedimiento anónimo
    sort-by predicado [a1 ... an] 

    Por ejemplo:

    sort-by [ [x y] -> first x < first y] [ [2 4] [1 5] [3 3] ] = [ [1 5] [2 4] [3 3] ]
    sort-by [ [x y] -> last x < last y] [ [2 4] [1 5] [3 3] ] = [ [3 3] [2 4] [1 5] ]

Listas de agentes

Habitualmente, los conjuntos de agentes se toman en orden aleatorio (distinto cada vez que se accede a ellos). A veces puede ser necesario fijar un orden en el conjunto de agentes y para ello trabajamos con lo que se llaman listas de agentes, que no son más que listas de agentes identificados por sus identificadores. Una vez hecho esto, podemos trabajar con la lista de agentes como haríamos con cualquier otra lista, por ejemplo, usando los comando sort y sort-by para ordenarlos. Se debe recordar que a pesar de que estas funciones pueden recibir como entrada un conjunto de agentes, su devolución es siempre una lista (que no es exactamente lo mismo).

Si se usa sort sobre un conjunto de agentes de tipo tortuga, el resultado es una lista de tortugas ordenadas de forma ascendente por el identificador (el que se almacena en la variable interna de agente who). Si se aplica sobre un conjunto de agentes de tipo patch, el resultado será una lista de patches ordenados de izquierda a derecha y de arriba a abajo. Si se desea el orden inverso, podemos hacer uso combinado del comando reverse.

Cómo trabajar sobre una lista de Agentes

Lo más probable es que una vez que tengamos una lista de agentes, queramos pedirles que hagan algo. El problema es que el comando ask solo se puede realizar sobre conjuntos de agentes, no sobre listas de agentes. Para conseguir el mismo resultado hemos de combinar el uso de foreach y ask de la siguiente forma:

foreach lista-agentes 
    [ x -> ask x lista-comandos]

Se debe tener en cuenta que por el método anterior, los agentes de la lista ejecutan los comandos de forma no concurrente, es decir, cada agente ejecuta todos los comandos antes de que comience el siguiente, y siempre se hace el recorrido siguendo el orden que los agentes ocuparan en la lista.

Listas de Propiedades de Agentes

Para quedarnos con la lista de todos los valores de una cierta propiedad de un grupo de agentes basta usar la partícula of:

[propiedad] of conjunto-agentes

Y podemos usar una propiedad directamente, o incluso un reporte que trabaja sobre los agentes y devuelve un valor (posiblemente, usando sus propiedades). Por ejemplo:

[who] of turtles

devolverá una lista de identificadores de las tortugas que haya en el mundo.

[ max contenido ] of patches

devolverá, suponiendo que cada patch tiene una propiedad contenido (que es una lista), una lista con los máximos de los contenidos de cada patch.

« NetLogo: Conjuntos de… « || Inicio || » NetLogo: Interfaz Grá… »