/**
* Name: Lists
* Author: Alexis Drogoul
* Description: Examples of the syntax and various operators used to manipulate the 'list' data type. 
* Read the comments and run the model to get a better idea on how to use lists in GAML. 
* Tags: list, loop
*/

model Lists

species declaring_list_attributes {
	
	/**
	 * Declarations of list attributes
	 */
	 // The simplest declaration identifies empty_list as a list that can contain any type of objects. 
	 // Its default value will be [] (the empty list) if it is not initialized.
	list empty_list;
	// To provide it with an initial value, use the '<-' (or 'init:') facet
	list explicit_empty_list <- [];
	// lists can also be provided with a default size, in which case they are filled with the nil element
	list list_of_size_10 <-[];// list_size(10); // => [nil,nil,nil,nil,nil,nil,nil,nil,nil,nil]
	// to fill them with a given initial value, use the 'list_with' operator
	list list_of_size_10_with_0 <- list_with(10, 0); // => [0,0,0,0,0,0,0,0,0,0]
	
	// lists can be declared so that they only accept a given type of contents.
	// For instance, empty_list_of_int will only accept integer elements
	list empty_list_of_int ;
	
	// the value passed to 'list_with' is verified and casted to the contents type of the list if necessary
	list list_of_int_size_10_filled_with_string<- list(list_with(10,'1')); // list_of_int_size_10_filled_with_string is filled with the casting of '1' to int, i.e. 1
	list list_of_string_size_10_filled_with_string <- list_with(10,'1'); // while list_of_string_size_10_filled_with_string is filled with the string '1'
	// the casting is also realized if the list is initialized with a value
	list list_of_int_with_init_of_string <- list(['10', '20']); // => [10,20]
	list list_of_float_with_init_of_string <- list(list_of_string_size_10_filled_with_string); // => [1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
	// When the casting is not obvious, the default value is used
	list list_of_float_with_impossible_casting <- list(['A','B']); 
	// lists can of course contain lists
	list list_of_lists <- list_with (5,[]);
	// untyped lists can contain heterogeneous objects
	list untyped_list <- ['5',5,[5]];
	// the casting applies to all elements when a contents type is defined (note the default last value of 0)
	list recasted_list_with_int <- list(untyped_list); //=> [5,5,0]

	
	init {
		write "";
		write "== DECLARING LISTS ==";
		write "";
		write sample(empty_list);
		write sample(explicit_empty_list);
		write sample(list_of_size_10);
		write sample(list_of_size_10_with_0);
		write sample(empty_list_of_int);
		write sample(list_of_int_size_10_filled_with_string);
		write sample(list_of_string_size_10_filled_with_string);
		write sample(list_of_int_with_init_of_string);
		write sample(list_of_float_with_init_of_string);
		write sample(list_of_float_with_impossible_casting);
		write sample(list_of_lists);
		write sample(untyped_list);
		write sample(recasted_list_with_int);
		write "";
		// lists are not always declared litterally and can be obtained from various elements
		// by using the casting 'list()' operator
		// for instance, list(species_name) will return a list of all the instances of species_name
		create test_species number:4;
		list my_agents <- list(test_species);
		write sample(my_agents);
		list my_names <- my_agents collect each.name;
		write sample(my_names);
		// 'create' can directly declare (and return) a list of the agents created. For instance:
		create test_species number: 5 returns: my_agents2;
		write (sample(my_agents2));
		// Some special casting operations are applied to specific types, like points...
		write sample(list(any(my_agents).location));
		// ... colors ...
		write sample(list(°pink));
		// ... or strings
		write sample(list("This is a string"));
		
 	}
}

species test_species{}

species accessing_list_elements {
	list l1 <- [1,2,3,4,5,6,7,8,9,10,2];
	list l2 <- ['this','is','a','list', 'of','strings'];
	init {
		write "";
		write "== ACCESSING LIST 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 l2);
		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(l1 index_of 2);
		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))); 
		write sample(copy_between(l2,1,3));
		write sample(copy_between(l2, 1, length(l2) - 1));
		write sample(l2 as_map (length(each)::"new"+each));
		// Ranges
		write sample(l2[1::3]);
	}
}

species combining_lists {
	list l1 <- [1,2,3,4,5,6,7,8,9,10];
	list l2 <- [1,3,5,7,9];
	list useful_list_of_lists <- [['A','B'],['C','D']]; 
	init {
		write "";
		write "== COMBINING LISTS ==";
		write "";
		write sample(list>([[1,2,3]]));
		write sample(l1);
		write sample(l2);
		write sample(l1 + l2);
		write sample(l1 - l2);
		write sample(l1 inter l2);
		write sample(l1 union l2);
		write sample(interleave ([l1,l2]));
		list l3 <- list(l1 + l2);
		write "list l3 <- l1 + l2; " + sample(l3);
		write sample(l1 as list);
		write sample(cartesian_product(useful_list_of_lists));
	}
}

species modifying_lists {
	init {
		write "";
		write "== MODIFYING LISTS ==";
		write "";
		trace {
		// Besides assigning a new value to a list, lists can be manipulated using
		// the "add", "remove" and "put" statements. 
		// Let's define an empty list supposed to contain integer elements
		list l1;
		// and add some stuff to it using "add"
		add 1 to: l1;
		add 2 to: l1;
		add 3 to: l1;
		write sample(l1);
		// the same can be done with the compact syntax introduced in GAMA 1.6 for "add"
		l1 <+ 4;
		l1 <+ 5;
		write sample(l1);
		// tired of writing lines of add ? The "all:" facet is here to serve:
		add all: [6, 7, 8, 9] to: l1;
		// or, in a more compact way:
		l1 <<+ [10,11,12,13];
		write sample(l1);
		// modifyng ranges of elements is also possible
		l1[1::3] <- 100;
		write sample(l1);
		// automatic casting applies to any element added to the list
 		l1 <+ (int("14"));
		// as well as any container of elements
		l1 <<+ (list(["15", 16.0]));
		write sample(l1);
		// elements are by default added to the end of the list
		// but they can be introduced at specific positions using the "at:" facet
		add 0 to: l1 at: 0;
		// or
		l1[0] +<- 0;
		// what about replacing some elements once they have been added ?
		// "put" can be used for that purpose
		put -2 at: 0 in: l1;
		// or, more elegantly:
		l1[0] <- -2;
		// Trying to put an element outside the bounds of the list will yield an error
		// l1[20] <- 10; will return the error "Index 20 out of bounds of l1"
		write sample(l1);
		// And what about replacing all the values with a new one ?
		l1[] <- 0;
		write sample(l1);
		// Well, l1 is a bit boring now, isnt't it ?
		// Let's fill it again with fresh values
		loop i from: 0 to: length(l1) -1 {
			l1[i] <- rnd(3);
		}
		write(sample(l1));
		// To remove values from it, the "remove" statement (and its compact forms) can be used
		// For instance, let's try to remove its first element
		remove first(l1) from: l1;
		// it can also be written
		l1 >- first(l1);
		write(sample(l1));
		// To remove all occurrences of a specific element, "all:" (or ">>-") can be used
		// For instance:
		l1 >>- 2;
		l1 >>- 1;
		// or, written using the long syntactic form
		remove all: 1 from: l1;
		write sample(l1);
		// To remove all the elements present in a given container, the same syntax can be used
		l1 >>- [0,3];
		write ("After l1 >>- [0,3] : " + sample( l1));
		// By all means, l1 should now be empty! Let's fill it again
		loop times: 20 {
			l1 <+ rnd(3);
		}
		write(sample(l1));
		// It is also possible to remove an index rather than a value (this will remove the 
		// value present at this index )
		l1[] >- 2;
		write("After l1[] >- 2 : " + sample(l1));
		// Removing several indexes can be written using a syntax similar to the one used
		// for removing values
		l1[] >>- [1,2,4];
		write("After l1[] >>- [1,2,4] : " + sample(l1));
		// Random things to try out
		// Using casting back and forth: all number now vary from 1000 to 31000
		l1 <- list(l1 collect (string(each) + "1000"));
		write sample(l1);
		// Removing elements based on a criteria
		l1 >>- l1 select (each > 20000);
		// Removing duplicates
		l1 <- remove_duplicates(l1);
		// Another way (see "../Maps.gaml")
		l1 <- map(l1).values; 
		write(sample(l1));
	}}
	
}

species looping_on_lists {
	init {
		write "";
		write "== LOOPING ON LISTS ==";
		write "";
		// Besides iterator operators (like "collect", "where", etc.), which provide 
		// functional iterations (i.e. filters), one can loop over lists using the imperative
		// statement 'loop'
		list l1 <- list("This is a list of strings");
		write sample(l1);
		int i <- 0;
		list l2 <- [];
		// Here, the value of 's' will be that of each element of the list
		loop s over: l1 {
			i <- i + 1;
			l2 << "Word #" + i + ": " + s;
		}
		write sample(l2);
		// 'loop' can also directly use an integer index (remember lists have a zero-based index)
		l2 <- [];
		loop k from: 0 to: length(l1) - 1 step:2 {
			l2 << l1[k];
		}
		write sample(l2);
		// Finally, list containing agents can be the support of implicit loops in the 'ask' statement
		create test_species number: 5 returns: my_agents;
		l2 <- [];
		ask my_agents{
			// attributes of each agent can be directly accessed
			l2 << name;
		}
		write sample(l2);
		// ... which is formally equivalent to:
		l2 <- [];
		l2 <<+ my_agents collect each.name;
		write sample(l2);
		// ... or, even simpler (since the casting of an agent to string returns its name)
		list l3 <- list(my_agents);
		write sample(l3);
		// Powerful filter expressions can be built by combining the various 'iterator' operators
		l3 <- list(my_agents where even(int(each))) collect ("Agent " + each + " has an even id");
		write sample(l3);
	}
}

experiment Lists type: gui {
	user_command "Declaring lists" {create declaring_list_attributes;}
	user_command "Accessing lists" {create accessing_list_elements;}
	user_command "Combining lists" {create combining_lists;}
	user_command "Modifying lists" {create modifying_lists;}
	user_command "Looping on lists" {create looping_on_lists;}	
}