Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Stride Functions

Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
09-18-2006 15:43
I've been working on some functions for working with strided lists. Wrote these up. I was wondering if anyone wants to comment on them.. check them for validity (they're pretty self explanitory) or possibly suggest any other functions that I should add to this list.

CODE

// Returns number of Strides in a List
integer fncStrideCount(list lstSource, integer intStride)
{
return llGetListLength(lstSource) / intStride;
}

// Find a Stride within a List (returns stride index, and item subindex)
list fncFindStride(list lstSource, list lstItem, integer intStride)
{
integer intListIndex = llListFindList(lstSource, lstItem);

if (intListIndex == -1) { return [-1, -1]; }

integer intStrideIndex = intListIndex / intStride;
integer intSubIndex = intListIndex % intStride;

return [intStrideIndex, intSubIndex];
}

// Deletes a Stride from a List
list fncDeleteStride(list lstSource, integer intIndex, integer intStride)
{
integer intNumStrides = fncStrideCount(lstSource, intStride);

if (intNumStrides != 0 && intIndex < intNumStrides)
{
integer intOffset = intIndex * intStride;
return llDeleteSubList(lstSource, intOffset, intOffset + (inStride - 1));
}
return ["-1"];
}

// Returns a Stride from a List
list fncGetStride(list lstSource, integer intIndex, integer intStride)
{
integer intNumStrides = fncStrideCount(lstSource, intStride);

if (intNumStrides != 0 && intIndex < intNumStrides)
{
integer intOffset = intIndex * intStride;
return llList2List(lstSource, intOffset, intOffset + (intStride - 1));
}
return ["-1"];
}

// Replace a Stride in a List
list fncReplaceStride(list lstSource, list lstStride, integer intIndex, integer intStride)
{
integer intNumStrides = fncStrideCount(lstSource, intStride);

if (llGetListLength(lstStride) != intStride) { return ["-1"]; }

if (intNumStrides != 0 && intIndex < intNumStrides)
{
integer intOffset = intIndex * intStride;
return llListReplaceList(lstSource, lstStride, intOffset, intOffset + (intStride - 1));
}
return ["-1"];
}

// Update a single item in a Stride within a List
list fncUpdateStride(list lstSource, list lstItem, integer intIndex, integer intSubIndex, integer intStride)
{
integer intNumStrides = fncStrideCount(lstSource, intStride);

if (llGetListLength(lstItem) != 1) { return ["-1"]; }

if (intNumStrides != 0 && intIndex < intNumStrides)
{
integer intOffset = (intIndex * intStride) + intSubIndex;
return llListReplaceList(lstSource, lstItem, intOffset, intOffset);
}
return ["-1"];
}


The only other function I'm thinking of trying to create would be a strided sort. (the current llListSortList wich does handle strides, only sorts by the first element of each stride. I'd perfer to find a way to sort by other elements of the stride, accending and decending, or (and this will be hard since we can't do function pointers) provide a way for someone to specify how to sort the list.

Added to this, that sorting lists, strided or not, can be proccessor intensive.. and it may be a can of worms not worth opening.
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
09-18-2006 22:13
The lack of replies has me currious. Is this something that already exists somewhere? Is this something that some people are finding handy? Is there no way to improve on these? Are there no other stride functions that people might find handy that would fit this set? Is there a chance that any of these might make the eventual wiki example page when it gets put up?

What's the verdict?
Ryu Darragh
Registered User
Join date: 20 Jul 2006
Posts: 28
09-21-2006 18:11
From: Aakanaar LaSalle
The lack of replies has me currious. Is this something that already exists somewhere? Is this something that some people are finding handy? Is there no way to improve on these? Are there no other stride functions that people might find handy that would fit this set? Is there a chance that any of these might make the eventual wiki example page when it gets put up?

What's the verdict?


Only two reasons I see for no replies. No one is interested (not likely.. it would have to be so self explanatory no one would even think to ask, and there's nothing like that in LSL) or no one knows what to do with it..

That last may be it. Come up with some cool uses for strided lists and post those as new threads. If there's a cool use for them, people will comment.
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
09-21-2006 18:21
hrm.. well, i mean.. strided lists are the closest we can get to multi-dimentional arrays.. Figgured people might like working with that.

I use strided lists in a lot of my code.. including when requesting multiple dataserver events, I put the queryID, and an integer in a strided list so that when the dataserver events come back i can figgure out which request i'm getting, and know what to do with the data return.

In one script I'm working on, I have several items about each visitor saved in a strided list, plus using a second strided list to update one element for each person whenever that info is returned by a dataserver event.

In another, I have names and Messages, like an answering machine..

Lots of uses for strided lists.. If anyone has any questions, do please ask.
Joannah Cramer
Registered User
Join date: 12 Apr 2006
Posts: 1,539
09-21-2006 18:41
From: Aakanaar LaSalle
The lack of replies has me currious.

I'd guess it has to do with strided list being avoided like a plague due to multiplying the problem of already high memory use tied with lists in general. Personally i've gotten into a habit of doing strided list through multiple lists each holding separate element type (list aaa, list bbb, list ccc rather than list abcabcabc) ...simply because when you need to manipulate the elements, it removes the necessity to fit a copy of all your list data in whatever memory may be still left at that point :/
Newgate Ludd
Out of Chesse Error
Join date: 8 Apr 2005
Posts: 2,103
09-22-2006 02:37
From: Joannah Cramer
I'd guess it has to do with strided list being avoided like a plague due to multiplying the problem of already high memory use tied with lists in general. Personally i've gotten into a habit of doing strided list through multiple lists each holding separate element type (list aaa, list bbb, list ccc rather than list abcabcabc) ...simply because when you need to manipulate the elements, it removes the necessity to fit a copy of all your list data in whatever memory may be still left at that point :/


I'm with Joannah on this, I actively avoid using strides.
Jesse Barnett
500,000 scoville units
Join date: 21 May 2006
Posts: 4,160
09-22-2006 09:10
Then finally you have the other 90% of us here that have absolutely no frigging idea what the hell it means yet;-)
Eloise Pasteur
Curious Individual
Join date: 14 Jul 2004
Posts: 1,952
09-22-2006 09:56
Me too.

I remember looking at strided lists for something a while ago. I ended up swearing and rewriting it with several lists, it worked as wanted that way, and, somewhat to my then surprise ran a LOT faster and a bit smaller. Since then... never touched them.
_____________________
Eloise's MiniMall
Visit Eloise's Minimall
New, smaller footprint, same great materials.

Check out the new blog
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
09-22-2006 10:01
well.. i'm finding using strides seems easier than using multiple lists.. I should do a test and see how storing something using a 5 element strided list and using 5 seperate unstrided lists compare on memory usage.

As for those who don't, know what a strided list is, it's a way of storing lists into lists.
an example would be, if you wanted to store the name of each person who touched an object, you could just add their names to a list. Then later, use a while loop to output them to you at your command.

If however you wanted to store their name, how many times they touched the object, and the timestamp of the last time they touched the object, then you have two choices.

You can have three lists, one for Names, one for Touch Count, and one for TimeStamps, like thus:
list name = ["Name 1", "Name 2", "Name 3", ..., "Name N"];
list count = ["Count 1", "Count 2", "Count 3", ..., "Count N"];
list time = ["Time 1", "Time 2", "Time 3", ..., "Time N"];

Or you can save them in one list as such:
list info = ["Name 1", "Count 1", "Time 1", "Name 2", "Count 2", "Time 2", ..., "Time N"];

The second form is a strided list.. All of your info is in one place, but retrieving and working with that info takes a few extra steps. In My functions the "Stride" is one list inside the other list, so each stride is 3 elements long, and consists of name, count, and time, in the above example.

I could, if i chose to, make these functions work with strides inside of strides.. allowing you to make a multi-dimentional array that's 3 or 4 or even 10 dimentions deep. Although anything above 3 would be extremely rare. 2 is usually the most you'll run into.

Again, I'll have to try an experiment and see how it compares with memory usage in each method of keeping the info.

hrm.. just occured to me, Those who havn't worked with other programming languages, Multi-Dimentional arrays work much the same way, but are easier to work with.
Arrays are kinda like lists with some differences. These differences vary on which language you're using. But most can be worked with a bit easier than lists.

For example, to get the fifth element out of a list you use
string var = llList2String(list, 5); // this is assuming it's a string

To get the fifth element out of an array, you use
string var = list(5);

and multi-dimentional arrays are just as easy. To get the fifth element out of the second list in a 2 dimentional array you use
string var = list(2)(5);

That's what i'm trying to replicate with the functions above.
Jesse Barnett
500,000 scoville units
Join date: 21 May 2006
Posts: 4,160
09-22-2006 10:48
Actually I was being half way facetious in my comment. At the time you first posting it was all greek to me. Now I have just started getting into lists and now find I need to go to the next step. Have been looking with curiosity at different ways of doin it and strided lists are one.

But also even without comments this post like so many are invaluable for posterity. Let's face it, once you move past the basics the scripting library and examples have less use. Even with some of the beautiful scripts there, to a novice they are meaningless. No context without discussion. For ideas that I am having trouble understanding I do my searchs here in the tips section. It has become invaluable and when I still don't understand then I ask here.

Just spent 45 minutes looking at Strife's thread on optimizations and learned so much. Both sides of the argument of optimization versus readability. Of course came to the conclusion that there really is no argument, it is actually a balance. But it still really hit home. Especially after the last couple of days of actually moving past script tweaking to writing from scratch. My silly little "Who do I love" script. Silly and with no use but the theory behind it has a multitude of uses. So many freinds here I have given a simple few line sit tp hack script to. Just told them to open it up and put in thier target location. All I get is a "Huh?" LMAO. SO to be able to write a script that anyone could actually change the buttons in the menu is worthwhile. Jeez, the 1000 meter menu driven would work great with it. No more having to type /1 tp add heaven. Just click "Options", click "Add" and it asks what you want to name destination and you are done.

Err sorry got side tracked. Back on optimizations versus readability thou and reading that thread. After getting my script in working order and then seeing how Newgate Ludd rewrote it was like a "eureka" moment. The before and after are dramatic. And I will definitely be making mine more readable now with optimizations thrown in like <1,1,1>.

So do not be discouraged that no one responded at first with comments. A year or two from now other scripting noobs like me will still come across this thread and pick up some usefull info from it about strided lists.
Joannah Cramer
Registered User
Join date: 12 Apr 2006
Posts: 1,539
09-22-2006 12:22
From: Aakanaar LaSalle
well.. i'm finding using strides seems easier than using multiple lists.. I should do a test and see how storing something using a 5 element strided list and using 5 seperate unstrided lists compare on memory usage.

Just storing and look ups are generally not a problem either way.... the problems part is when you are trying to add/replace/delete part of your list ^^

Consider:

// strided list
abcabcabcabc = llReplaceList( abcabcabcabc, "def", 2, 2);

and

// regular lists
aaaa = llReplaceList( aaaa, "d", 2, 2);
bbbb = llReplaceList( bbbb, "e", 2, 2);
cccc = llReplaceList( cccc, "f", 2, 2);

... the latter is more inconvenient to work with, but the former will cause your script to generate temporary copy of your complete list to work on, that's much larger than list holding just single element (the a's, the b's or the c's) ... and the more elements you have in the strided list, the larger becomes the difference.
Jesse Barnett
500,000 scoville units
Join date: 21 May 2006
Posts: 4,160
Well I am going to resurrect this thread after all
10-01-2006 22:33
Well I said I might look at strides soon. Played around and made this script as a template for another one. Chose strides because I will be able to add or delete sim, name, vector in another script through the menu instead of opening the script or using notecards. Not the prettiest scripting but it works fast and takes little memory. Saved me a lot of if's etc.
Welcome any feedback.

CODE

//fruits vegetables.lsl

list main_menu = ["fruits", "vegetables"];//Only these two choices on this menu
list menu_fruits;
list menu_vegetables;
list all = ["vegetable", "squash", "fruit", "apple", "vegetable", "corn", "fruit", "grapes", "vegetable", "lettuce", "fruit", "mango", "fruit", "cherries", "vegetable", "cucumber"];
list fruits_test;
list vegetables_test;
integer all_length; //Length of the all list
integer fruits_int;
integer fruits_length;//Length of the fruits list
integer vegetables_length;//Length of the vegetables list
integer vegetables_int;
integer chan; //Listen channel

default
{
state_entry()
{
llListenRemove(chan);
}

touch_start(integer num_detected)
{
chan = -9;
menu_fruits = [];
menu_vegetables = [];
menu_fruits = ["Back"];//Adds a Back button
menu_vegetables = ["Back"];//Ditto
fruits_test= ["fruit"];
vegetables_test = ["vegetable"];
llListen(chan,"",llGetOwner(),"");
llSetTimerEvent(20);
all_length = llGetListLength(all);// =10
all = llListSort(all, 2, TRUE); //returns a list of all sorted in ascending order by the first name of a 2 element strided list in this case; first it will list all the fruits and then all the vegetables
fruits_int = llListFindList(all, fruits_test);//position of first fruit in the all list
vegetables_int = llListFindList(all, vegetables_test);//Position of first vegetable in the all list
vegetables_length = (all_length - vegetables_int); //all entries in list minus position of first vegetable
fruits_length = (all_length - (vegetables_length + 1));//all entries in list minus how many vegetable entries plus one
menu_fruits = menu_fruits + llList2ListStrided(llDeleteSubList(all, 0, 0), fruits_int, fruits_length, 2);// delete the first entry in list (fruit) and then just get every other entry until the end point.
menu_vegetables = menu_vegetables + llList2ListStrided(llDeleteSubList(all, 0, 0), vegetables_int, all_length, 2);//Same as vegetables
llDialog(llDetectedKey(0), "Choose Fruits or Vegetables", main_menu, chan);
}
listen(integer chan, string name, key id, string message)
{
if (llListFindList(main_menu + menu_fruits + menu_vegetables, [message]) != -1)
{
if (message == "fruits")
{
llDialog(id, "Pick a fruit!", menu_fruits, chan);
}

if (message == "vegetables")
{
llDialog(id, "Pick a vegetable!", menu_vegetables, chan);
}

if (message == "Back")
{
llDialog(id, "Pick one", main_menu, chan);
}
}
}
timer()
{
llSetTimerEvent( 0 );
llListenRemove(chan);
return;
}
}


So anyways. Thank you Aakanaar!!
_____________________
I (who is a she not a he) reserve the right to exercise selective comprehension of the OP's question at anytime.
From: someone
I am still around, just no longer here. See you across the aisle. Hope LL burns in hell for archiving this forum
2fast4u Nabob
SL-ice.net
Join date: 28 Dec 2005
Posts: 542
10-02-2006 06:29
From: Aakanaar LaSalle
What's the verdict?


Just came across this - looks very useful indeed. I started to work with strided lists and then went the multi-list route since I was more focused on the solution at hand rather than debugging my code that manages strided lists.

I like your abstractions and plan to try them in a couple of my projects to see how it works out.

Don't let the general lack of responses deter you from continuing with this and other ideas. As others have said, it is possible that many don't understand the benefits that strided lists provide or people may be too busy to provide feedback on your code.

-2fast
Trevor Langdon
Second Life Resident
Join date: 20 Oct 2004
Posts: 149
10-02-2006 09:44
Aakanaar--

Thanks for the post.

I am using the strided list method in a sit-teleporter. The main reason I am using the strided list method is so I can sort the tp locations by name. However, I too wish the llListSortList command could sort on more than just the first item in the strided list. I would like to add the Sim name to my lists; however, I won't do that at this time since I want to not have to give up sorting the tp locations themselves.

Given time, I hope to incorporate multi-scripts (for up to say 10 Sims), each to hold the storage of tp locations for a given Sim. With this approach, I could still use my existing 2-strided list method and still sort with LL sort function.

From: someone
The only other function I'm thinking of trying to create would be a strided sort. (the current llListSortList wich does handle strides, only sorts by the first element of each stride. I'd perfer to find a way to sort by other elements of the stride, accending and decending, or (and this will be hard since we can't do function pointers) provide a way for someone to specify how to sort the list.


A supped up llListSortList replacement would still be valuable, even with its overhead. Maybe pushing the call off to a separate script (when using it) could help compensate for the memory overhead. Not sure at this point, if the passing the list to the separate script would defeat that purpose. Depends on whether the "function" causes the list to be stored multiple times in memory. Seems like a function of this type, would require multiple storage in memory, so I would think that calling the "function" from a separate script could be beneficial, with regards to memory overhead.

I for one, hope you pursue such a function.
Jesse Barnett
500,000 scoville units
Join date: 21 May 2006
Posts: 4,160
10-03-2006 14:21
Hey Trevor! Yep I had been working on the same thing. I was able to use strides and it works fine. Testing now to see memory limits but from the nubbers so far, should be able to store somewhere around 70 destinations. I'll save you some work, not the prettiest but heavily commented so you can see what I did. Just dump this in an object to use as a hud and Teneo Hopes teleport Object script into whatever you want to rez and put it inside the hud. (Be sure to change the name in the script to what the teleport object is called).

Actually I use a move instead of a sit teleport but it works fine either way. I'll be posting this to the library tonight. Took me waaay too many hours to figure out all the parts. Hopefully it will help others learn.

CODE

//Menu Driven Teleport HUD by Jesse Barnett
//A significant thanks to Teneo Hopes wonderful Teleport Script


list main_menu;
list options = ["Options"];//to be added to main menu to call options menu
list sims;///list of sims
list dest;//List of Destinations
list menu_options=["Add", "Remove", "Back", "List"]; //menu options
list sim_test;//used to get location of current sim in both dest and sims list
list next_test;//used ot get location of next sim in dest list
integer chan;//listen channel
integer current_sim_loc;//used to find location of current sim in list
integer current_sim_loc2;//Location of the next sim name in sims list
integer next_sim_loc;//Location of the next sim in dest list
string next_sim_name;//name of next sim in list
integer dest_length;//Destinations list length
string sim;//The current sim
string object = "pumpkin"; // Name of object to rez that will teleport the avatar
default
{
state_entry()
{
main_menu = [];//Clears the main menu
llListenRemove(chan);//remove all listens
}

touch_start(integer num_detected)
{
chan = -8173;//listen channel
main_menu = [];//Clears the main menu
sim = llGetRegionName();//Use this to limit menu to local destinations
sim_test = [sim];//The name of the current sim changed to list
dest_length = llGetListLength(dest);
dest = llListSort(dest, 3, TRUE);//returns a lsorted list according to sim name
sims = llListSort(sims, 1, TRUE);//sorts the sims list
current_sim_loc = llListFindList(dest, sim_test);//position of first current sim
in the dest list
current_sim_loc2 = llListFindList(sims, sim_test);//Postion of current sim in sims list
next_sim_name = llList2String(sims, (current_sim_loc2 + 1));//Now I have the name of the next sim
next_test = [next_sim_name];//Changes string to List
next_sim_loc = (llListFindList(dest, next_test) - 1);//Postion of the next sim sim in dest list
main_menu = llList2ListStrided(llDeleteSubList(dest, 0, 0),current_sim_loc, next_sim_loc, 3);//unsorted main menu
main_menu = llListSort(main_menu, 1, TRUE);//Buttons sorted by name
main_menu = options + main_menu;//Inserts "Options" button before sorted name buttons
llListen(chan,"",llGetOwner(),"");
llSetTimerEvent(20);
llDialog(llDetectedKey(0), "Choose destination in this sim or Options to add/remove destinations", main_menu, chan);//displays main menu
}

listen(integer channel, string lm, key id, string message)
{
list params = llParseString2List(message,[" "],[]);
if (llListFindList(main_menu + menu_options, [message]) != -1)
{
if (message == "Options")
{
llDialog(id, "Pick an option!", menu_options, chan);
}

else if (message == "Back")
{
llDialog(id, "Where in this sim do you want to go?", main_menu, chan);
}

else if (message == "Add")
{
integer b = FALSE;
integer m = FALSE;
b = ((llGetListLength(main_menu)) <= 11); //Limits 11 destinations in per sim
m = (llGetFreeMemory() >= 200);//Checks for available memory
if( b || m ) // Tests to see if either statement is true
{
llOwnerSay("You can not add any more destinations");
}
else
{
llOwnerSay("What do you want to name this destination?");
state adddest;
}
}

else if (message == "Remove")
{
llDialog(id, "Which destination do you want to remove?", main_menu, chan);
state remdest;
}

else if (message == "List")
{
integer i;
if(llGetListLength(dest) > 0)
{
for (i = 0; i < llGetListLength(dest); i+=3)
{
string sim = llList2String(dest, i);
string places = llList2String(dest, i+1);
string loc= llList2String(dest, i+2);
llOwnerSay(sim + " , " + places + " = " + loc);
}
}

else
{
llOwnerSay("No Destinations Available.");
}
}
else if(llListFindList(dest,[message]) != -1)
{
integer index = llListFindList(dest, [llList2String(params,0)]);
if(index != -1)
{
vector target = (vector)llList2String(dest, index+1);
integer chanfran = (integer)llFrand(9999999 - 9000000) + 9000000;
vector pos = llGetPos();
llSay(0,"Touch the pumpkin to take a ride");
llRezObject(object, (pos + llRot2Fwd(llGetRot()) + <1.2,1.2,0>), ZERO_VECTOR, ZERO_ROTATION, chanfran);
llWhisper(chanfran, (string)target);
}
}
}
}
timer()
{
llSetTimerEvent( 0 );
llListenRemove(chan);
return;
}
}
state adddest
{
state_entry()
{
chan = 0;
llSetTimerEvent(20);
llListen(chan,"",llGetOwner(),"");
}
listen(integer chan, string name, key id, string newdest)
{
integer e = llListFindList(sims, [sim]);//This checks to see if this sim name is in the sims list
if(e != -1)
{
vector pos = llGetPos();
dest = dest + [sim, newdest, pos];
llOwnerSay("Added : " + sim + " , " + newdest + " = " + (string)pos);
state default;
}
else
{
vector pos = llGetPos();
dest = dest + [sim, newdest, pos];
llOwnerSay("Added : " + sim + " , " + newdest + " = " + (string)pos);
sims = sims +[sim];//Adds this sim name to the sims list
state default;
}
}
timer()
{
llListenRemove(chan);
llOwnerSay("Timeout. Click TP HUD to start again");
state default;
}
}

state remdest
{
state_entry()
{

chan = -8173;
llSetTimerEvent(20);
llListen(chan,"",llGetOwner(),"");
}
listen(integer chan, string name, key id, string remdest)
{
integer d = llListFindList(dest, [remdest]);
if(d != -1)
{
dest = llDeleteSubList(dest, d - 1, d + 1);
llOwnerSay("Removed : " + remdest);
state default;
}
}
timer()
{
llListenRemove(chan);
llOwnerSay("Timeout. Click TP HUD to start again");
state default;
}
}
_____________________
I (who is a she not a he) reserve the right to exercise selective comprehension of the OP's question at anytime.
From: someone
I am still around, just no longer here. See you across the aisle. Hope LL burns in hell for archiving this forum
Trevor Langdon
Second Life Resident
Join date: 20 Oct 2004
Posts: 149
10-03-2006 15:17
Jesse--

Thx.

I'll take a look once I get a chance (currently busy working on some builds).

My current teleporter script has additional options, such as replace, read from notecard, set float text destination, custom title, color, as well as tp stone color. That being said, it's pushing the upper limit of memory and will need to be split inorder to incorporate a dialog function (currently command driven only). I have 30-40+ players using it now, besides myself in Navora, playing DarkLife (very helpful in bouncing around to the different custom locations for doing battle). I will soon update the code to include the dialog function (currently using gestures to assist with shortcuts to the full commands).

I have yet to create a HUD, so this will give me a jump-right-in approach :)

--Trevor
Aakanaar LaSalle
Registered User
Join date: 1 Sep 2006
Posts: 132
10-17-2006 00:23
wow, hadn't checked in a while, got more replies. I did finally put these strided functions up on the wiki (once i figgured out how to post to a wiki).. and even made them a tad bit better.. having the ones that manipulate the lists return the original list when it encounters a problem.

I gave some examples on how to use it too.

Perhaps that should be a next project is a method of a strided list sort through a seperate script. Perhaps have it llMessageLinked() info back and forth. It would be interesting as I have already programmed the bubble sort method (ok, not the best of the sorting methods, but consider the languages i'm about to mention) in Muf (muck forth language, on text mucks), in scripting for mIRC, and a language called dragonspeak or dspeak for a game called Furcadia. (anyone who has seen the limitations of that language would greatly appreciate the work I had to do to do a bubble sort in dspeak.)

So I've done bubble sort in languages that were never designed with sorting in mind.. LSL can definately handle it.

Edit: oh yea.. that page is Here.