Functions for reporting the geometric properties of an array

These functions return the various dimensional properties that jointly characterize an array. These three together completely specify the shape and size:

  • its dimensionality (int)
  • the lower bound along each dimension (int)
  • the upper bound along each dimension (int).

There are functions for returning two other properties: the length along each dimension (int); and its cardinality (int). But each of these can be derived from the set of the lower and upper bounds for all the dimensions. There is also a function that returns the values for the lower and upper bounds for all the dimensions as a single text value.

Overview

The behavior of each of the functions for reporting the geometric properties of an array is illustrated by supplying the same two arrays as the first actual argument to two invocations of the function. The return value, in all cases, is insensitive to the array's data type and the values within the array.

Create and populate "table t" thus:

create table t(k int primary key, arr_1 text[], arr_2 text[]);
insert into t(k, arr_1, arr_2) values(1,
      '[3:10]={ 1, 2, 3, 4,   5, 6, 7, 8}',
  '[2:3][4:7]={{1, 2, 3, 4}, {5, 6, 7, 8}}'
  );

You will use it in the example for each of the functions. (The optional syntax items [3:10] and [2:3][2:5] specify the lower and upper bounds along the one dimension of the first array and along both dimensions of the second array. This syntax is explained in Multidimensional array of numeric values.

Run the SELECT statement for each function to illustrate what produces for the same pair of input arrays.

array_ndims()

Purpose: Return the number of dimensions (that is, the dimensionality) of the specified array.

Signature:

input value:       anyarray
return value:      int

Example:

select
  array_ndims(arr_1) as ndims_1,
  array_ndims(arr_2) as ndims_2
from t where k = 1;

It produces this result:

  ndims_1 | ndims_2
---------+---------
       1 |       2

array_lower()

Purpose: Return the lower bound of the specified array along the specified dimension.

Signature:

input value:       anyarray, int
return value:      int

Example:

select
  array_lower(arr_1, 1) as arr_1_lb,
  array_lower(arr_2, 1) as arr_2_lb_1,
  array_lower(arr_2, 2) as arr_2_lb_2
from t where k = 1;

It produces this result:

 arr_1_lb | arr_2_lb_1 | arr_2_lb_2
----------+------------+------------
        3 |          2 |          4

array_upper()

Purpose: Return the upper bound of the specified array along the specified dimension.

Signature:

input value:       anyarray, int
return value:      int

Example: The use of array_upper() is exactly symmetrical with the use of array_lower().

select
  array_upper(arr_1, 1) as arr_1_ub,
  array_upper(arr_2, 1) as arr_2_ub_1,
  array_upper(arr_2, 2) as arr_2_ub_2
from t where k = 1;

It produces this result:

 arr_1_ub | arr_2_ub_1 | arr_2_ub_2
----------+------------+------------
       10 |          3 |          7

array_length()

Purpose: Return the length of the specified array along the specified dimension. Notice that, among array_lower() and array_upper() and array_length(), the result of any two of them determines the result of the third. You could therefore decide, for example, only to use array_lower() and array_upper() and to determine the length, when you need it, by subtraction. But, for code clarity and brevity, you may as well use exactly the one that matches your present purpose.

Signature:

input value:       anyarray, int
return value:      int

Example: The use of array_length() is exactly symmetrical with the use of array_lower() and array_upper().

select
  array_length(arr_1, 1) as arr_1_len,
  array_length(arr_2, 1) as arr_2_len_1,
  array_length(arr_2, 2) as arr_2_len_2
from t where k = 1;

It produces this result:

 arr_1_len | arr_2_len_1 | arr_2_len_2
-----------+-------------+-------------
         8 |           2 |           4

cardinality()

Purpose: Return the total number of values in the specified array. Notice that the value that this function returns can be computed as the product of values returned by the array_length() function along each direction.

Signature:

input value:       anyarray
return value:      int

Example:

select
  cardinality(arr_1) as card_1,
  cardinality(arr_2) as card_2
from t where k = 1;

It produces this result:

 card_1 | card_2
--------+--------
      8 |      8

array_dims()

Purpose: Return a text representation of the same information as array_lower() and array_length() return, for all dimensions, in a single text value.

Signature:

input value:       anyarray
return value:      text

Example: The array_dims() function is useful to produce a result that is easily humanly readable. If you want to use the information that it returns programmatically, then you should use array_lower(), array_upper(), or array_length().

select
  array_dims(arr_1) as arr_1_dims,
  array_dims(arr_2) as arr_2_dims
from t where k = 1;

It produces this result:

 arr_1_dims | arr_2_dims
------------+------------
 [3:10]     | [2:3][4:7]

Joint semantics

Create the procedure "assert_semantics_and_traverse_values()" and then invoke it for each of the three provided data sets. You supply it with a one-dimensional array and a two dimensional array and, for each, your humanly determined estimates of these values:

  • what array_ndims() returns
  • what array_lower() returns for each dimension
  • what array_upper() returns for each dimension.

The procedure obtains the actual values, programmatically, for all of the values that you supply and asserts that they agree.

Notice that the cardinality and the length along each dimension are omitted, by design, from this list. The procedure also obtains these values, programmatically, and checks that these agree with the values that are computed from, respectively, the length along each dimension and the upper and lower bounds along each dimension.

Notice, too, that the value that array_dims() returns can be computed from the upper and lower bounds along each dimension. So the procedure does this too and checks that the value returned by array_dims() is consistent with the values returned by array_lower() and array_upper().

The procedure has some particular requirements:

  • The cardinality of each of the two supplied arrays must be the same.
  • The actual array values, in row-major order, must be the same, pairwise.

Briefly, "row-major" order is the order in which the last subscript varies most rapidly.

Meeting these requirements are allows the procedure to deliver two bonus benefits. First, it demonstrates how to traverse array values in row-major order using the values returned by the functions that this section describes, thereby demonstrating what the term "row-major order" means. Second. it compares the values, pairwise, for equality. This comparison rule is the basis of the semantics of the comparison operations described in Operators for comparing two arrays.

Note: There are no built-in functions for computing, for example, the product of two matrices or the product of a vector and a matrix. (A vector is a one-dimensional array, and a matrix is a two-dimensional array.) But, as long as you know how to traverse the values in a matrix in row-major order, you can implement the missing vector and matrix multiplication functionality for yourself.

create procedure assert_semantics_and_traverse_values(
  a in int[], b in int[],

  a_ndims in int, a_lb   in int, a_ub   in int,

  b_ndims in int, b_lb_1 in int, b_ub_1 in int,
                  b_lb_2 in int, b_ub_2 in int)
  language plpgsql
as $body$
declare
  -- Get the facts that are implied by the user-supplied facts.
  a_len   constant int := array_length(a, 1);
  b_len_1 constant int := array_length(b, 1);
  b_len_2 constant int := array_length(b, 2);

  a_dims constant text := '['||a_lb::text||':'||a_ub::text||']';
  b_dims constant text := '['||b_lb_1::text||':'||b_ub_1::text||']'||
                          '['||b_lb_2::text||':'||b_ub_2::text||']';
begin
  -- Confirm that the supplied arrays meet the basic
  -- dimensionalities requirement.
  assert
    a_ndims = 1 and b_ndims = 2,
  'ndims assert failed';

  -- Confirm the user-supplied facts about the shape and size of "a".
  assert
    array_ndims(a) = a_ndims           and
    array_lower(a, 1) = a_lb           and
    array_upper(a, 1) = a_ub           ,
  '"a" dimensions assert failed';

  -- Confirm the user-supplied facts about the shape and size of "b".
  assert
    array_ndims(b) = b_ndims           and
    array_lower(b, 1) = b_lb_1         and
    array_upper(b, 1) = b_ub_1         and
    array_lower(b, 2) = b_lb_2         and
    array_upper(b, 2) = b_ub_2         ,
  '"b" dimensions assert failed';

  -- Confirm the length overspecification rule.
  assert
    (a_ub   - a_lb + 1  ) = a_len and
    (b_ub_1 - b_lb_1 + 1) = b_len_1 and
    (b_ub_2 - b_lb_2 + 1) = b_len_2 ,
  'Length overspecification rule assert failed.';

  -- Confirm the cardinality overspecification rule.
  assert
    cardinality(a) = a_len             and
    cardinality(b) = b_len_1 * b_len_2 ,
  'Cardinality overspecification rule assert failed.';

  -- Confirm the "dims" overspecification rule.
  assert
    array_dims(a) = a_dims             and
    array_dims(b) = b_dims             ,
  '"dims" overspecification rule assert failed.';

  -- Do the row-major order traversal and
  -- check that the values are pairwise-identical.
  for j in 0..(a_len - 1) loop
    declare
      -- Traversing a 1-d array is trivial.
      a_idx   constant int := j + a_lb;

      -- Traversing a 2-d array is need a bit more thought.
      b_idx_1 constant int := floor(j/b_len_2)          + b_lb_1;
      b_idx_2 constant int := ((j + b_len_2) % b_len_2) + b_lb_2;

      a_txt   constant text := lpad(a_idx::text,    2);
      b_txt_1 constant text := lpad(b_idx_1::text,  2);
      b_txt_2 constant text := lpad(b_idx_2::text,  2);
      val     constant text := lpad(a[a_idx]::text, 2);

      line constant text :=
        'a['||a_txt||'] = '||
        'b['||b_txt_1||']['||b_txt_2||'] = '||
        val;
    begin
      assert
        a[a_idx] = b[b_idx_1][b_idx_2],
      'Row-major order pairwise equality assert failed';
      raise info '%', line;
    end;
  end loop;
end;
$body$;

Try it on the first data set:

do $body$
declare
  a constant int[] :=            '{ 1, 2,   3,  4,   5,  6}';
  b constant int[] :=            '{{1, 2}, {3,  4}, {5,  6}}';

  a_ndims constant int  := 1;
  a_lb    constant int  := 1;
  a_ub    constant int  := 6;

  b_ndims constant int  := 2;
  b_lb_1  constant int  := 1;
  b_ub_1  constant int  := 3;
  b_lb_2  constant int  := 1;
  b_ub_2  constant int  := 2;
begin
  call assert_semantics_and_traverse_values(
    a, b,
    a_ndims, a_lb,   a_ub,
    b_ndims, b_lb_1, b_ub_1,
             b_lb_2, b_ub_2);
end;
$body$;

It produces this result (after manually removing the "INFO:" prompts):

a[ 1] = b[ 1][ 1] =  1
a[ 2] = b[ 1][ 2] =  2
a[ 3] = b[ 2][ 1] =  3
a[ 4] = b[ 2][ 2] =  4
a[ 5] = b[ 3][ 1] =  5
a[ 6] = b[ 3][ 2] =  6

Try it on the second data set:

do $body$
declare
  a constant int[] :=     '[3:14]={ 1, 2, 3,   4, 5, 6,   7, 8, 9,   10, 11, 12}';
  b constant int[] := '[3:6][6:8]={{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}';

  a_ndims constant int  := 1;
  a_lb    constant int  := 3;
  a_ub    constant int  := 14;

  b_ndims constant int  := 2;
  b_lb_1  constant int  := 3;
  b_ub_1  constant int  := 6;
  b_lb_2  constant int  := 6;
  b_ub_2  constant int  := 8;
begin
  call assert_semantics_and_traverse_values(
    a, b,
    a_ndims, a_lb,   a_ub,
    b_ndims, b_lb_1, b_ub_1,
             b_lb_2, b_ub_2);
end;
$body$;

It produces this result:

a[ 3] = b[ 3][ 6] =  1
a[ 4] = b[ 3][ 7] =  2
a[ 5] = b[ 3][ 8] =  3
a[ 6] = b[ 4][ 6] =  4
a[ 7] = b[ 4][ 7] =  5
a[ 8] = b[ 4][ 8] =  6
a[ 9] = b[ 5][ 6] =  7
a[10] = b[ 5][ 7] =  8
a[11] = b[ 5][ 8] =  9
a[12] = b[ 6][ 6] = 10
a[13] = b[ 6][ 7] = 11
a[14] = b[ 6][ 8] = 12

Try it on the third data set:

do $body$
declare
  a constant int[] :=     '[3:18]={ 1, 2, 3, 4,   5, 6, 7, 8,   9, 10, 11, 12,   13, 14, 15, 16}';
  b constant int[] := '[3:6][2:5]={{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}';

  a_ndims constant int  := 1;
  a_lb    constant int  := 3;
  a_ub    constant int  := 18;

  b_ndims constant int  := 2;
  b_lb_1  constant int  := 3;
  b_ub_1  constant int  := 6;
  b_lb_2  constant int  := 2;
  b_ub_2  constant int  := 5;
begin
  call assert_semantics_and_traverse_values(
    a, b,
    a_ndims, a_lb,   a_ub,
    b_ndims, b_lb_1, b_ub_1,
             b_lb_2, b_ub_2);
end;
$body$;

It produces this result:

a[ 3] = b[ 3][ 2] =  1
a[ 4] = b[ 3][ 3] =  2
a[ 5] = b[ 3][ 4] =  3
a[ 6] = b[ 3][ 5] =  4
a[ 7] = b[ 4][ 2] =  5
a[ 8] = b[ 4][ 3] =  6
a[ 9] = b[ 4][ 4] =  7
a[10] = b[ 4][ 5] =  8
a[11] = b[ 5][ 2] =  9
a[12] = b[ 5][ 3] = 10
a[13] = b[ 5][ 4] = 11
a[14] = b[ 5][ 5] = 12
a[15] = b[ 6][ 2] = 13
a[16] = b[ 6][ 3] = 14
a[17] = b[ 6][ 4] = 15
a[18] = b[ 6][ 5] = 16