Eve Collections

Collections are composite types represents groups of elements of the same type. In Eve collections are actually generics. Usually we declare collections using "gradual typing" technique but it is possible to use an explicit declaration.

Ordinal

Ordinal is an generic class representing a set of unique ordered symbols. Each symbol represents an integer value starting from N to capacity C. Element values start with 1, or with a specific number, and grow with one for each element: class Color = {Red, Green} <: Ordinal; is short for class Color = {Red:1, Green:2} <: Ordinal;. The type Logic starts with 0: {False:0, True} gives False = 0 and True = 1.

A brace literal with plain names is normally a DataSet. In a class declaration, {Red, Green} is the exception: it declares the elements of an ordinal. Every class names its superclass after <:.

Pattern:

# Define a Ordinal subtype
primary aspect test_ordinal is

  class TypeName = {name1:0, name2, name3} <: Ordinal;

  process main is
    new a := TypeName.name1; ** a=0
    new b := TypeName.name2; ** b=1
    new c := TypeName.name3; ** c=2
  return;
end test_ordinal;

Note: When an element name start with capital letter there is no need to use qualifiers for the individual values. This is a language rule: capitalized names become part of the scope where the ordinal is visible. When a module exports the ordinal, the script that imports it with Color(*) gets Red and Green as constants of its own scope: it writes Red, not Color.Red, and the names are reserved there. The superclass <: Ordinal is always written.

## Declare public elements in enumeration
primary aspect constant_ordinals is

  class TypeName = {Name1:10, Name2} <: Ordinal;

  process main is
    new a := Name1; ** a = 10
    new b := Name2; ** b = 11
    ** verify values
    expect a == 10;
    expect b == 11;
  return;
end constant_ordinals;

List

A list is a sequence of elements having a dynamic capacity. Elements are stored in the order you have create them. The order can change if you sort the list or add/remove elements. However you can't insert elements in the middle.

Syntax:

#declare two lists
** empty list
  new list1 : ()Type;

** type inference
  new list2 := (value1, value2, ...);

** using constructor
  new list3 := List(value1,value2,...);

Notes:

  • The elements of a list have the same type;
  • Access to all or each element is sequential;
  • The constructor List(...) is optional: the literal (1,2,3) is already a list, not a tuple;
  • A list with one element ends with a comma: (x,). For example let A := (5,); is a list of length 1. Without the comma, (x) is only a group: (2 + 3) * 4 is 20 and let B := (5); is the number 5, not a list;
  • () is the empty list. {} is the empty set and {:} is the empty map.

Examples:


# empty list declaration (no element type)
  new e_list := ();
  new x_list :List;

# explicit declarations
  new n_list = (1,2,3) :()Integer;
  new c_list = ('a','b','b') :()Rune;
  new s_list = ("a","b","c") :()String;
  new o_list = ({a:1},{b:2},{c:3}) :()Object;

list literals

  • Lists are enclosed in round brackets ();
  • Elements are separated by comma,
  • All elements are having the same type.

Examples:

Literals can be used for initialization of variables:

# initialized list with type inference
  new c_list := ('a', 'b', 'c');
  new n_list := (1, 2, 3);

Literals can be used in assign expressions:

# Defer initialization
primary aspect begin_initial is
  ** define empty list
  set c_list :List;
  process main is
    ** initialize the list with integers
    let c_list := (1,2,3);
  return;
end begin_initial;

List operations

There are many things you can do with a list. You can add/remove elements at both ends. You can deconstruct a list and you can concatenate lists together. Most important features are implemented as built-in functions or special operators:

List builder

You can populate a list from a range using a function or expression. List builder are similar to Array builders. Also you can append in bulk new elements into an existing list.


#list builder demo
primary aspect list_builder is
  process main is
    ** initialize list from range
    new a := (a   | a in ('A'..'F'));
    print a;

    ** initialize list from another list
    new b := (x^2 | x in (1, 2, 3, 4));
    print b;

    ** exclude the first value with a range open at start
    new c := (x | x in (2>..6));
    print c;
  return;
end list_builder;
'A' 'B' 'C' 'D' 'E' 'F'
 1 4 9 16
 3 4 5 6

Note: The first generator must be a range or a collection. It can be followed by a condition, which is optional, joined with and. The range (2>..6) excludes 2.


** keep only the even numbers
new evens := (x | x in (1..10) and x % 2 == 0);
expect evens == (2,4,6,8,10);

List deconstruct

The deconstruct is basically the inverse of a builder. You create individual variables from a list. This is useful to clone or borrow specific parts of a list.


#list deconstruct demo
primary aspect list_deconstruct is
  set a : List;
  process main is
    ** initialize list from Range
    let a := (1..10);

    ** create new variables using clone deconstruct
    new x, y, _, *rest, _ :: a;

    ** check new variables
    expect x == 1;
    expect y == 2;

    ** anonymous variable vanish the value
    expect _ == null;

    ** check the rest
    print rest;
    print a;
  return;
end list_deconstruct;
(4,5,6,7,8,9)
(1,2,3,4,5,6,7,8,9,10)

Note: variable "_" is special. It can consume one single element and is always null. It can be re-declared several times in the same block. It can be written, but it stays null: whatever value you put inside is forgotten. The star "*" alone is the same, but it consumes many elements. Use "*name" to collect them.

Append to list

List is using operator +> or <+ to grow. This operator represent insert or append. You can insert an element or many elements into a list at the beginning or you can add element or list of elements at the end but you can not insert elements in the middle of the list.

In a statement, let a <+ b; appends b to the list a, and let x +> a; puts x in front of it. With new, new a <+ b; creates a new list a that holds b; it is an error when a already exists. The arrow shows the direction: the list stays after +> (x +> list, the element goes in front) and before <+ (list <+ x, the element goes at the end).


#list demo concatenation
primary aspect list_concat is
  process main is
    ** initialized list
    new a := ('a','b');
    new b := ('1','3');

    ** concatenate two lists
    new c := a <+ b;
    expect c ==  ('a','b','1','3');

    ** concatenate two lists
    new d := b <+ a;
    expect d ==  ('1','3','a','b');
  return;
end list_concat;

Alter a list

To grow a list use <+ (append at the end) or +> (put in front). To shrink it use the arrows <- or -> (remove the first or the last element) or the method delete() (remove every element equal to a value). The operators +=, -=, *=, /=, %= and ^= do not grow or shrink a list: with a scalar on the right they update every element (see Bulk operations).


#altering a list
primary aspect list_alter is
  new lst :()Rune;
  process main is
    ** initialized list
    let lst := ('a','b','c','b');

    ** append a new element at the end of list
    let lst <+ 'z';
    expect lst ==  ('a','b','c','b','z');

    ** remove all "b" elements from list
    lst.delete('b');
    expect lst ==  ('a','c','z');

    ** append several elements at the end
    let lst <+ ('a','z','x');
    expect lst ==  ('a','c','z','a','z','x');

    ** remove the last element and drop it
    let lst -> _;
    expect lst ==  ('a','c','z','a','z');

    ** delete every element equal to 'z'
    lst.delete('z');
    expect lst ==  ('a','c','a');

    ** remove all elements equal to the first element
    lst.delete(lst[1]);
    expect lst ==  ('c',);
  return;
end list_alter;

Note: To remove the first element use <- and to remove the last element use ->: the arrow points to the side the element leaves from, and the element goes into the name on the other side. let x <- lst; removes the first element and puts it in x; let lst -> y; removes the last element and puts it in y. When x or y does not exist yet, let creates it, with the type of the elements; in a loop it is created on the first pass and assigned again on the next ones. The explicit forms new x <- lst; and let lst -> new y; are also valid. To drop the element, write _, which swallows the result: let _ <- lst; removes the first element, let lst -> _; the last one. An arrow never removes by value: lst.delete(v) removes every element equal to v (on a DataMap, delete(k) removes the key k).

The list is prepared to support queue and stack. It has all the operations required. We can implement a class wrapper with the suggestive method names.

Note: There is no protection about using a List as stack or queue. Is developer responsibility to append and remove correctly using one of the operators: {<+, +>} {<-, ->}.

Queue (FIFO)

FIFO = First In First Out

For queue, two operations are possible

  • enqueue, - append to the end of the List: let queue <+ x;
  • dequeue, - extract the first element from the List: new e <- queue;. The arrow points to the side the element leaves from.

#using a list as a queue
primary aspect list_remove is
  ** initialized queue
  set queue :()Rune;
  process main is
    ** enqueue one element (at end)
    let queue <+ "x";
    expect queue ==  ('x');

    ** enqueue several elements
    let queue <+ ('z','y');
    expect queue ==  ('x', 'z', 'y');

    ** dequeue one element: the first one is the oldest
    new e <- queue;
    expect queue ==  ('z', 'y');
    expect e == 'x';
  return;
end list_remove;

Stack

LIFO = Last In First Out

For stack, two operations are possible

  • push - can append elements at end of List
  • pop - can extract the last element of the List

Note: we do not implement push and pop, instead user can define it's own stack based on a list and implement methods push and pop. Maybe standard library can implement Stack class.


#remove elements from list
primary aspect list_remove is
  ** initialized stack
  set stack := ('a','b','c');
  process main is
    ** append element at the end
    let stack <+ 'x';
    expect stack ==  ('a','b','c','x');

    ** extract element from the end
    let stack -> e;
    expect stack ==  ('a','b','c');
    expect e == 'x';
  return;
end list_remove;

List methods

A List is a class, so it has all required methods and properties to be used in algorithms and expressions. We do not yet foresee all methods but maybe these would be useful. Here is a minimalist suggestion for some methods.


List.empty;  ** true/false
List.full;   ** true/false
List.capacity; ** list with capacity

join() method

The join method receive a list and convert elements into a string separated be specified character.


#call List.join() demo
process main is
  new str := (1,2,3).join(',');
  expect str == "1,2,3";
return;

split() method The split method is actually a method of a String, that create a List of strings by splitting a long string into parts. Usually you split by comma, but any symbol can be used. The separator is removed and will not be part of the resulting list.


** string split demo
process main is
  ** split creates a list of strings
  new lst := "1,2,3".split(',');
  expect lst == ("1","2","3");
return;

Bulk operations

When the left operand is a List or an Array and the right operand is a scalar (a number, a boolean, a symbol or a string), the operator applies to every element. An expression returns a new collection of the same kind and leaves the operand unchanged; a modifier updates the elements in place.

Arithmetic operations

The operators + - * / % ^ apply the operation to each element. The expression (1, 2, 3) * 2 is (2, 4, 6). It is not an append and not a removal: to add an element use <+, to remove one use delete().


#a scalar applies to every element
primary aspect bulk_arithmetic is
  process main is
    new b := [1, 2, 3];
    expect b * 10 == [10, 20, 30];
    expect (b + 1) == [2, 3, 4];
    expect (b ^ 2) == [1, 4, 9];
    expect b == [1, 2, 3];       ** the operand does not change
  return;
end bulk_arithmetic;

Note: Strings are not bulk. Arithmetic with a string element or a string scalar is an error, because it would build strings: ("a", "b") + "x" raises an error. Create a new collection, or use a string builder, in a loop. A DataSet and a DataMap are not bulk either: their += and -= add and remove, because a bulk update could merge two elements into one.

Comparison with modifiers

The modifiers +=, -=, *=, /=, %= and ^= are a bulk update of the list in place: let a += 5; adds 5 to every element. The comparisons < > <= >= and the match =~ give a collection of booleans, one per element. == and <> still compare the whole collections.


#bulk update and bulk tests
primary aspect bulk_modifiers is
  process main is
    new a := (1, 2, 3, 4);
    let a += 5;                  ** a bulk update, not an append
    expect a == (6, 7, 8, 9);
    let a -= 1;
    expect a == (5, 6, 7, 8);
    let a *= 2;
    expect a == (10, 12, 14, 16);

    ** comparisons return one boolean per element
    expect (a > 12) == (False, False, True, True);

    ** a regular expression is tested on every string
    new w := ("cat", "dog", "cow");
    expect (w =~ "/^c/") == (True, False, True);
  return;
end bulk_modifiers;

Collection iteration

We can use a  for loop  that is executed for each element belonging to a collection. All collections are iterable. To be iterable a class must adopt the library trait Iterable.

Example:


#iteration process
primary aspect test_iteration is
  set collection := ('a','b','c');
  process main is
    loop
      new element: Rune;
    for element in collection do
       write element;
       write ',' if element is not collection[-1];
    done;
    print;
  return;
end test_iteration;

Example:

List traversal with break.


# list iteration with early termination
primary aspect list_iteration is
  set my_list: ()Rune; **  this list is empty
  process main is
    let my_list := ('a','b','c','d','e');
    loop
      new e: Rune;
    for e in my_list do
      write e;
      if e == 'd' do
        break; **  early termination;
      else
        write(',');
      done;
    done;
    print; ** a,b,c,d
  return;
end list_iteration;

Array

An array is a collection of elements ordered by one or more index numbers. Eve count elements in range (1..n), where n is the number of elements. First element is array[1]. We count starting from 1 while other languages start from 0. The array types are: Array (one dimension), Matrix (two dimensions) and Tensor (three or more dimensions).

Example:

Eve define Arrays using notation: [c]Type, where [c] is the capacity and Type is the type of elements. Arrays are automatically initialized. However, all elements will have default zero values.

** diverse array variables
primary aspect test_array is
  set n = 2, m = 3  :Natural;
  set (x, y, z) = 2 :Natural;

  ** define Array sub-types
  class AType = []Integer <: Array;

  ** define a global array with user define type
  set array = [1,2,3,4,5,6,7,8,9,10]: AType;

  process main is
    ** define local arrays of various capacities
    new array_name1: [10]Real;  ** Array of 10 large numbers
    new array_name3: [2^8]Rune; ** Array of 256 Runes
    new array_name4: [n,m]Type;   ** 2d matrix, capacity: 6
    new array_name5: [x,y,z]Type; ** 3d tensor, capacity: 8
  return;
end test_array;

Inference:

In next example we declare an array and use index "i" to access each element of the array by position. Later we will learn smarter ways to initialize an arrays and access its elements by using a visitor pattern.

eve-array

Eve Array


Lets implement previous array: numbers[] and print its elements using a loop. For initialization we use type inference with symbol ":=". Array literal contains all the elements.


# define single dimension array
process main is
  new numbers := [0,1,2,3,4,5,6,7,8,9];

** access .numbers elements one by one
  write "numbers = [";
  for i in (1..10) do
    write numbers[i];
    write ',' if i < 10;
  done;
  write "]";
  print; ** flush the buffer
return;

Expected Output

numbers = [0,1,2,3,4,5,6,7,8,9]

Notes:

  • Array index start from 1 so we use range (1..10);
  • Array capacity (10) is fix after array initialization;
  • Array element is accessed by the index: numbers[i];

initialize elements

Initial value for elements can be set during declaration or later:


# How to initialize all elements
process main is
  new zum: [10]Integer;
  ** explicit initialization using single value
  let zum[1..-1] := 0;
  expect zum == [0,0,0,0,0,0,0,0,0,0];

  ** modify two special elements:
  let zum[1] := 1;
  let zum[-1] := 10;
  print zum; ** expect [1,0,0,0,0,0,0,0,0,10]
return;

Deferred initialization: We can define an empty array and initialize its elements later. Empty arrays have capacity zero until array is initialized.


** array without capacity
process main is
  new vec :[?]Rune;
  new nec :[?]Integer;

  ** arrays are empty
  print vec == []; ** True
  print nec == []; ** True

  ** extend symbol array;
  vec.extend(10);
  print vec; ** expect ['','','','','','','','','','']

  ** extend numeric array;
  nec.extend(10);
  print nec; ** expect [0,0,0,0,0,0,0,0,0,0];
return;

Array builder

The array builder is a syntax feature that enable you to populate an array with data in a single statement. An array builder can be based on one or more ranges. Before you can populate the array it must be declared.

Syntax:

You can declare and initialize an array in the global area using the array builder. Array total capacity must match the number of elements created by the range. A function can be used to compute the values of all elements.


** declare an Array initialized using range
   set array = [ x | x in (min..max)(step)]: []ElementType;

** declare a new Array using a map function with range
   set array = [ f(x) | x in (min..max)(step)]: []ElementType;

** declare a new 2d array that is called matrix, row by row
   set matrix = [ f(x) | x in (min..max)(step)]: [n,m]ElementType;

Example:


primary aspect test_matrix is
  set array  :[4]Integer;
  set matrix :[4,4]Integer;
  process main is
  ** init a vector Array using a range
     let array := [ x | x in (1..10)(3)];
     expect array == [1,4,7,10];
     print array;
     print "----------";
  ** init a matrix Array using cartesian product between two ranges
     let matrix := [ x * y | (x, y) in (1..4) >< (1..4)];
     print matrix;
  return;
end test_matrix;

output:

 1 4 7 10
 ----------
 1 2  3  4
 2 4  6  8
 3 6  9 12
 4 8 12 16

Array deconstruct

The deconstruct is basically the inverse of a builder. You create individual variables from an Array. This is useful for copy or borrow specific parts of an Array.


#array deconstruct demo
primary aspect array_deconstruct is
  set a :[]Integer;
  process main is
    ** initialize array from Range
    let a := [x | x in (1..10)];

    ** create new variables using deconstruct
    new x, y, *, z := a;

    ** check new variables
    expect x == 1;
    expect y == 2;
    expect z == 10;

  return;
end array_deconstruct;

Note: The spreading statement must specify what is happening with all the elements. Using * you can ignore several elements and reach the last element z. You can have only one rest operator "*" in a statement.

Array slicing

An array can be sliced using slice notation [n..m] (the form [n:m] does not exist). A negative index counts from the end: [-1] is the last element and a[-x] is a[N + 1 - x], where N is the number of elements, so a[-N] is a[1]. a[0], and any index outside -N..N, is an out-of-bounds error. A slice can mix both: a[1..-1] is the whole array. A slice is also an array and can be sliced. A slice assigned with := is a view over the existing elements of the array; a slice assigned with the clone operator :: is a new array with a copy of the elements: new view := v[2..4]; shares the elements of v, new part :: v[2..4]; copies them (a deep copy). Before you can slice an array, it must be already initialized.

Named slices

In next example we use slicing notation to create two named slices. One is a dynamic slice, the other is a static slice. When you modify the dynamic slice, also the base array is modified. If you modify the static slice, the base array is unaffected.


primary aspect test_slice is
  set slice :[]Rune;
  process main is
    new base: [20]Rune;
    let base := "this is a test array".split();

    ** create a dynamic slice
    let slice := base[11..14];

    ** check slice content
    expect slice[1] == 't';
    expect slice[-1] == 't';
    expect slice == ['t','e','s','t'];

    ** join back an array to a string
    print slice.join(); ** "test"

    ** making a static slice;
    new copy :: base[11..14];

    ** alter two characters in slice
    let slice[1] := 'b';
    let slice[2] := 'u';

    ** the copy is not modified
    expect copy.join() == "test";

    ** base array is now modified;
    print base.join(); ** "this is a bust array"
  return;
end test_slice;

Limited slices

A limited slice is a slice that starts at a particular position and goes from that position several elements. You can give the end of the slice with a negative index, counted from the last element: base[5..-4] stops 3 elements before the last one.


primary aspect test_slice is
  set slice, sreve :[?]Rune;
  process main is
    ** generate Array elements from a Range
    new base  := [x | x in (1..10)];
    new x := 5;

    ** limited slices
    let slice := base[x..x+2];
    new sreve := base[5..-4].reverse();

    ** check the results
    expect slice == [5,6,7];
    expect sreve == [7,6,5];
  return;
end test_slice;

Anonymous slices

You can use anonymous slice to modify an Array or to print a section of the array. You do not have to assign a variable to the slice. This is a shortcut, and is very useful for handling Array elements in bulk. A slice gets a single value with :=, no [*] is needed.


primary aspect ano_slicing is
  process main is
    new base:[10]Integer;

    ** using bulk assignment
    let base[1..5] := 0;
    let base[6..-1] := 2;

    ** create a dynamic slice
    expect base == [0,0,0,0,0,2,2,2,2,2];

    ** print 2 elements from the middle
    print base[5..6]; ** [0,2]
  return;
end ano_slicing;

Deconstruct with rest

You can use deconstruct with rest to create a slice in the same time extract several values. By using operator ":=" you will create a slice but using "::" you will create a new Array.


# deconstruct with slice
process main is
  ** generate Array elements from a Range
  new base  := [x | x in (1..8)];

  ** create a rest slice
  new x,y,z, *rest  := base;

  ** check the rest slice
  expect rest == [4,5,6,7,8];
  expect (x,y,z) == (1,2,3);
return;

# deconstruct with clone
process main is
  ** generate Array elements from a Range
  new base  := [x | x in (1..8)];

  ** create a clone from reversed array
  new a,b,c, *clone :: base.reverse();

  ** check the clone elements
  expect clone == [5,4,3,2,1];
  expect (a,b,c) == (8,7,6);
return;

Matrix

A matrix is an array with 2 dimensions. An array with 3 or more dimensions is a Tensor: a 3D tensor is an array of matrices and a tensor can have any number N of dimensions. In next diagram we have a matrix with 4 rows and 4 columns. Total 16 elements. Observe, matrix index start from [1,1] as in Mathematics.

eve-array

2D Array

Example:

In next example we demonstrate a matrix literal. Eve has a default output for printing a matrix using columns. You can print a matrix on rows or columns using "print" statement.

# define a subtype of Array
primary aspect test_matrix is

  class Mat4 = [4,4]Integer <: Matrix;

  process main is
    new mat: Mat4; ** define matrix variable

    ** modify matrix using ":=" operator
    let mat :=[
       [1,  2, 3, 4]
      ,[5,  6, 7, 8]
      ,[9, 10,11,12]
      ,[13,14,15,16]
    ];

    expect mat[1,1]; ** 1  = first element
    expect mat[2,1]; ** 5  = first element in second row
    expect mat[3,-1]; ** 12 = last element in third row
    expect mat[-1,1]; ** 13 = first element last row
    expect mat[-1,-1]; ** 16 = last element

    ** nice output using array print method
    mat.print;
  return;
end test_matrix;

Expected output:

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

Matrix indexes

A matrix needs two indexes: the row and the column. They are written in one pair of brackets separated by a comma, or in one pair of brackets each: mat[x, y] and mat[x][y] are the same element. A tensor works the same way with more indexes: t[x, y, z] is t[x][y][z]. Both forms can be read and assigned.

Every index can be a range, so a chain of indexes selects a part of the matrix. The result is an array:

  • mat[1..5][3] reads column 3 of the first 5 rows: 5 elements;
  • mat[3][1..5] reads row 3, the first 5 columns;
  • mat[*][2] reads the whole column 2 (see [*] below).

primary aspect matrix_index is
  process main is
    new m := [[1,  2,  3,  4],
              [5,  6,  7,  8],
              [9, 10, 11, 12]];
    expect m[2][3] == m[2, 3];      ** 7
    let m[3][1] := 90;                  ** the same as m[3, 1] := 90
    expect m[1..2][3] == [3, 7];    ** column 3, rows 1 and 2
    expect m[2][1..3] == [5, 6, 7]; ** row 2, the first 3 columns

    new t :[2, 2, 2]Integer;
    let t[2][1][2] := 6;
    expect t[2, 1, 2] == 6;
  return;
end matrix_index;

Note: a single index on a matrix, mat[7], is the absolute index explained below. Only a second pair of brackets after it makes it a row: mat[3][*] is row 3.

All elements: [*]

The symbol [*] selects all the elements of one dimension. It is needed only for a matrix or a tensor, and can be combined with a range: mat[1..2][*] is the first two rows, mat[*][1..2] is the first two columns. We do not write [*][*]: one [*] is enough to assign every element.


process main is
  new mat: [4,4]Integer;
  let mat[*] := 0;          ** all elements
  let mat[1..2][*] := 1;    ** first two rows
  let mat[*][-1] := 9;       ** last column
return;

A matrix or a tensor can be accessed with one absolute index. Elements are counted row by row. The row and the column are calculated by dividing the index by the number of columns: for a matrix with 4 columns, mat[7] is the same element as mat[2,3].

Memory model

In memory elements are organized linear, row by row. We say that Eve matrix are in row major order like C, in contrast to Fortran where column major order is used. It is important to use the right major order when you traverse a 2d array.

row major order

Row Major Order

Example

Memory is linear, so we fake a matrix. We can access the entire matrix with a single loop or nested loops. Next program can initialize a matrix in a flat loop. The order of elements is automatically established.

** initialize matrix elements
process main is
  new mat: [3,3]Integer <: Array;
  ** initialize matrix elements
  loop
    new i   := 1;
    new x   := mat.length;
  while (i <= x) do
    let mat[i] := i;
    let i += 1;
  done;
  print mat; ** nice output
return;

Output:


 1  2  3
 4  5  6
 7  8  9

Note: The capacity of an Array is part of data type. An array of fixed size. When you define an array with variable capacity using [?], you do not reserve any memory for its elements. This kind of Array is dynamic. It can be extended.

Example:

Nested loops can be used to access every element in the matrix. When you do this kind of loops, pay attention to order of indexes. In Eve you must use first index for outer loop and second index for inner loop. For large matrix this can improve performance. Due to better cache management.


primary aspect matrix_demo is
  set matrx :[?, ?]Integer;
  process main is
    ** give value to all elements
    matrx.extend(3,3);
    loop
      new i, j :Integer;
    for i in (1..3) do
      for j in (1..3) do
         let matrx[i,j] := i*10+j;
      done;
    done;
    ** output matrix elements
    print "{matrx % 3i4}"; ** 3 columns, each element is an integer of width 4
    print;
    print "{matrx % 9i4}"; ** 9 columns
  return;
end matrix_demo;

Output


  11  12  13
  21  22  23
  31  32  33

  11  12  13  21  22  23  31  32  33

DataSet

In mathematics a data set is an abstract data structure that can store certain values, without repeated values. In Eve a DataSet is sorted by value. Sets are fast collections for search, add update with new values. However, you have to know the element value in order to search for it. Lists, on the other hand, are not sorted unless you sort them explicitly.

Empty DataSet

An empty DataSet is represented like this: {} (the empty map is {:}) and can be assigned to a set if you wish to remove all elements of the set. A set that is not initialized is empty: {} is its zero value.


  ** Define an empty DataSet with unknown element type
  set n_name :DataSet;

  ** Define DataSet with known element type
  set t_name : {}Integer;

DataSet restrictions

  • All elements of a set must have the same type;
  • DataSet elements are sorted by value, print shows them in order;
  • Elements are unique, You can't store duplicates;

Mutability

A set can be modified during run-time using operators. When you modify a set, keep in mind that sets are automatically sorted by value. The position of a new element is decided by its value. If you add or remove an element the position of several other elements will be shifted.

  • add x in dataSet
  • del x in dataSet

Note: You can refer to all elements using "*" and modify them using modifiers.

Example:


#demo for using a set
primary aspect set_demo is

  ** initialized DataSet of integers
  set my_set = {0,2,3} :DataSet;
  process main is
    ** append element 1
    add 1 in my_set;
    expect 1 in my_set;

    ** remove element 2
    del 2 in my_set;
    expect 2 not in  my_set;

    ** remove all elements
    let my_set := {};
    expect my_set == {};
  return;
end set_demo;

Union

Use union operator to combine two sets. The operators &= and |= do not exist.

# combine two sets using "||" = (two vertical bars)
primary aspect set_union_demo is
  ** element set
  set first = {0,1,2,3} :DataSet;
  set second: DataSet; ** empty DataSet
  process main is
    ** assign elements using union
    let second := first || {2, 4, 8, 10};

    ** common value 2 is not duplicate
    expect second == {0,1,2,3,4,8,10};
  return;
end set_union_demo;

Intersection

Intersect operator & find common elements:


# Create intersection using &&
primary aspect set_intersection_demo is
  set test: DataSet; ** empty set
  process main is
    let test := {1,2,3,4} && {3,4,5};
    expect test == {3,4};
  return;
end set_intersection_demo;

Difference

Difference operator "-", and modifier "-=", extract elements. With this trick you can remove one single element from a DataSet. Remember, elements in a set must be unique.


# Extract several elements;
process main is
  new test := {1,2,3,4};
  new rest := test - {3,4,5};

  ** check remaining elements
  expect (1,2) in rest;
  expect (3,4) not in rest;

  ** use modifier -=
  let test -= {1,3};
  expect test == {2,4};

  ** use modifier += to add elements
  let test += {1,3,5};
  expect test == {1,2,3,4,5}; ** we keep elements sorted
return;

Using DataSet

A DataSet is very useful for its property to hold unique elements sorted. This enable deduplication of elements in a list by converting the list to a DataSet. Operator || is overloaded to accept list arguments but will return a DataSet.


# convert a list to a dataset
process main is
  new list := (1,2,1,2,3,1,2,3,4);

  ** using operator || (union)
  new dset := {} || list;
  expect dset == {1,2,3,4};
return;

DataSet Builder

You can create elements of a DataSet from a range using builder.


/* demonstrate how to create a set from a range
   using shift operator to calculate power of two fast */
process main is
  new dset := {1 << x | x in (2..6)};

  ** check the new set
  expect dset == {4,8,16,32,64};
return;

Difference

You can extract elements out of a DataSet using a list, or another set.


# Extract several elements;
process main is
  new test := {1,2,3,4};
  new rest := test - (3,4);

  ** check remaining elements
  expect (1,2) in rest;
  expect (3,4) not in rest;

  ** use modifier -=
  let test -= (1,3);
  expect test == {2,4};
return;

Intersection

You can intersect 2 lists but the result will be a DataSet.


# Create intersection using &&
primary aspect set_intersection_demo is
  set test = {} :DataSet; ** empty set
  process main is
    let test := (1,2,3,4) && (2,3,4,5);
    expect test == {2,3,4};
  return;
end set_intersection_demo;

DataMap

A DataMap is a collection of (key:value) pairs. In Eve a DataMap is a sorted map: the elements are ordered by unique keys. This helps to find elements faster using a binary search algorithm. We use only sorted maps, so we keep the name DataMap and we do not use the shorter name Map.

  • DataMaps are sets of (key:value) pairs;
  • The key must be sortable: { Number, String, Ordinal};
  • Value can be anything including objects.

Syntax:


class Dic = {:}(String,Object) <: DataMap;
primary aspect test is
  # empty map
  set m :Dic; ** declare empty map
  process main is
    expect type(m) == DataMap;
    expect m == {:};
  return;
end test;

Example:


# how to initialize a table
primary aspect table_init is
  set h: DataMap;
  process main is
    ** initialize table
    let h := {'a':1, 'b':2};

    ** accessing elements
    expect h['a'] == 1;
    expect h['b'] == 2;

    ** create new elements: assign to a missing key
    let h['c'] := 3;
    let h['d'] := 4;

    ** output all elements
    print h ** {'a':1, 'b':2, 'c':3, 'd':4};
  return;
end table_init;

Using DataMap

Maps are sorted in memory by key for faster search. It is more difficult to search by value because usually is not unique and not sorted. To search by value one must create a loop and verify every element. This is called full scan and is slow.

Map Initialization

Initialize a collection later than declaration.

# local map initialization
primary aspect map_init is
  set my_map :DataMap; ** uninitialized collection
  process main is
    ** initialize my_map with values
    let my_map := {1:'a',2:'b',3:'c'};

    ** check if a specific key is present in a map collection
    expect 3 in my_map; ** will pass

    ** check if a sub-set of keys is present in a map collection
    expect {1,3} in my_map;
  return;
end map_init;

Create Elements

** create new elements
process main is
  set animals: {:}(String, String); ** empty collection
  ** append several elements
  let animals += {"Rob":"dog", "Kiwi":"bird"};

  ** create single element
  let animals["Toto"] := "parrot";
  print  animals;
return;

Output:

{"Kiwi":"bird","Rob":"dog","Toto":"parrot"}

Modify Elements

Assigning to a missing key creates the element. Using a modifier like += on a missing key raises an error.

#update a map
primary aspect map_update is
  set animals : {:}(String, Integer);
  process main is
    ** print the new collection
    print  animals;

    ** modify the collection
    let animals["dogs"]   := 1; ** new element
    let animals["birds"]  := 2; ** new element
    let animals["birds"]  += 3; ** alter element

    ** print the new collection
    print  animals;
  return;
end map_update;

Output:

{}
{"birds":5,"dogs":1}

Iteration of Map items

Collections have common methods that enable traversal using for loop.

{List, Map, Set}

built-in:

  • count() - retrieve the number of elements
  • capacity() - retrieve the maximum capacity
  • next - position next element
  • first - position to first element
  • last - position to last element
  • this - reference to current element

set iteration Map and Set are iterable. We can visit all elements using for loop:

Example:


# DataMap iteration
primary aspect map_iteration is
  process main is
    new my_map := {'a':1,'b':2,'c':3};
    loop
      new key: Rune;
      new value: Integer;
    for (key: value) in my_map do
      ** print pairs (key:value)
      print "({key}:{value})";
    done;
  return;
end map_iteration;

Output:

('a':1)
('b':2)
('c':3)

Generate Elements

You can generate elements for a DataMap using a collection builder. This is similar to a List builder, except you generate pair of values instead of a single value. You can use a function to calculate the value, or you can search values from another collection.

Example:


# DataMap iteration
primary aspect map_generator is
  set my_map = { ((x + y): 1 << y) | x in ('a'..'c') and y in (1..3)}: DataMap;
  process main is
    loop
      new key  :String;
      new value:Integer;
    for (key: value) in my_map do
      ** print pairs (key:value)
      print "({key}:{value})";
    done;
  return;
end map_generator;

Output:

('a1':2)
('b1':2)
('c1':2)
('a2':4)
('b2':4)
('c2':4)
('a3':8)
('b3':8)
('c3':8)

Objects

Objects are collections of attributes of different types. The most simple object is an instance of type Object. The Object is the fundamental class in EVE, that is why it starts with uppercase. An object literal has unquoted fields: {x:1.5, y:2}. A map literal has quoted or numeric keys: {'a':1, 'b':2}.

Pattern:

Initialize an object by using an object literal with initialization values:


# define global object instances
primary aspect test is
  ** empty object
  set object_name = {attribute:value, ...}: Object;

  process main is
    ** accessing attributes
    print object_name.attribute;
    ...
  return;
end test;

Example:

Define a local object that has two attributes:


# define global object instances
process main is
  ** simple object with two fields
  new point = {x:1.5, y:1.5}: Object;

  ** accessing attributes
  print point.x;
  print point.y;
return;

Empty object:

Define a local empty object {}


# define global object instances
process main is
  ** simple object with two fields
  new object = {} :Object;

  ** create new attributes by assignment
  let object.x := 1;
  let object.y := 2;
  ...
  print object.x; ** 1
  print object.y; ** 2
return;

Note: Objects and DataMap are very similar. The difference is, object attributes can have diverse types. In a DataMap all elements have the same type. Also in DataMap the key can be numeric, symbol or string. Quoted fields represent characters (strings), unquoted fields are symbols, also called identifiers: in an Object the attribute names are always identifiers.


Read next: Scripts