/**
* Name: Maps
* Author: Alexis Drogoul
* Description: Examples of the syntax and various operators used to manipulate the 'map' data type.
* Read the comments and run the model to get a better idea on how to use maps in GAML.
* Tags: map, loop
*/
model Maps
/* Maps is a data structure consisting of a list of pair where each key is unique */
species declaring_map_attributes {
/**
* Declarations of map attributes
*/
// The simplest declaration identifies empty_map as a map that can contain any type of objects.
// Its default value will be [] (the empty list/map) if it is not initialized.
map empty_map;
// To provide it with a literal initial value, use the '<-' (or 'init:') facet and pass a map
map explicit_empty_map <- [];
// Or, more explicitely
map explicit_empty_map2 <- map([]);
// Values can be declared litterally in this map, which is nothing more than a list of pair objects
map explicit_filled_map <- ["First"::1, "Second"::2];
// If a map is initialized with a list that contains non-pair objects, the pairs element::element are added to the map
map map_initialized_with_list <- map([1,2,3,4]);
// maps can be declared so that they only accept a given type of keys and values
// For instance, empty_map_of_int will accept string keys and integer values
map empty_map_of_int;
// The appropriate casting is realized if the map is initialized with a list of values
map map_of_int_with_init_of_string <- map(['10', '20']); // => ['10'::10,'20'::20]
// or with another map
map map_of_float_with_init_of_map <- map(map_initialized_with_list);
// When the casting is not obvious, the default values are used
// Here, the list is first casted to return pairs, and they are casted to pair
map map_of_float_with_impossible_casting <- map(['A','B']);
// maps can of course contain maps
map map_of_maps <- map(['A'::[], 'B'::[]]);
// untyped maps can contain heterogeneous objects
map untyped_map <- [10::'5','11'::5,[12]::[5]];
// the casting applies to all elements when a key and contents type is defined
map recasted_map_with_int_and_string <- map(untyped_map); //=> [5,5,0]
init {
write "";
write "== DECLARING MAPS ==";
write "";
write sample(empty_map);
write sample(explicit_empty_map);
write sample(empty_map_of_int);
write sample(explicit_filled_map);
write sample(map_initialized_with_list);
write sample(map_of_int_with_init_of_string);
write sample(map_of_float_with_init_of_map);
write sample(map_of_float_with_impossible_casting);
write sample(map_of_maps);
write sample(untyped_map);
write sample(recasted_map_with_int_and_string);
write "";
// Declaring temporary variables of type map follows the same pattern
map map_of_string <- [1::'A',2::'B',3::'C'];
// maps are not always declared litterally and can be obtained from various elements
// by using the casting 'map()' operator
// for instance, map(species_name) will return a list of all the agents of species_name
// using pairs of agent::agent. If the key is explicit, it is used in the casting:
create test_species number:4;
map my_agents <- map(test_species);
write sample(my_agents);
// Some special casting operations are applied to specific types, like agents (returns a copy of their attributes)
write sample(map(any(my_agents)));
// The 'as_map(pair)' iterator operator also provides a way to build more complex maps
write sample(list("This is a string") as_map (length(each)::each));
// As well as "group_by" or "index_by"
write sample(my_agents index_by (each distance_to {0,0}));
write sample([1,2,3,4,5,6,7] group_by ((each mod 3) = 0));
}
}
species test_species{}
species accessing_map_elements {
map l1 <- map([1,2,3,4,5,6,7,8,9,10]);
map l2 <- [1::'this',2::'is',3::'a',4::'list', 5::'of',6::'strings'];
init {
write "";
write "== ACCESSING MAPS ELEMENTS ==";
write "";
write sample(l1);
write sample(l2);
write sample(first(l1));
write sample(last(l1));
write sample(l1 at 1);
write sample(l1[1]);
write sample(length(l1));
write sample(mean(l1));
write sample(max(l1));
write sample(min(l1));
write sample(any(l1));
write sample(3 among l1);
write sample(l1 contains 1);
write sample(l1 contains_all [1,4,6, 14]);
write sample(l1 contains_any [1,23]);
write sample(reverse(l2));
write sample(l1 collect (each + 1));
write sample(l1 collect (norm({each, each, each})));
write sample(l1 where (each > 5));
write sample(l1 count (each > 5));
write sample(l1 group_by (even(each)));
write sample(l2 index_by (each + "_index"));
write sample(l1 index_of 100);
write sample(l2 last_index_of 'is');
write sample(l2 sort_by each);
write sample(l2 sort_by length(each));
write sample(l2 first_with (first(each) = 'o'));
write sample(l2 where (length(each) = 2) );
write sample(l2 with_min_of (length(each)));
write sample(l2 with_max_of (length(each)));
write sample(l2 min_of (length(each)));
write sample(l2 max_of (length(each)));
}
}
species combining_maps {
map l1 <- map([1,2,3,4,5,6,7,8,9,10]);
map l2 <- map([1,3,5,7,9]);
init {
write "";
write "== COMBINING MAPS ==";
write "";
write sample(l1);
write sample(l2);
write sample(l1 + l2);
write sample(l1 - l2);
write sample(l1 inter l2);
write sample(l1 union l2);
map l3 <- map(l1 + l2);
write "map l3 <- l1 + l2; " + sample(l3);
}
}
species modifying_maps {
init {
write "";
write "== MODIFYING MAPS ==";
write "";
// Besides assigning a new value to a map, maps can be manipulated using
// the "add", "remove" and "put" statements.
// Let's define an empty list supposed to contain integer keys and values
trace { map m1 <- [0::0];
// and add some stuff to it using "add"
add 1::1 to: m1;
add 2::2 to: m1;
add 3::3 to: m1;
write sample(m1);
// the same can be done with the compact syntax introduced in GAMA 1.6.1 for "add"
m1 <+ 4::4;
m1 <+ 5::5;
write sample(m1);
// tired of writing lines of add ? The "all:" facet is here to serve:
add [6, 7, 8, 9] to: m1 all: true;
// or, in a more compact way:
m1 <<+ [10,11,12,13];
write sample(m1);
// automatic casting applies to any element added to the map
m1 <+ int("14");
// as well as any container of elements
m1 <<+ map([15::"15", 16::16.0]);
write sample(m1);
// elements are by default added to the map while their keys are unique
// So, what about replacing some elements once they have been added ?
// "put" can be used for that purpose
put -2 at: 0 in: m1;
// or, more simply:
m1[0] <- -3;
// Trying to put an element outside the "bounds" of the map will of course not yield an error
m1[20] <- 10;
write sample(m1);
// And what about replacing all the values with a new one ?
m1[] <- 0;
write("m1[] <- 0;");
write sample(m1);
// Well, m1 is a bit boring now, isnt't it ?
// Let's fill it again with fresh values
loop i over: m1.keys {
m1[i] <- rnd(3);
}
write(sample(m1));
// To remove values from it, the "remove" statement (and its compact ">-" form) can be used
// WARNING: this form operation on the *values* of the map (i.e. it will remove the first pair
// whose value = 0
remove 0 from: m1;
// it can also be written
m1 >- 0;
write(sample(m1));
// To remove all occurrences of pairs with a specific value, "all:" (or ">>-") can be used
// For instance:
m1 >>- 2;
// or, written using the long syntactic form
remove 1 from: m1 all: true;
write sample(m1);
// To remove keys instead, the same syntax can be used, but on the keys of the map (i.e. map[])
m1[] >- 1; // This will remove the (unique) pair whose key = 1
// The equivalent long syntax is
remove key: 1 from: m1;
// To remove a set of keys, the following syntax can be used
m1[] >>- [2,3,4];
// And to remove all the keys present in a given map (using the 'keys' attribute)
m1[] >>- m1.keys;
write sample(m1);
// By all means, m1 should now be empty! Let's fill it again
int i <- 0;
loop times: 20 {
i <- i + 1;
m1 <+ i::rnd(3);
}
// Random things to try out
// Using the 'pairs' attribute: all number now vary from 1000 to 1003
m1 <- m1.pairs as_map (each.key::((each.value) + 1000));
write sample(m1);
// Removing values based on a criteria
m1 >>- m1 select (each > 1001);
write(sample(m1));
}
}
}
species looping_on_maps {
init {
write "";
write "== LOOPING ON MAPS ==";
write "";
// Besides iterator operators (like "collect", "where", etc.), which provide
// functional iterations (i.e. filters), one can loop over maps using the imperative
// statement 'loop'
list strings <- list("This a list of string");
write sample(strings);
map l1 <- strings as_map (first(each)::each);
write sample(l1);
int i <- 0;
list l2 <- [];
// Here, the value of 's' will be that of each value of each pairs of the list
loop s over: l1 { // equivalent to 'loop s over: l1.values'
i <- i + 1;
l2 << "Word #" + i + ": " + s;
}
write sample(l2);
// To loop on the keys of l1, simply use its 'keys' attribute
l2 <- [];
i <- 0;
loop s over: l1.keys{
i <- i + 1;
l2 << "Key #" + i + ": " + s;
}
write(sample(l2));
// Looping on indexes allows to gain access to each element in turn
l2 <- [];
loop k over: l1.keys {
l2 <+ l1[k];
}
write sample(l2);
// Finally, maps containing agents can be the support of implicit loops in the 'ask' statement
create test_species number: 5 returns: my_agents;
map map_of_agents <- map(my_agents);
write(sample(map_of_agents));
l2 <- [];
ask map_of_agents{
// attributes of each agent can be directly accessed
l2 << name;
}
write sample(l2);
// Of course, this can be done more simply like this
l2 <- map_of_agents collect each.name;
}
}
experiment Maps type: gui {
user_command "Declaring maps" {create declaring_map_attributes;}
user_command "Accessing maps" {create accessing_map_elements;}
user_command "Combining maps" {create combining_maps;}
user_command "Modifying maps" {create modifying_maps;}
user_command "Looping on maps" {create looping_on_maps;}
}