Worst-case scenario: the UEd Goblin wipes the map and burns down your house.

User:Wormbo/Random useful code snippets

From Unreal Wiki, The Unreal Engine Documentation Site
< User:Wormbo
Revision as of 15:36, 8 February 2011 by Wormbo (Talk | contribs) (int modulo, arctan and optimized linear search)

Jump to: navigation, search

On this page I will post various code snippets that are too useful to keep them to myself. Some of these probably deserve their own article pages with background information, and maybe I'll create them at some point.

Linear search one-liner

The most simple way to search for a value in an array is linear search. The following implementation is (almost) a one-liner that works on all arrays, sorted or not.

local int i;
 
i = A.Length;
do {} until (--i < 0 || A[i] == Value);

Success is indicated by i >= 0, failure by i == -1 or i == INDEX_NONE in UE3.

If the array is sorted and has more than just a few elements, you should consider using binary search instead.

Binary Search

The following very simply piece of code implements the binary search algorithm with deferred detection of equality, looking for Value in array A. If Value isn't in A, the code returns the index where Value would have to be inserted.

local int Low, High, Middle;
 
Low = 0;
High = A.Length;
while (Low < High) {
  Middle = (High + Low) / 2;
  if (A[Middle] < Value)
    Low = Middle + 1;
  else
    High = Middle;
}

As you can see, the advantage here is that each loop iteration only performs a single less-than comparison between the search value and an array element.

So, why would you combine the equality and greater-than comparisons and give up early abort? Quite simple: The equality comparison would fail most of the time anyway, so it doesn't actually give that much of an advantage, considering binary search only has an overall worst case time complexity of O(log2n). And, well, you only need to implement a less-than comparison for custom data types as opposed to less-then and greater-than. Here "less than" could actually mean "greater than" if the array is sorted in descending order or anything else, as long as it is a strict total order on the possible values for the array's type.

To figure out whether you found the value or not, an additional comparison is required:

if (Low < A.Length && A[Low] == Value)
  // found it
else
  // not found

You can assume that Low always is a valid insert index less than or equal to A.Length, so A.Insert(Low, 1); A[Low] = Value; (or A.InsertItem(Low, Value); in UE3) will always work correctly.

It is guaranteed that Low < A.Length implies A[Low] >= Value and Low > 0 implies A[Low-1] < Value. That means if you want to remove all values less than Value, you can simply do A.Remove(0, Low). To remove all values greater than or equal to Value, you use A.Remove(Low, A.Length - Low).

To find the first index with a value greater than Value, you just need a tiny modification:

local int Low, High, Middle;
 
Low = 0;
High = A.Length;
while (Low < High) {
  Middle = (High + Low) / 2;
  if (A[Middle] <= Value)
    Low = Middle + 1;
  else
    High = Middle;
}

This version can be used if you want to ensure that new instances of values considered as "equal" are inserted after all previous instances, whereas the other version of the algorithm finds an insert location before all other equal instances.

Again you can assume that Low always is a valid insert index less than or equal to A.Length. It is guaranteed that Low < A.Length implies A[Low] > Value and Low > 0 implies A[Low-1] <= Value. That means if you want to remove all values less than or equal to Value, you can simply do A.Remove(0, Low). To remove all values greater than Value, you use A.Remove(Low, A.Length - Low). Note how the "or equal to" now is part of the other side of the array.

Finding common string prefix

For incremental encoding you need to find the common prefix of two strings. Usually you'd do that by comparing corresponding characters of both strings, starting from the front, but UnrealScript is quite inefficient for that. I found that, at least in UT2004, you could use the StrCmp() function to compare string prefixes. Now you only need to find the largest prefix length at which both string starts are still equal. Technically this is a searching operation and the easy way to do it would be a linear search. But if there's a reasonably good chance the strings have large common parts, you could even abuse binary search for this. The following code snippet finds the largest common prefix length of strings A and B:

local int Low, High, Middle;
 
Low = 0;
High = Min(Len(A), Len(B));
while (Low < High) {
  Middle = (High + Low) / 2;
  if (StrCmp(A, B, Middle) == 0)
    Low = Middle + 1;
  else
    High = Middle;
}

Looks scary? I hope so. ;-)

Collapsing whitespace

This code will remove leading and trailing whitespace from a string and simultaneously collapse any sequence of space and tab characters into a single space:

static final function string CollapseAndTrimWhitespace(string S)
{
  S = " " $ Repl(S, Chr(9), " ") $ " "; // replace tabs with spaces and enclose in space chars for collapsing
  while (InStr(S, "  ") != -1) { // need whitespace collapsing?
    S = Repl(S, "  ", " "); // collapse two adjacent spaces to a single space
  }
  return Mid(S, 1, Len(S) - 2); // remove enclosing space chars
}

I know, InStr() and Repl() are really linear time operations, causing the algorithm to be something like O(n*logn). But as usual UnrealScript's slowness adds a high constant part to them, essentially making them constant time for reasonably short strings (up to a few hundred characters), and the whole function sort-of O(logn).

Integer modulo operator

The [wp:modulo operation|modulo]] operator % in UnrealScript is only defined for float operands, which can cause bad rounding errors if you use it with large int values. The following code overloads the modulo operator for int values:

static final operator(18) int % (int A, int B)
{
  return A - (A / B) * B;
}
<uscript>
This has similar semantics as the float modulo operator, i.e. it may return negative values if you pass in negative operands. Note that (A/B)*B is not the same as A due to integer division dropping any potential remainder.
 
==ArcTan function==
Need the standard one-parameter version of the [[wp:arctan|atan]] function and your version of the game only provides the [wp:atan2|two-parameter version]]? Try this one:
<uscript>
static final function float ArcTan(float A)
{
  return ATan(1, A);
}