Programming style¶
In addition to mathematical operators, LocalSolver’s modeler provides some elements of an imperative programming language for building easily and quickly your models. This language, called LSP, has all characteristics of a scripting language but is dedicated to problem modeling and solving. Note that the LSP language is case-sensitive. This modeling language has its own manual available at LocalSolver’s Modeler (LSP). Only the main features are presented here.
Primitives types and assignments¶
The declaration of variables is done on the fly. Thus, the statement x = v
assigns the value v
to the variable x
. The language is dynamically typed.
Thus, the type is hold by the value, not the variable. Values can be written
as the combination of the two literal types: integers (x = 12
) and strings
(x = "foo"
). The boolean type is implicitly defined through integers 0 and 1
coding for false and true respectively. If the value v
corresponds to a
modeling expression, then the leftward arrow <-
must be used instead of =
.
In this way, the following statements are valid:
a = true; // a = 1
b = 9; // b = 9
c = a + b; // c = 10
c = a * b; // c = 9
c = a == b; // c = 0
c = a < b; // c = 1
Maps¶
Maps are generic containers that can be used as arrays or dictionnaries.
More precisely, a map is a data structure matching some values to some keys.
Keys and values can be of any type except nil
that has a special meaning.
The value associated to a key is set or retrieved by
using the “bracket” notation: a[9] = "abc"
, a["abc"] = 9
. A map is
implicitly declared when inserting its first element. Alternatively, an empty
map can be defined using map()
function or the braces {}
, as follows:
a = map("z", 9); // a[0] = "z", a[1] = 9
a = {"z", 9}; // a[0] = "z", a[1] = 9
a["a"] = "abc"; // a[0] = "z", a[1] = 9, a["a"] = abc
a[-3] = "xyz"; // a[0] = "z", a[1] = 9, a["a"] = abc, a[-3] = "xyz"
a[0] = -2; // a[0] = -2, a[1] = 9, a["a"] = abc, a[-3] = "xyz"
a[-3][1] = 0; // a[0] = -2, a[1] = 9, a["a"] = abc, a[-3] = {1: 0}
print(a[42]); // display nil
Note that a[k]
returns nil
if no value is assigned to key k
in map a
.
Setting the value of an existing key (with the =
operator) will overwrite
the previous value associated to this one. It is possible to assign another map
as a value of a key to create nested maps.
Conditions¶
if (C) S_true; else S_false;
is a conditional statement: the statement
S_true
is executed if the condition C
is true
(that is, if C
is equal to 1) while statement S_false
is executed if
C
is false (that is, if C
is equal to 0). Note that the else
branch
is optional. A block of statements can be declared instead of a sole statement
by using the braces {}
. The conditional ternary operator ? :
can also
be used as shortcut:
if (1 < 2) c = 3; else c = 4;
c = 1 < 2 ? 3 : 4;
if (0) c = "ok";
if (true) c = "ok";
if (2) c = "error"; // ERROR: invalid condition
c = 0 * 9; // c = 0
if (c) {
a = "L";
b = 0;
} else { // executed block
a = "S";
b = 1;
}
Loops¶
while (C) S;
iteratively executes the statement S
while the condition
C
is true (that is, is equal to 1). The syntax do S; while (C);
first
executes the statement S
once regardless of the condition C
and then
repeatedly executes S
while the condition is true.
for [v in V] S;
is an iteration loop where the statement S
is
iteratively executed with v
taking all values in V
, where V
can be
a range, a map or any iterable type. A range is declared with the from...to
syntax where to
is excluded. When a map is given,
the iteration is performed on the values of the map following the increasing
order of the keys. The pairs (key, value) of the map can also be iterated by
using the syntax for [k,v in M]
:
for [i in 0...3] a[i] = i + 1; // a[0] = 1, a[1] = 2, a[2] = 3
s = 0; for [v in a] s = s + v; // s = 6
s = 0; for [k,v in a] s = s + k + v; // s = 9
for [v in V : C]
is a filtered iteration loop: v
takes only the values
in V
satisfying the condition C
:
for[i in 1...201 : i % 21 == 0] {
println(i + " is a multiple of 21");
}
The nested loops can be written in a compact way, as follows:
for[i in 0...10]
for[j in i+1...10]
for[k in j+2...10]
a[i][j][k] = i + j + k;
for[i in 0...10][j in i+1...10][k in j+2...10] // compact
a[i][j][k] = i + j + k;
For all kinds of loops, a block of statements can be declared instead of a sole
statement by using the braces {}
:
for[i in 0...10][j in i+1...10][k in j+2...10] {
a[i][j][k] = i + j + k;
b[i][j][k] = i * j * k;
}
Iterated assignment¶
A novelty provided by the LSP language is the “iterated assignment”, which will
help you to make your models shorter and clearer.
Indeed, the code for [v in V] a[v] = f(v);
can be written in a very compact
way: a[v in V] = f(v);
. Similarly to the for
statement, iterations can
be nested and filtered:
for[i in 0...10][j in i+1...10][k in j+2...10]
a[i][j][k] = i + j + k;
a[i in 0...10][j in i+1...10][k in j+2...10] = i + j + k; // compact alternative
The iterated assignment is also allowed for declaring modeling expressions:
x[i in 0...N][j in 0...M] <- bool();
.
Functions¶
Functions can be declared outside other functions using the function
keyword.
The parameters are given in parenthesis, and its code is encapsulated between
braces:
function isEven(v) {
if (v % 2 == 0) return true;
else return false;
}
A value can be returned from any point in the function using the return
statement. By default, all variables have a global scope and can be accessed
from anywhere in the program, to the notable exception of parameters of
functions and iteration variables (introduced in for
statements) which are
local to their block. The local
keyword can be used to make a variable local
to its block:
function computeSumOfEvenNumbers(a,b) {
local total = 0;
for [v in a...b : isEven(v)]
total = total + v;
return total;
}