From Strings to Lists...and Back Again
November 9, 2001
by Dan Manes
Have you ever noticed that Director moves like a snail on NyQuil when working with strings and more like a cheetah with a caffeine buzz when you use lists instead? If you have, then you may have actually converted some of your longer strings into lists. Aside from some major improvements in speed, you probably noticed a plethora of other benefits, such as better organization, automatic sorting, and rapid searches.
"BUT!", you might be thinking, "The last time I tried to convert a long string to a list, my computer came to a screeching halt." Which shouldn't be surprising since Director has to do a whole mess of operations on that long string to make that conversion -- at least if you're doing things the old way. Yes, the old way. You repeat through each item in the string, appending one item at a time to a list. What could be simpler?
What could be slower?
If you think about what Director has to do, you will see why it gets so bogged down. Director finds items (or words or lines) in strings by literally searching through the characters one-by-one until it finds that magic one called the itemDelimiter. At this point, it says:"Okay, that's the end of the first item." If you're searching for item number 1000, it says:"Only 999 more to go." Kinda' reminds you of that old "100 Bottles of Beer on the Wall" song, doesn't it? If you have a long string, and you tell Director to grab every item one-by-one, it will take longer and longer the higher the item number (and no, Director is not smart enough to pick up where it left off-it starts from the beginning of the string each and every time it goes to look for something).
So, you've probably had it up to here with the old way and are good and ready to hear about the new way. The new way is basically to break up those long strings into halves. And break those halves into quarters. And break those quarters into eighths. You keep going until each string is just one item long. Pretty neat, huh? And as you will see shortly, not terribly difficult to code. By the way, the resemblance of this to that famous Binary Search Algorithm is no accident. The new way is, in fact, the binary way.
At this point, you may be wondering if there is a binary technique for getting a long list of items back into a string again. The answer is, of course! You pretty much just have to reverse the process by pairing consecutive items. In other words, you go through your list of items and combine each pair of items into twins. Then you combine each pair of twins into quadruplets. As the process continues, you end up with octuplets and big conglomerations I don't even know the word for. Anyway, in the end, what you have is a single really long string.
So, without further adieu, here is the Lingo that does these conversions. Notice I have provided handlers for both the old (serial) way (serialStringToList and serialListToString) and the new (binary) way (binaryStringToList and binaryListToString).
on serialStringToList InputString, ItemDelim
OrigDelim = the itemDelimiter
the itemDelimiter = ItemDelim
OutputList = []
TotalItems = InputString.item.count
repeat with ItemIndex = 1 to TotalItems
ItemString = InputString.item[ItemIndex]
OutputList.append (ItemString)
end repeat
the itemDelimiter = OrigDelim
return OutputList
end
on serialListToString InputList, ItemDelim
OrigDelim = the itemDelimiter
the itemDelimiter = ItemDelim
OutputString = EMPTY
repeat with ItemIndex = 1 to InputList.count
ItemString = InputList[ItemIndex]
OutputString = OutputString &ItemDelim &ItemString
end repeat
delete OutputString.item[1]
the itemDelimiter = OrigDelim
return OutputString
end
on binaryStringToList InputString, ItemDelim -- keep cutting strings in half until there is only one item per string
OrigDelim = the itemDelimiter
the itemDelimiter = ItemDelim
TotalItems = InputString.item.count
MainItemsList = list (TotalItems) -- keep track of the total items of each string
MainList = list (InputString)
repeat while MainList.count < TotalItems
TempItemsList = []
TempList = []
repeat with MainIndex = 1 to MainList.count
MainItems = MainItemsList[MainIndex]
HalfwayPoint = MainItems / 2
MainString = MainList[MainIndex]
if MainItems > 1 then
TempItemsList.append (HalfwayPoint)
TempList.append (MainString.item[1..HalfwayPoint])
end if
TempItemsList.append (MainItems - HalfwayPoint)
TempList.append (MainString.item[HalfwayPoint + 1..MainItems])
end repeat
MainItemsList = TempItemsList
MainList = TempList -- the MainList now contains double the strings, but each string contains half the items
end repeat
the itemDelimiter = OrigDelim
return MainList
end
on binaryListToString InputList, ItemDelim -- keep pairing adjacent strings together until only a single string remains
MainList = InputList
repeat while MainList.count > 1
TempList = []
repeat with MainIndex = 1 to MainList.count
MainIndex = MainIndex + 1 -- skip to the next index so that the index steps by two
FirstString = MainList[MainIndex - 1] -- grab the first string in the pair
if MainIndex > MainList.count then MergeString = FirstString
else
SecondString = MainList[MainIndex] -- grab the second string in the pair
MergeString = FirstString &ItemDelim &SecondString -- merge the two strings together
end if
TempList.append (MergeString)
end repeat
MainList = TempList -- the MainList now contains half the strings, but each string contains double the items
end repeat
if MainList.count = 0 then OutputString = EMPTY
else OutputString = MainList[1]
return OutputString
end
Now for the fun part. To actually see for yourself just how staggering the difference is between serial and binary, paste the above handlers as well as the "compareTimes" handler below into a movie script (or download the convenient Director 7-compatible test movie in Mac or Windows format).
on compareTimes ItemDelim
BaseString = member ("Test Data").text
BeginTime = the milliseconds -- test serialStringToList
TestList = serialStringToList (BaseString, ItemDelim)
NetTime = the milliseconds - BeginTime
put "serialStringToList: " &NetTime
BeginTime = the milliseconds -- test serialListToString
TestString = serialListToString (TestList, ItemDelim)
NetTime = the milliseconds - BeginTime
put "serialListToString: " &NetTime
BeginTime = the milliseconds -- test binaryStringToList
TestList = binaryStringToList (BaseString, ItemDelim)
NetTime = the milliseconds - BeginTime
put "binaryStringToList: " &NetTime
BeginTime = the milliseconds -- test binaryListToString
TestString = binaryListToString (TestList, ItemDelim)
NetTime = the milliseconds - BeginTime
put "binaryListToString: " &NetTime
end
Stick a new field into your cast called "Test Data" and paste in a long string with lots of items. "Items" can be anything you want-words, lines, comma- or tab-delimited lists, etc.
To test the speed, type compareTimes SPACE or compareTimes RETURN in the message window (depending on which character you want to use for an item delimiter).
[Editor's note: With 400 lines of data, the test movie we're providing can take upward of 20 seconds to run all four tests, you can cut that down by reducing the amount of text in the Test Data field.]
-- Welcome to Director --
comparetimes space
-- "serialStringToList: 16567"
-- "serialListToString: 1263"
-- "binaryStringToList: 578"
-- "binaryListToString: 371"
Sample output from the test movie on a MacOS G4/400 in Director 7.
If your test string is long enough, you should note that the binary technique takes considerably less time. So, if you like your code to run like your trutsy old minivan, then by all means, use the serial handlers. For a more Ferrari-like experience, go with the binary.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.