Prefix Sums - Applicability & Use-Case

Defining the setup of using Prefix Sum technique. Assessing its applicability and potential use-cases

Prefix Sums - Applicability & Use-Case

Prefix Sums Study Resources

Note that the Prefix Sum technique requires that the elements from the set have the existence of inverse property. For instance, in case of computing Range Sum, from [l, r], we effectively, add the additive inverse of pref[l-1] to pref[r], to get the identity element 0 for the sum which is outside the range of [l, r], namely, elements from [0, l-1]. We cannot therefore employ this technique to compute Range Min/Max as there is no inverse element for these operations.

The above technique does not work straight-off for non-commutative operations. How can it be adapted to this case?

This is a very important and provoking question. At its core, this questions the setup under which the Prefix Sum technique is applicable, with a flavor of generalizing this setup.

It turns out that if you are applying the Prefix Sum technique on the “field” (S,*), where S is the set of elements (more importantly the class of elements or rather the type of elements (integers, strings, matrices, etc.)), and * is the operation on which they are being accumulated. For instance, the basic setup of a Prefix “Sum“ array happens over (int,+). The general setup however requires the following properties on this field:

  1. Any element of S say x must satisfy the following properties:

    • Existence of identity

      There must exist an element I in S such that I*x=x*I=x. Also, it is clear that the identity element is invariant to the general commutativity situation of the operation *.

    • Existence of inverse

      For every x in S, there must exist inv(x) in S, such that inv(x)*x = x*inv(x)=I, where I is the identity element over * in S and inv(x) denotes the inverse of x. Note, that again this expression is invariant to the general commutativity situation of the operator *.

  2. * is associative*. “*Commutativity makes things easier. But even if it is non-commutative, we just have to extra careful in defining the inverse operation, which answering range queries”.

Think about matrix multiplication for the example of non-commutative associative. If * is non-associative, there is no question that the prefix sum approach would not be applicable, at least not unambiguously. Consider the operator - on A={1,2,3}. The prefix “difference” array can be either {1,1-2,(1-2)-3} or {1,1-2,1-(2-3)}. So, there is ambiguity in defining the operation, and it can become complicated in execution, probably. Based on the traditional left associativity of -, we may be tempted to move forward with one of these options, but that is completely dependent on intended use-case.

This paper discusses the applicability of the Prefix Sum technique, when the operation being tackled is associative with invariance to its commutativity property. Consider Matrix Multiplication and String Concatenation. (Both are associative and non-commutative)

  • Matrix Multiplication - Traditional computation of prefix products from left to right. To compute range product simply compute inv(pref[l-1]) * pref[r], where inv(A) denotes the traditional inverse of a matrix.

  • String Concatenation - Computation of the prefix array would entail concatenations of the cumulative strings, with the definition, that s1+s2 is s2 appended to s1. With this definition, it is easy to define the inverse of a string concatenation operation while computing a range concatenation. For instance if the string array is {“abc“, “def“, “ghi“}, then the prefix array would be (still rather long) {“abc“, “abcdef“, “abcdefghi“}. To compute the range string concatenation with l=1, r=2, we can perform the following computation. inv(pref[l-1]) + pref[r], where inv(s) denotes the string s reversed, and we perform character cancellation until we have exhausted pref[l-1].size() number of steps. Visualize a stack of characters.

struct matrix {}; // assume standard definition of a 2D matrix
struct query {int l,r;};

matrix a[] = {A,B,C,D};
matrix pref[];

for (i = 0 .. (a.size()-1)) pref[i] = (i ? pref[i-1] : I) * a[i]; // computes the matrix product
query q = query(l,r); // defines a query to compute matrix products from index l to r

matrix range_product = (l ? inv(pref[l-1]) : I) * pref[r]; // (pref[l-1])^(-1) * pref[r]