/* * 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(var/list/list,index) if(istype(list) && list.len) if(isnum(index)) if(InRange(index,1,list.len)) return list[index] else if(index in list) return list[index] return proc/islist(list/list) return(istype(list)) //Return either pick(list) or null if list is not of type /list or is empty proc/safepick(list/list) if(!islist(list) || !list.len) return return pick(list) //Checks if the list is empty proc/isemptylist(list/list) if(!list.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 //Checks for specific paths in a list /proc/is_path_in_list(var/atom/A, var/list/L) for(var/path in L) if(ispath(A, path)) 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/listfrom) if (listfrom.len > 0) var/picked = pick(listfrom) listfrom -= picked return picked return null //Returns the top(last) element from the list and removes it from the list (typical stack function) /proc/pop(list/listfrom) if (listfrom.len > 0) var/picked = listfrom[listfrom.len] listfrom.len-- return picked return null //Returns the next element in parameter list after first appearance of parameter element. If it is the last element of the list or not present in list, returns first element. /proc/next_in_list(element, list/L) for(var/i=1, i= 1; i--) output += L[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 RO.vars[key]) // Works around list += list2 merging lists; it's not pretty but it works result += "temp item" result[result.len] = R[Ri++] else result += "temp item" result[result.len] = L[Li++] if(Li <= L.len) return (result + L.Copy(Li, 0)) return (result + R.Copy(Ri, 0)) //Mergesort: any value in a list, preserves key=value structure /proc/sortAssoc(var/list/L) if(L.len < 2) return L var/middle = L.len / 2 + 1 // Copy is first,second-1 return mergeAssoc(sortAssoc(L.Copy(0,middle)), sortAssoc(L.Copy(middle))) //second parameter null = to end of list /proc/mergeAssoc(var/list/L, var/list/R) var/Li=1 var/Ri=1 var/list/result = new() while(Li <= L.len && Ri <= R.len) if(sorttext(L[Li], R[Ri]) < 1) result += R&R[Ri++] else result += L&L[Li++] if(Li <= L.len) return (result + L.Copy(Li, 0)) return (result + R.Copy(Ri, 0)) // Macros to test for bits in a bitfield. Note, that this is for use with indexes, not bit-masks! #define BITTEST(bitfield,index) ((bitfield) & (1 << (index))) #define BITSET(bitfield,index) (bitfield) |= (1 << (index)) #define BITRESET(bitfield,index) (bitfield) &= ~(1 << (index)) #define BITFLIP(bitfield,index) (bitfield) ^= (1 << (index)) //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 // Returns the key based on the index /proc/get_key_by_value(var/list/L, var/value) for(var/key in L) if(L[key] == value) return key /proc/count_by_type(var/list/L, type) var/i = 0 for(var/T in L) if(istype(T, type)) i++ return i //Don't use this on lists larger than half a dozen or so /proc/insertion_sort_numeric_list_ascending(var/list/L) //world.log << "ascending len input: [L.len]" var/list/out = list(pop(L)) for(var/entry in L) if(isnum(entry)) var/success = 0 for(var/i=1, i<=out.len, i++) if(entry <= out[i]) success = 1 out.Insert(i, entry) break if(!success) out.Add(entry) //world.log << " output: [out.len]" return out /proc/insertion_sort_numeric_list_descending(var/list/L) //world.log << "descending len input: [L.len]" var/list/out = insertion_sort_numeric_list_ascending(L) //world.log << " output: [out.len]" return reverselist(out) /proc/dd_sortedObjectList(var/list/L, var/cache=list()) if(L.len < 2) return L var/middle = L.len / 2 + 1 // Copy is first,second-1 return dd_mergeObjectList(dd_sortedObjectList(L.Copy(0,middle), cache), dd_sortedObjectList(L.Copy(middle), cache), cache) //second parameter null = to end of list /proc/dd_mergeObjectList(var/list/L, var/list/R, var/list/cache) var/Li=1 var/Ri=1 var/list/result = new() while(Li <= L.len && Ri <= R.len) var/LLi = L[Li] var/RRi = R[Ri] var/LLiV = cache[LLi] var/RRiV = cache[RRi] if(!LLiV) LLiV = LLi:dd_SortValue() cache[LLi] = LLiV if(!RRiV) RRiV = RRi:dd_SortValue() cache[RRi] = RRiV if(LLiV < RRiV) result += L[Li++] else result += R[Ri++] if(Li <= L.len) return (result + L.Copy(Li, 0)) return (result + R.Copy(Ri, 0)) // Insert an object into a sorted list, preserving sortedness /proc/dd_insertObjectList(var/list/L, var/O) var/min = 1 var/max = L.len var/Oval = O:dd_SortValue() while(1) var/mid = min+round((max-min)/2) if(mid == max) L.Insert(mid, O) return var/Lmid = L[mid] var/midval = Lmid:dd_SortValue() if(Oval == midval) L.Insert(mid, O) return else if(Oval < midval) max = mid else min = mid+1 /* proc/dd_sortedObjectList(list/incoming) /* Use binary search to order by dd_SortValue(). This works by going to the half-point of the list, seeing if the node in question is higher or lower cost, then going halfway up or down the list and checking again. This is a very fast way to sort an item into a list. */ var/list/sorted_list = new() var/low_index var/high_index var/insert_index var/midway_calc var/current_index var/current_item var/current_item_value var/current_sort_object_value var/list/list_bottom var/current_sort_object for (current_sort_object in incoming) low_index = 1 high_index = sorted_list.len while (low_index <= high_index) // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.) midway_calc = (low_index + high_index) / 2 current_index = round(midway_calc) if (midway_calc > current_index) current_index++ current_item = sorted_list[current_index] current_item_value = current_item:dd_SortValue() current_sort_object_value = current_sort_object:dd_SortValue() if (current_sort_object_value < current_item_value) high_index = current_index - 1 else if (current_sort_object_value > current_item_value) low_index = current_index + 1 else // current_sort_object == current_item low_index = current_index break // Insert before low_index. insert_index = low_index // Special case adding to end of list. if (insert_index > sorted_list.len) sorted_list += current_sort_object continue // Because BYOND lists don't support insert, have to do it by: // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. list_bottom = sorted_list.Copy(insert_index) sorted_list.Cut(insert_index) sorted_list += current_sort_object sorted_list += list_bottom return sorted_list */ proc/dd_sortedtextlist(list/incoming, case_sensitive = 0) // Returns a new list with the text values sorted. // Use binary search to order by sortValue. // This works by going to the half-point of the list, seeing if the node in question is higher or lower cost, // then going halfway up or down the list and checking again. // This is a very fast way to sort an item into a list. var/list/sorted_text = new() var/low_index var/high_index var/insert_index var/midway_calc var/current_index var/current_item var/list/list_bottom var/sort_result var/current_sort_text for (current_sort_text in incoming) low_index = 1 high_index = sorted_text.len while (low_index <= high_index) // Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.) midway_calc = (low_index + high_index) / 2 current_index = round(midway_calc) if (midway_calc > current_index) current_index++ current_item = sorted_text[current_index] if (case_sensitive) sort_result = sorttextEx(current_sort_text, current_item) else sort_result = sorttext(current_sort_text, current_item) switch(sort_result) if (1) high_index = current_index - 1 // current_sort_text < current_item if (-1) low_index = current_index + 1 // current_sort_text > current_item if (0) low_index = current_index // current_sort_text == current_item break // Insert before low_index. insert_index = low_index // Special case adding to end of list. if (insert_index > sorted_text.len) sorted_text += current_sort_text continue // Because BYOND lists don't support insert, have to do it by: // 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list. list_bottom = sorted_text.Copy(insert_index) sorted_text.Cut(insert_index) sorted_text += current_sort_text sorted_text += list_bottom return sorted_text proc/dd_sortedTextList(list/incoming) var/case_sensitive = 1 return dd_sortedtextlist(incoming, case_sensitive) /datum/proc/dd_SortValue() return "[src]" /obj/machinery/dd_SortValue() return "[sanitize_old(name)]" /obj/machinery/camera/dd_SortValue() return "[c_tag]" /datum/alarm/dd_SortValue() return "[sanitize_old(last_name)]" /proc/subtypesof(prototype) return (typesof(prototype) - prototype) //creates every subtype of prototype (excluding prototype) and adds it to list L. //if no list/L is provided, one is created. /proc/init_subtypes(prototype, list/L) if(!istype(L)) L = list() for(var/path in subtypesof(prototype)) L += new path() return L