# Slice List String Etc

 LSL Portal

## 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

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

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.