|
|
Container Types
These are the container types in Pike:
-
array or "vector" (written array in Pike)
-
mapping, "dictionary" or "associative array" (written
mapping)
-
multiset or "bag" (written multiset)
A data item of a container type can contain other data items. The
container types are also reference types: When a data item of a
container type is stored in a variable, it is not the data item itself
that is stored, but a reference to it.
The Data Type array
As described earlier in this tutorial, an array is a container that
can contain a sequence of elements. The elements are numbered from 0
and on.
array(string) b; // Array of strings
b = ({ "foo", "bar", "fum" });
b[1] = "bloo"; // Replaces "bar" with "bloo"
As you can see, array literals are written as comma-separated lists
inside parenthesis-curly-bracket quotes. The data type of an array
that can contain elements of the data type datatype is
array(datatype). The data type array(mixed),
i. e. an array that can contain any types of values, can also be
written just array.
An array variable that hasn't been given a value contains 0, and
not an empty array. If you want an empty array, you have to give it
explicitly:
array(string) a1; // a1 contains 0
a1 = ({ }); // Now a1 contains an empty array
array(int) a2 = ({ }); // a2 contains an empty array
As described earlier, you can access the elements in an array,
either to just get the value or to replace it. This is usually called
indexing the array. Indexing is done by writing the position
number, or index, within square brackets after the array:
write(a[0]);
b[1] = "bloo";
c[1] = b[2];
Note that the first position in an array is numbered 0 and not 1,
and the second one is numbered 1, and so on.
A special feature is that you can use negative indices:
array[-1] means the last position in the array
array, array[-2] the next-to-last position, and
so on.
An array can contain any type of values, including other arrays. In
that case, you may need several indexing operators after each
other:
array(array(int)) aai = ({
({ 7, 9, 8 }),
({ -4, 9 }),
({ 100, 1, 2, 4, 17 })
});
write("aai[2][3] is " + aai[2][3] + "\n");
This will print aai[2][3] is 4.
It is sometimes important to differentiate between two array
expressions being equal, and two that also are the same.
Whenever you write an array literal in your program, you get a new
array. This array is only the same as itself, but it can be
equal to other arrays. After executing the following code
snippet, the variables a and b will refer to the
same array, but c will refer to an array that is just
equal to the first one.
array(string) a = ({ "foo", "bar" });
array(string) b = a;
array(string) c = ({ "foo", "bar" });
Here are some of the many things that you can do with arrays:
- Check if it is an array
arrayp(something)
The function arrayp returns 1 if the value
something is an array, otherwise 0.
- Extract a range
array[from..to] returns a new array,
containing the elements at the index from up to and including
the index to.
({ 1, 7, 3, 3, 7 })[ 1..3 ] gives the result
({ 7, 3, 3 }).
The form array[from..] will give the
elements starting at index from and to the end of the array.
The form array[..to] will give the elements
from the start of the array, up to and including index to.
- Comparing arrays
array1 == array2 returns 1 if
array1 and array2 are the same array, otherwise
0. They have to be the same array, not just equal. Given
the variable definition
array(int) a = ({ 7, 1 });
this will be true:
a == a
but this will be false:
({ 7, 1 }) == ({ 7, 1 })
You can also use the operator !=, which means "not same".
The relational operators (<, >, etc) do not
work with arrays.
- Comparing arrays (again)
equal(array1, array2) returns 1 if
array1 and array2 look the same, otherwise 0. Two
arrays look the same if they have the same number of elements, and
each two corresponding elements in the two arrays look the same. For
example, this will be return 1:
equal( ({ 7, 1 }), ({ 7, 1 }) );
- Concatenation
array1 + array2 returns a new array with the
elements from both arrays, in the same order. This is a simple
concatenation of the arrays, so duplicate elements are of course not
removed.
({ 7, 1, 1 }) + ({ 1, 3 }) gives the result
({ 7, 1, 1, 1, 3 }).
- Union
array1 | array2 returns a new array with the
elements that are present in array1, or in array2, or in
both. The elements in the result can come in any order, and duplicates
may or may not be removed.
({ 7, 1 }) | ({ 3, 1 }) gives the result
({ 7, 3, 1 }).
- Intersection
array1 & array2 returns a new array with
the elements that are present in both arrays. The elements in the
result can come in any order, and duplicates may or may not be
removed.
({ 7, 1 }) & ({ 3, 1 }) gives the result
({ 1 }).
- Difference
array1 - array2 returns a new array with the
elements in the array array1 that are not also present in the
array array2. The elements in the result can come in any order,
and duplicates may or may not be removed.
({ 7, 1 }) - ({ 3, 1 }) gives the result
({ 7 }).
- Exclusive or
array1 ^ array2 returns a new array with the
elements that are present in array1 or in array2, but
not in both. The elements in the result can come in any order, and
duplicates may or may not be removed.
({ 7, 1 }) ^ ({ 3, 1 }) gives the result
({ 7, 3 }).
- Division
array / delimiter
This will split the array array into an array of arrays. If
the delimiter is an array, the array array will be split
at each occurrence of that array:
({ 7, 1, 2, 3, 4, 1, 2, 1, 2, 77 }) / ({ 1, 2 })
gives the result
({ ({ 7 }), ({ 3, 4 }), ({ }), ({ 77 }) }).
If the delimiter is an integer,
the array array will be split into arrays of size delimiter,
with any extra elements ignored:
({ 7, 1, 2, 3, 4, 1, 2 }) / 3
gives the result
({ ({ 7, 1, 2 }), ({ 3, 4, 1 }), ({ 2 }) }).
If you convert the same integer to a floating-point number,
the extra elements will not be thrown away:
({ 7, 1, 2, 3, 4, 1, 2 }) / 3.0 gives the result
({ ({ 7, 1, 2 }), ({ 3, 4, 1 }) }).
- Modulo
array % integer
This gives the extra elements that would be ignored in the division
operation array / integer:
({ 7, 1, 2, 3, 4, 1, 2 }) % 3 gives the result
({ 2 }).
- Finding the size
sizeof(array) returns the number of elements in the
array array.
sizeof( ({ }) ) gives the result 0.
- Allocating an empty array
allocate(size)
This will create an array with size elements. size is
an integer. All the elements will have the value 0.
- Reversing an array
reverse(array) returns a new array with the
elements in the array array in reverse order: with the first
element last, and so on. This operation creates a copy, and does not
change the array array itself.
- Finding an element in an array
search(haystack, needle) returns the index
of the first occurrence of an element equal to the needle in
the array haystack. The comparison is done with ==, so
the element must be the same as the needle.
- Replacing elements in an array
replace(array, old, new) replaces all
the elements that are equal (with ==) to old with
new. This operation does not create a copy, but changes the
array array itself.
The Data Type mapping
Mappings are sometimes called dictionaries or associative
arrays. A mapping lets you translate from one value (such as
"beer") to another value ("cerveza"). This is
possible since the mapping contains index-value pairs,
consisting of two data items. If you know the index, Pike can
quickly find the corresponding value for you.
A mapping literal can be written as a comma-separated list
of index-value pairs inside parenthesis-square-bracket quotes:
([ "beer" : "cerveza", "cat" : "gato", "dog" : "perro" ])
The data type of a mapping with indices of the type
index-type and values of the type
value-type is written mapping(index-type :
value-type). The data type mapping(mixed:mixed),
i. e. a mapping that can contain any types of indices and values, can
also be written just mapping.
Here are a few variables that can contain mappings:
mapping(string:string) m;
mapping(int:float) mif = ([ 1:3.6, -19:73.0 ]);
mapping(string:string) english2spanish = ([
"beer" : "cerveza",
"cat" : "gato",
"dog" : "perro"
]);
mapping(mixed:int) m2i = ([ 19.0 : 3, "foo" : 17 ]);
A mapping variable that hasn't been given a value contains
0, and not an empty mapping. If you want an empty mapping, you
have to give it explicitly:
mapping(string:float) m1; // m1 contains 0
m1 = ([ ]); // Now m1 contains an empty mapping
mapping(int:int) m2 = ([ ]);
// m2 contains an empty mapping
When you want to look up a value in the mapping, you use the
same indexing operator as for arrays: write the index within
square brackets after the mapping. You can use this both to just
retrieve values, and to change them:
write(english2spanish["cat"]); // Prints "gato"
english2spanish["dog"] = "gato";
// Now, english2spanish["dog"] is "gato" too
english2spanish["beer"] = english2spanish["cat"];
// Now, all values are "gato"
Index-value pairs can be inserted in the mapping either by writing
them in the mapping literal, or with the indexing operator.
There is no specific order between the index-value pairs in a
mapping, so there is no difference between the following two mapping
literals:
([ 1:2, 3:4 ])
([ 3:4, 1:2 ])
If you try to look up an index that hasn't been inserted
in the mapping, the indexing operator will return 0:
english2spanish["cat"] // Gives "gato"
english2spanish["glurble"] // Gives 0
Lookups are done using ==, so the thing used as index in
the lookup must be the same as the thing used when inserting
things in the mapping. Remember that arrays, mappings and multisets
may look the same, without being the same. Look at this example:
mapping(array(int) : int) m = ([ ]);
array(int) a = ({ 1, 2 });
m[a] = 3;
After running this code snippet, the expression m[a] will
give the value 3, but the expression m[ ({ 1, 2 }) ]
will give the value 0.
Mappings are similar to arrays. If you had a mapping from integers
(to something), and used the integer values 0, 1,
2, and so on, in order, this mapping would work very much like
an array. But mappings are much more flexible, since you can use any
type of values as indices. They are also slower and take up more space
in the computer's memory.
Here are some useful things that you can do with mappings:
- Check if it is a mapping
mappingp(something)
The function mappingp returns 1 if the value
something is a mapping, otherwise 0.
- Comparing mappings
mapping1 == mapping2 returns 1 if
mapping1 and mapping2 are the same mapping, otherwise
0. Just as with arrays, they have to be the same
mapping, not just equal. You can also use the operator !=,
which means "not same". The relational operators (<,
>, etc) do not work with mappings.
- Comparing mappings (again)
equal(mapping1, mapping2) returns 1
if mapping1 and mapping2 look the same, otherwise
0.
- Getting just the indices
indices(mapping) returns an array containing all
the indices from the index-value pairs in the mapping
mapping.
- Getting just the values
values(mapping) returns an array containing all the
values from the index-value pairs in the mapping mapping. If
you retrieve the indices (with indices) and the values (with
values) from the same mapping, without performing any other
mapping operations in between, the returned arrays will be in the same
order. They can be be used as arguments to mkmapping to
create an equivalent copy of the mapping.
- Create a mapping
mkmapping(index-array, value-array) builds a
new mapping with indices from the array index-array, and the
corresponding values from the array value-array.
- Union
mapping1 | mapping2
You can use set operations such as union (|)
on mappings. All the indices in a mapping are considered as a set, and
the set operators work with these sets. The values just "tag
along".
The union operator returns a new mapping with the elements that are
present in mapping1, or in mapping2, or in both. If an
index is present in both mappings, the value part of the resulting
index-value pair will come from the right-hand mapping
(mapping2). Example:
([ 1:2, 3:4 ]) | ([ 3:5, 6:7 ]) gives the result
([ 1:2, 3:5, 6:7 ]).
But note that the elements in a mapping don't have a specified
order.
The addition operator (+) is a synonym for
union (|) on mappings.
- Intersection
mapping1 & mapping2 returns a new
mapping with the elements that are present in both mappings. The value
parts of the resulting index-value pairs will come from the right-hand
mapping (mapping2).
([ 1:2, 3:4 ]) & ([ 3:5, 6:7 ]) gives the result
([ 3:5 ]).
- Difference
mapping1 - mapping2 returns a new mapping
with the elements in the mapping mapping1 that are not also
present in the mapping mapping2.
([ 1:2, 3:4 ]) - ([ 3:5, 6:7 ]) gives the result
([ 1:2 ]).
- Exclusive or
mapping1 ^ mapping2 returns a new mapping
with the elements that are present in mapping1 or in
mapping2, but not in both.
([ 1:2, 3:4 ]) ^ ([ 3:5, 6:7 ]) gives the result
([ 1:2, 6:7 ]).
- Finding the size
sizeof(mapping) returns the number of index-value
pairs in the mapping mapping.
sizeof( ([ ]) ) gives the result 0.
- Finding a value in an mapping
search(haystack, needle)
This is a "reverse lookup" that searches among the values of the
index-value pairs instead of among the indices. It returns the index
of the index-value pair that has the value needle in the
mapping haystack. If there are several index-value pairs that
have the same needle as value, any of them can be chosen. The
comparison is done with ==, so the element must be the same
as the needle. Example:
search(([ 1:2, 3:4, 4:5, 7:4 ]), 4) gives either
3 or 7.
- Replacing values in an mapping
replace(mapping, old, new) replaces
all the values that are equal (with ==) to old with
new. This operation does not create a copy, but changes the
mapping mapping itself. Example:
replace(([ 1:2, 2:3, 3:2 ]), 2, 17)
gives the result ([ 1:17, 2:3, 3:17 ]).
- Checking if an index is present
zero_type(mapping[index]) returns 0
if the index index is present in the mapping mapping,
otherwise it returns something other than 0. This can be useful
to discriminate between an index that isn't present in the mapping,
and one that is present but associated with the value 0:
if(temp["sauna"] == 0)
{
if(zero_type(temp["sauna"]))
write("We don't know the temp in the sauna.\n");
else
write("It's mighty cold in that sauna.\n");
}
The Data Type multiset
A set is something where a value is either a member or not. A
multiset (sometimes called a "bag") is a set where a value can
be a member several times. The multiset can contain several copies of
the same value.
A multiset literal can be written as a comma-separated list
of the elements, inside (< >) like this:
(< "foo", "bar", "fum", "foo", "foo" >)
The data type of a set with elements of the type
element-type is written
multiset(element-type). The data type
multiset(mixed:mixed), i. e. a multiset that can contain any
types of elements, can also be written just multiset.
Here are a few variables that can contain multisets:
multiset(string) m;
multiset(int) mi = (< 1, -19, 0 >);
multiset(string) dogs = (< "Fido", "Buster" >);
A multiset variable that hasn't been given a value contains
0, and not an empty multiset. If you want an empty multiset,
you have to give it explicitly:
multiset(string) m1; // m1 contains 0
m1 = (< >); // Now m1 contains an empty multiset
multiset(int) m2 = (< >);
// m2 contains an empty multiset
Multisets are very similar to mappings, except that:
-
Multisets just have elements, not index-value pairs.
-
You can have multiple instances of the same element in a multiset,
while in a mapping you can only have the same index in one single
index-value pair.
Multisets also use the indexing operator,
to see if an element is present, and to add and remove elements:
if(dogs["Fido"])
write("Fido is one of my dogs.\n");
if(!dogs["Dirk"])
write("Dirk is not one of my dogs.\n");
dogs["Kicker"] = 1; // Add Kicker to the set
dogs["Buster"] = 0; // Remove Buster
As you can see, you can write
multiset[element] = 1
to add the element element to the multiset multiset,
and
multiset[element] = 0
to remove it. Your program may be easier to understand if you use
set operations instead:
dogs |= (< "Kicker" >); // Add Kicker to the set
dogs -= (< "Buster" >); // Remove Buster
There is no specific order between the index-value pairs in a
multiset, so there is no difference between the following two multiset
literals:
(< "foo", "bar" >)
(< "bar", "foo" >)
Here are some useful things that you can do with multisets:
- Check if it is a multiset
multisetp(something)
The function multisetp returns 1 if the value
something is a multiset, otherwise 0.
- Comparing multisets
multiset1 == multiset2 returns 1 if
multiset1 and multiset2 are the same multiset, otherwise
0. Just as with arrays, they have to be the same
multiset, not just equal. You can also use the operator !=,
which means "not same". The relational operators (<,
>, etc) do not work with multisets.
- Comparing multisets (again)
equal(multiset1, multiset2) returns 1
if multiset1 and multiset2 look the same, otherwise
0.
- Set operations
All the set operations work with multisets:
union (|),
difference (-),
intersection (&), and
exclusive or (^).
|
|