Files
fulpstation/code/__HELPERS/lists.dm
carnie 05b76b123e TimSort for byond:
RESULTS:
sorting 10 random lists of length 3 to 303 in increments of 3
(Meh, I forgot to refresh this one, there were only 338 trials rather than 1010, can't be bothered to recode the test)
                                    Profile results (total time)
Proc Name                                             Self CPU    Total CPU    Real Time        Calls
-------------------------------------------------    ---------    ---------    ---------    ---------
/proc/sortList                                           0.672       16.141       16.243       171226  <--TG's current mergesort(recursive, hence the higher number of calls)
/proc/sortTim                                            0.008        3.278        3.274          338  <--TimSort
/proc/sortMerge                                          0.011        2.839        2.855          338  <--new mergesort
/proc/sortInsert                                         0.010        2.124        2.103          338  <--binary insertion

Sorting 10 presorted lists with 3 inversions (3 elements shuffled up), Lists of length 3 to 303 (increments of 3)
                                    Profile results (total time)
Proc Name                                             Self CPU    Total CPU    Real Time        Calls
-------------------------------------------------    ---------    ---------    ---------    ---------
/proc/sortList                                           1.290       23.056       23.254       308050	<--rather cataclysmic
/proc/sortMerge                                          0.015        4.077        4.068         1010	<--
/proc/sortInsert                                         2.639        3.472        3.464         1010	<--
/proc/sortTim                                            0.014        1.567        1.576         1010	<--TimSort is faaar more effective in these cases,
Timsort can exploit runs effectively

sorting 10 presorted lists which have been reversed
                                    Profile results (total time)
Proc Name                                             Self CPU    Total CPU    Real Time        Calls
-------------------------------------------------    ---------    ---------    ---------    ---------
/proc/sortList                                           1.234       23.193       23.295       308050
/proc/sortMerge                                          0.023        4.681        4.686         1010
/proc/sortInsert                                         2.875        3.750        3.765         1010
/proc/sortTim                                            0.020        3.294        3.284         1010	//This can be lower by using a different comparison method
 *Corrected: /proc/sortTim                                0.017        0.665        0.663         1010	//Using a non-strictly ascending comparison

sorting 10 presorted lists
                                    Profile results (total time)
Proc Name                                             Self CPU    Total CPU    Real Time        Calls
-------------------------------------------------    ---------    ---------    ---------    ---------
/proc/sortList                                           1.199       21.391       21.517       308050
/proc/sortMerge                                          0.018        3.724        3.729         1010
/proc/sortInsert                                         2.497        3.302        3.309         1010
/proc/sortTim                                            0.024        0.586        0.584         1010

Summary, all the new procs are faster than the old ones. TimSort is ever so slightly slower than Insertion and Merging on random lists. But on lists with natural runs (partially sorted data) it is far faster than all others.

The old merge sort was removed and replaced with timSort. Other algorithms are provided as alternatives.

All algorithms use a central datum, so accept many of the same parameters. For instance, setting associative=1 will make them sort associative lists by their associated values, rather than keys.
They also accept a cmp argument. This allows sorting of lists of datums, text, numbers or whatever. The pre-existing helpers in lists.dm were rewritten as examples.
2014-09-01 11:29:49 +01:00

332 lines
8.3 KiB
Plaintext

/*
* Holds procs to help with list operations
* Contains groups:
* Misc
* Sorting
*/
/*
* Misc
*/
//Returns a list in plain english as a string
/proc/english_list(var/list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
var/total = input.len
if (!total)
return "[nothing_text]"
else if (total == 1)
return "[input[1]]"
else if (total == 2)
return "[input[1]][and_text][input[2]]"
else
var/output = ""
var/index = 1
while (index < total)
if (index == total - 1)
comma_text = final_comma_text
output += "[input[index]][comma_text]"
index++
return "[output][and_text][input[index]]"
//Returns list element or null. Should prevent "index out of bounds" error.
/proc/listgetindex(list/L, index)
if(istype(L))
if(isnum(index))
if(IsInRange(index,1,L.len))
return L[index]
else if(index in L)
return L[index]
return
/proc/islist(list/L)
if(istype(L))
return 1
return 0
//Return either pick(list) or null if list is not of type /list or is empty
/proc/safepick(list/L)
if(istype(L) && L.len)
return pick(L)
//Checks if the list is empty
/proc/isemptylist(list/L)
if(!L.len)
return 1
return 0
//Checks for specific types in a list
/proc/is_type_in_list(var/atom/A, var/list/L)
for(var/type in L)
if(istype(A, type))
return 1
return 0
//Empties the list by setting the length to 0. Hopefully the elements get garbage collected
/proc/clearlist(list/list)
if(istype(list))
list.len = 0
return
//Removes any null entries from the list
/proc/listclearnulls(list/list)
if(istype(list))
while(null in list)
list -= null
return
/*
* Returns list containing all the entries from first list that are not present in second.
* If skiprep = 1, repeated elements are treated as one.
* If either of arguments is not a list, returns null
*/
/proc/difflist(var/list/first, var/list/second, var/skiprep=0)
if(!islist(first) || !islist(second))
return
var/list/result = new
if(skiprep)
for(var/e in first)
if(!(e in result) && !(e in second))
result += e
else
result = first - second
return result
/*
* Returns list containing entries that are in either list but not both.
* If skipref = 1, repeated elements are treated as one.
* If either of arguments is not a list, returns null
*/
/proc/uniquemergelist(var/list/first, var/list/second, var/skiprep=0)
if(!islist(first) || !islist(second))
return
var/list/result = new
if(skiprep)
result = difflist(first, second, skiprep)+difflist(second, first, skiprep)
else
result = first ^ second
return result
//Pretends to pick an element based on its weight but really just seems to pick a random element.
/proc/pickweight(list/L)
var/total = 0
var/item
for (item in L)
if (!L[item])
L[item] = 1
total += L[item]
total = rand(1, total)
for (item in L)
total -=L [item]
if (total <= 0)
return item
return null
//Pick a random element from the list and remove it from the list.
/proc/pick_n_take(list/L)
if(L.len)
var/picked = rand(1,L.len)
. = L[picked]
L.Cut(picked,picked+1) //Cut is far more efficient that Remove()
//Returns the top(last) element from the list and removes it from the list (typical stack function)
/proc/pop(list/L)
if(L.len)
. = L[L.len]
L.len--
/proc/sorted_insert(list/L, thing, comparator)
var/pos = L.len
while(pos > 0 && call(comparator)(thing, L[pos]) > 0)
pos--
L.Insert(pos+1, thing)
// Returns the next item in a list
/proc/next_list_item(var/item, var/list/L)
var/i
i = L.Find(item)
if(i == L.len)
i = 1
else
i++
return L[i]
// Returns the previous item in a list
/proc/previous_list_item(var/item, var/list/L)
var/i
i = L.Find(item)
if(i == 1)
i = L.len
else
i--
return L[i]
/*
* Sorting
*/
/*
//Reverses the order of items in the list
/proc/reverselist(var/list/input)
var/list/output = list()
for(var/i = input.len; i >= 1; i--)
output += input[i]
return output
*/
//Randomize: Return the list in a random order
/proc/shuffle(var/list/L)
if(!L)
return
L = L.Copy()
for(var/i=1, i<=L.len, ++i)
L.Swap(i,rand(1,L.len))
return L
//Return a list with no duplicate entries
/proc/uniquelist(var/list/L)
var/list/K = list()
for(var/item in L)
if(!(item in K))
K += item
return K
//for sorting clients or mobs by ckey
/proc/sortKey(list/L, order=1)
return sortTim(L, order >= 0 ? /proc/cmp_ckey_asc : /proc/cmp_ckey_dsc)
//Specifically for record datums in a list.
/proc/sortRecord(list/L, field = "name", order = 1)
cmp_field = field
return sortTim(L, order >= 0 ? /proc/cmp_records_asc : /proc/cmp_records_dsc)
//any value in a list
/proc/sortList(var/list/L, cmp=/proc/cmp_text_asc)
return sortTim(L.Copy(), cmp)
//uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead
/proc/sortNames(var/list/L, order=1)
return sortTim(L, order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc)
//Converts a bitfield to a list of numbers (or words if a wordlist is provided)
/proc/bitfield2list(bitfield = 0, list/wordlist)
var/list/r = list()
if(istype(wordlist,/list))
var/max = min(wordlist.len,16)
var/bit = 1
for(var/i=1, i<=max, i++)
if(bitfield & bit)
r += wordlist[i]
bit = bit << 1
else
for(var/bit=1, bit<=65535, bit = bit << 1)
if(bitfield & bit)
r += bit
return r
// Returns the key based on the index
/proc/get_key_by_index(var/list/L, var/index)
var/i = 1
for(var/key in L)
if(index == i)
return key
i++
return null
/proc/count_by_type(var/list/L, type)
var/i = 0
for(var/T in L)
if(istype(T, type))
i++
return i
/proc/find_record(field, value, list/L)
for(var/datum/data/record/R in L)
if(R.fields[field] == value)
return R
//Move a single element from position fromIndex within a list, to position toIndex
//This will preserve associations ~Carnie
/proc/moveElement(list/L, fromIndex, toIndex)
if(fromIndex > toIndex)
++fromIndex
else
++toIndex
L.Insert(toIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(fromIndex, fromIndex+1)
//Move elements [fromIndex,fromIndex+len) to [toIndex,toIndex+len)
//This will preserve associations and is much faster than copying to a new list
//or checking for associative lists and manually copying elements ~Carnie
/proc/moveRange(list/L, fromIndex, toIndex, len=1)
var/distance = abs(toIndex - fromIndex)
if(len > distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going
if(fromIndex < toIndex)
toIndex += len
else
fromIndex += len
for(var/i=0, i<distance, ++i)
L.Insert(fromIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(toIndex, toIndex+1)
else
if(fromIndex > toIndex)
fromIndex += len
else
toIndex += len //?
for(var/i=0, i<len, ++i)
L.Insert(toIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(fromIndex, fromIndex+1)
//Move elements from [fromIndex, fromIndex+len) to [toIndex, toIndex+len)
//Move any elements being overwritten by the move to the now-empty elements, preserving order
//Note: if the two ranges overlap, only the destination order will be preserved fully, since some elements will be within both ranges ~Carnie
/proc/swapRange(list/L, fromIndex, toIndex, len=1)
var/distance = abs(toIndex - fromIndex)
if(len > distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements
if(fromIndex < toIndex)
toIndex += len
else
fromIndex += len
for(var/i=0, i<distance, ++i)
L.Insert(fromIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(toIndex, toIndex+1)
else
if(toIndex > fromIndex)
var/a = toIndex
toIndex = fromIndex
fromIndex = a
for(var/i=0, i<len, ++i)
L.Swap(fromIndex++, toIndex++)
//replaces reverseList ~Carnie
/proc/reverseRange(list/L, start=1, end=0)
if(L.len)
start = start % L.len
end = end % (L.len+1)
if(start <= 0)
start += L.len
if(end <= 0)
end += L.len + 1
--end
while(start < end)
L.Swap(start++,end--)
return L