Slice List String Etc
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
Slice List String Etc.
The functions here return empty lists and strings when expected, while slicing lists or strings.
Introduction
LSL defines llList2List to slice lists of llGetListLength entries and LSL defines llGetSubString to slice strings of llStringLength chars.
Some people write scripts that expect llList2List to return an empty list more often than it does. In a couple of boundary test cases, llList2List functions exactly as specified, thus astonishingly returns a list of all entries or a list of the first entry, rather than the expected empty list. In exactly the same, way llGetSubString sometimes astonishes people by returning a list of all chars or a list of the first char, in these boundary test cases where some people erroneously expect the empty string.
The example slicing functions here more often just do the right thing, returning empty lists and strings when expected. Page down below the code to see a brief informal proof of this advantage, cribbed here from the design of the Python scripting language, if those detailed mathematics interest you.
Code to slice zero or more entries from a list
// Return the entries between the first index and the lastPlus index.
// cf. http://wiki.secondlife.com/wiki/Slice_List_String_Etc
// cf. http://www.google.com/search?q=site:docs.python.org+slice
list listGetBetween(list entries, integer first, integer lastPlus)
{
// Count negative indices back from beyond, stopping at zero
integer beyond = llGetListLength(entries);
if (first < 0) { first += beyond; if (first < 0) { first = 0; } }
if (lastPlus < 0) { lastPlus += beyond; if (lastPlus < 0) { lastPlus = 0; } }
// Slice if indices nonnegative and strictly ordered
if (first < lastPlus) // implies && (1 <= lastPlus)
{
return llList2List(entries, first, lastPlus - 1);
}
// Else return the empty list
return [];
}
// Demo
default
{
state_entry()
{
list entries = ["a", "b", "c", "d", "e", "f", "g", "h"];
llOwnerSay(llList2CSV(listGetBetween(entries, 0, 0))); // no entries
llOwnerSay(llList2CSV(listGetBetween(entries, 0, llGetListLength(entries)))); // all entries
llOwnerSay(llList2CSV(listGetBetween(entries, 0, 6))); // first six
llOwnerSay(llList2CSV(listGetBetween(entries, 6, llGetListLength(entries)))); // all but first six
llOwnerSay(llList2CSV(listGetBetween(entries, 0, -4))); // all but last four
llOwnerSay(llList2CSV(listGetBetween(entries, -4, llGetListLength(entries)))); // last four
llOwnerSay(llList2CSV(listGetBetween(entries, 0, 3))); // entries before the fourth char
llOwnerSay(llList2CSV(listGetBetween(entries, 3, 4))); // the fourth char
llOwnerSay(llList2CSV(listGetBetween(entries, 4, llGetListLength(entries)))); // the entries after
llOwnerSay(llList2CSV(llList2List(entries, 0, 0))); // never empty, sometimes astonishingly so
llOwnerSay(llList2CSV(llList2List(entries, llGetListLength(entries), llGetListLength(entries) - 1)));
llOwnerSay("OK");
}
}
Code to slice zero or more chars from a string
// Return the chars between the first index and the lastPlus index.
// cf. http://wiki.secondlife.com/wiki/Slice_List_String_Etc
// cf. http://www.google.com/search?q=site:docs.python.org+slice
string stringGetBetween(string chars, integer first, integer lastPlus)
{
// Count negative indices back from beyond, stopping at zero
integer beyond = llStringLength(chars);
if (first < 0) { first += beyond; if (first < 0) { first = 0; } }
if (lastPlus < 0) { lastPlus += beyond; if (lastPlus < 0) { lastPlus = 0; } }
// Slice if indices nonnegative and strictly ordered
if (first < lastPlus) // implies && (1 <= lastPlus)
{
return llGetSubString(chars, first, lastPlus - 1);
}
// Else return the empty string
return "";
}
// Demo
default
{
state_entry()
{
string qu = "\"";
string chars = "abcdefgh";
llOwnerSay(qu + stringGetBetween(chars, 0, 0) + qu); // no chars
llOwnerSay(qu + stringGetBetween(chars, 0, llStringLength(chars)) + qu); // all chars
llOwnerSay(qu + stringGetBetween(chars, 0, 6) + qu); // first six
llOwnerSay(qu + stringGetBetween(chars, 6, llStringLength(chars)) + qu); // all but first six
llOwnerSay(qu + stringGetBetween(chars, 0, -4) + qu); // all but last four
llOwnerSay(qu + stringGetBetween(chars, -4, llStringLength(chars)) + qu); // last four
llOwnerSay(qu + stringGetBetween(chars, 0, 3) + qu); // chars before the fourth char
llOwnerSay(qu + stringGetBetween(chars, 3, 4) + qu); // the fourth char
llOwnerSay(qu + stringGetBetween(chars, 4, llStringLength(chars)) + qu); // the chars after
llOwnerSay(qu + llGetSubString(chars, 0, 0) + qu); // never empty, sometimes astonishingly so
llOwnerSay(qu + llGetSubString(chars, llStringLength(chars), llStringLength(chars) - 1) + qu);
llOwnerSay("OK");
}
}
Brief informal proof of the natural usability advantage these slicing functions hold over the LSL slicing functions
Four steps of human logic:
1. Focus on the small space of test cases most commonly found in actual scripts that slice lists or strings:
- Let First be the index of the first entry or char that you do want, let LastPlus be the index of the first entry or char you don't want.
- Suppose you politely guarantee ((0 <= First) && (First <= Beyond)).
- Suppose you politely guarantee ((0 <= LastPlus) && (LastPlus <= Beyond)).
- Suppose you politely guarantee (First <= LastPlus).
2. See that a slice of length (LastPlus - First) is what you get always from the slicing functions here, as you expect, in those common circumstances.
3. Notice these functions encode slices as (First, LastPlus).
4. Remember LSL encodes slices as (First, LastPlus - 1) only while (First < LastPlus). The LSL slice length is (LastPlus - First) like it should be, while (First < LastPlus). But when (LastPlus - First) goes to zero, the LSL slice length goes astonishingly discontinuous in two dimensions. The LSL slice length jumps out to Beyond when ((First == LastPlus) && (LastPlus < Beyond)) and jumps back to 1 when ((First == LastPlus) && (LastPlus == Beyond)).
Get it?
These functions don't astonish your human intuition with those arbitrary discontinuities. Sure yea, unusually well-disciplined people do actually write LSL scripts to verbosely work around those discontinuities inline at every call of llList2List or llGetSubString. Other people erroneously think they are remembering to work around both those discontinuities at every call, but then actually forget one or more calls, or forget one or the other discontinuity, and thus write bugs.
Making a habit of calling these slice functions in place of the LSL slice functions will give you the expected, continuous, adjacent answer even in the boundary case of the empty slice, and thus often making your whole script work better, albeit measurably slower.
Enjoy!
P.S. Don't forget, these slice functions do the same work as the LSL slice functions, but the second parameter is significantly different: it is the LastPlus index, not the Last index. You can't just substitute these slice functions for the LSL slice functions -- you have to also change the calculation of the second parameter.