Files
mercury/library/version_array2d.m
Julien Fischer 8f61d08328 Fix errors in version_array2d documentation.
library/version_array2d.m:
    As above.
2026-02-16 13:56:50 +11:00

441 lines
18 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2004-2006, 2011 The University of Melbourne.
% Copyright (C) 2013-2015, 2017-2019, 2022, 2024-2026 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: version_array2d.m.
% Author: Ralph Becket <rafe@cs.mu.oz.au>.
% Stability: medium.
%
% Two-dimensional rectangular (i.e. not ragged) version arrays.
%
% See the header comments in version_array.m for more details about version
% structures.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- module version_array2d.
:- interface.
:- import_module list.
%---------------------------------------------------------------------------%
% A version_array2d is a two-dimensional version array which stores
% the array in row-major order, meaning that first it stores
% the elements of the first row in left-to-right order, then the elements
% of the second row in the same order, and so on.
%
:- type version_array2d(T).
% version_array2d([[X11, ..., X1N], ..., [XM1, ..., XMN]]):
%
% Given a list of NumRows (M) elements, each of which is a list of
% NumColumns (N) elements, returns the two-dimensional array
% with NumRows rows and NumColumns columns.
%
% Throws an exception if the sublists are not all the same length.
%
% When given the empty list as input, it sets not just NumRows
% but also NumColumns to zero.
%
:- func version_array2d(list(list(T))) = version_array2d(T).
% init(NumRows, NumColumns, DefaultValue):
% uinit(NumRows, NumColumns, DefaultValue):
%
% Returns the two-dimensional array with the given numbers of rows and
% columns in which every element is DefaultValue.
%
% Throws an exception if either NumRows or NumColumns is negative.
%
:- func init(int, int, T) = version_array2d(T).
:- func uinit(uint, uint, T) = version_array2d(T).
% bounds(VersionArray2d, NumRows, NumColumns):
% ubounds(VersionArray2d, NumRows, NumColumns):
%
% Given VersionArray2d, returns the number of rows and columns it has.
%
:- pred bounds(version_array2d(T)::in, int::out, int::out) is det.
:- pred ubounds(version_array2d(T)::in, uint::out, uint::out) is det.
% in_bounds(VersionArray2d, RowNum, ColumnNum):
% in_ubounds(VersionArray2d, RowNum, ColumnNum):
%
% Succeeds if-and-only-if both RowNum and ColumnNum are in-bounds
% for VersionArray2d. If the sizes of the two dimensions of VersionArray2d
% are NumRows and NumColumns respectively, this means that this predicate
% succeeds if-and-only-if both 0 =< RowNum < NumRows and
% 0 =< ColumnNum < NumColumns are true.
%
:- pred in_bounds(version_array2d(T)::in, int::in, int::in) is semidet.
:- pred in_ubounds(version_array2d(T)::in, uint::in, uint::in) is semidet.
% lookup(VersionArray2d, RowNum, ColumnNum, Value):
% ulookup(VersionArray2d, RowNum, ColumnNum, Value):
% VersionArray2d ^ elem(RowNum, ColumnNum) = Value:
% VersionArray2d ^ uelem(RowNum, ColumnNum) = Value:
%
% Return the value at the given row and column numbers in VersionArray2d.
% Note that both row and column numbers start from zero.
%
% Throw an exception if either RowNum or ColumnNum is out of bounds.
%
:- pred lookup(version_array2d(T)::in, int::in, int::in, T::out) is det.
:- pred ulookup(version_array2d(T)::in, uint::in, uint::in, T::out) is det.
:- func elem(int, int, version_array2d(T)) = T.
:- func uelem(uint, uint, version_array2d(T)) = T.
% set(RowNum, ColumnNum, X, !VersionArray2d):
% uset(RowNum, ColumnNum, X, !VersionArray2d):
% ( !.VersionArray2d ^ elem(RowNum, ColumnNum) := X ) = !:VersionArray2d:
% ( !.VersionArray2d ^ uelem(RowNum, ColumnNum) := X ) = !:VersionArray2d:
%
% Return a version of the initial version_array2d that contains
% all the same values, with the exception that the element at the
% given row and column number is set to X.
%
% Throw an exception if either RowNum or ColumnNum is out of bounds.
%
:- pred set(int::in, int::in, T::in,
version_array2d(T)::in, version_array2d(T)::out) is det.
:- pred uset(uint::in, uint::in, T::in,
version_array2d(T)::in, version_array2d(T)::out) is det.
:- func 'elem :='(int, int, version_array2d(T), T) = version_array2d(T).
:- func 'uelem :='(uint, uint, version_array2d(T), T) = version_array2d(T).
% lists(version_array2d([[X11, ..., X1N], ..., [XM1, ..., XMN]])) =
% [[X11, ..., X1N], ..., [XM1, ..., XMN]]
%
:- func lists(version_array2d(T)) = list(list(T)).
% copy(OldVersionArray2d) = VersionArray2d:
%
% Returns a copy of OldVersionArray2d that has O(1) access time
% to all its elements.
%
:- func copy(version_array2d(T)) = version_array2d(T).
% resize(OldVersionArray2d, NumRows, NumColumns, DefaultValue)
% = VersionArray2d:
% uresize(OldVersionArray2d, NumRows, NumColumns, DefaultValue)
% = VersionArray2d:
%
% Returns a copy of OldVersionArray2d that is resized to
% NumRows * NumColumns. Items with coordinates that exist in
% OldVersionArray2d are copied from OldVersionArray2d; all other items
% are initialised to DefaultValue.
%
% Throws an exception if either NumRows < 0 or NumColumns < 0.
%
:- func resize(version_array2d(T), int, int, T) = version_array2d(T).
:- func uresize(version_array2d(T), uint, uint, T) = version_array2d(T).
% unsafe_rewind(OldVersionArray2d) = VersionArray2d
%
% Returns a new 2d version array that has O(1) access time to all its
% elements, at the cost of rendering the contents of OldVersionArray2d
% and all its descendants undefined.
%
% Call this function *only* if you are absolutely certain that
% there are no remaining live references to either OldVersionArray2d
% or to any of its descendants.
%
:- func unsafe_rewind(version_array2d(T)) = version_array2d(T).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module exception.
:- import_module int.
:- import_module require.
:- import_module string.
:- import_module uint.
:- import_module version_array.
%---------------------------------------------------------------------------%
% version_array2d(NumRows, NumColumns, Array)
%
:- type version_array2d(T)
---> version_array2d(int, int, version_array(T)).
%---------------------------------------------------------------------------%
version_array2d(Rows) = VersionArray2d :-
(
Rows = [],
VersionArray2d = version_array2d(0, 0, version_array([]))
;
Rows = [FirstRow | _],
list.length(Rows, NumRows),
list.length(FirstRow, FirstRowNumColumns),
( if
all [Row] (
list.member(Row, Rows)
=>
list.length(Row) = FirstRowNumColumns
)
then
VersionArray = version_array(list.condense(Rows)),
VersionArray2d =
version_array2d(NumRows, FirstRowNumColumns, VersionArray)
else
error($pred, "non-rectangular list of lists")
)
).
%---------------------------------------------------------------------------%
init(NumRows, NumColumns, InitValue) = VersionArray2d :-
( if NumRows >= 0, NumColumns >= 0 then
VersionArray = version_array.init(NumRows * NumColumns, InitValue),
VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray)
else
error($pred, "bounds must be non-negative")
).
uinit(NumRows, NumColumns, InitValue) = VersionArray2d :-
NumRowsI = uint.cast_to_int(NumRows),
NumColumnsI = uint.cast_to_int(NumColumns),
VersionArray2d = init(NumRowsI, NumColumnsI, InitValue).
%---------------------------------------------------------------------------%
bounds(version_array2d(NumRows, NumColumns, _A), NumRows, NumColumns).
ubounds(version_array2d(NumRowsI, NumColumnsI, _A), NumRows, NumColumns) :-
NumRows = uint.cast_from_int(NumRowsI),
NumColumns = uint.cast_from_int(NumColumnsI).
%---------------------------------------------------------------------------%
in_bounds(version_array2d(NumRows, NumColumns, _A), RowNum, ColumnNum) :-
0 =< RowNum, RowNum < NumRows,
0 =< ColumnNum, ColumnNum < NumColumns.
in_ubounds(version_array2d(NumRowsI, NumColumnsI, _A), RowNum, ColumnNum) :-
RowNumI = uint.cast_to_int(RowNum),
ColumnNumI = uint.cast_to_int(ColumnNum),
RowNumI < NumRowsI,
ColumnNumI < NumColumnsI.
%---------------------------------------------------------------------------%
lookup(VersionArray2d, RowNum, ColumnNum, Value) :-
VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray),
( if not (0 =< RowNum, RowNum < NumRows) then
out_of_bounds_error("version_array2d.lookup", "row",
RowNum, NumRows)
else if not (0 =< ColumnNum, ColumnNum < NumColumns) then
out_of_bounds_error("version_array2d.lookup", "column",
ColumnNum, NumColumns)
else
Slot = RowNum * NumColumns + ColumnNum,
version_array.lookup(VersionArray, Slot, Value)
).
ulookup(VersionArray2d, RowNum, ColumnNum, Value) :-
VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray),
RowNumI = uint.cast_to_int(RowNum),
ColumnNumI = uint.cast_to_int(ColumnNum),
( if not (RowNumI < NumRows) then
out_of_bounds_error("version_array2d.ulookup", "row",
RowNumI, NumRows)
else if not (ColumnNumI < NumColumns) then
out_of_bounds_error("version_array2d.ulookup", "column",
ColumnNumI, NumColumns)
else
Slot = RowNumI * NumColumns + ColumnNumI,
version_array.lookup(VersionArray, Slot, Value)
).
elem(RowNum, ColumnNum, VersionArray2d) = Value :-
lookup(VersionArray2d, RowNum, ColumnNum, Value).
uelem(RowNum, ColumnNum, VersionArray2d) = Value :-
ulookup(VersionArray2d, RowNum, ColumnNum, Value).
%---------------------------------------------------------------------------%
set(RowNum, ColumnNum, NewValue, !VersionArray2d) :-
!.VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray0),
( if not (0 =< RowNum, RowNum < NumRows) then
out_of_bounds_error("version_array2d.set", "row",
RowNum, NumRows)
else if not (0 =< ColumnNum, ColumnNum < NumColumns) then
out_of_bounds_error("version_array2d.set", "column",
ColumnNum, NumColumns)
else
SlotNum = RowNum * NumColumns + ColumnNum,
version_array.set(SlotNum, NewValue, VersionArray0, VersionArray),
!:VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray)
).
uset(RowNum, ColumnNum, NewValue, !VersionArray2d) :-
!.VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray0),
RowNumI = uint.cast_to_int(RowNum),
ColumnNumI = uint.cast_to_int(ColumnNum),
( if not (RowNumI < NumRows) then
out_of_bounds_error("version_array2d.uset", "row",
RowNumI, NumRows)
else if not (ColumnNumI < NumColumns) then
out_of_bounds_error("version_array2d.uset", "column",
ColumnNumI, NumColumns)
else
SlotNum = RowNumI * NumColumns + ColumnNumI,
version_array.set(SlotNum, NewValue, VersionArray0, VersionArray),
!:VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray)
).
'elem :='(RowNum, ColumnNum, !.VersionArray2d, NewValue) = !:VersionArray2d :-
set(RowNum, ColumnNum, NewValue, !VersionArray2d).
'uelem :='(RowNum, ColumnNum, !.VersionArray2d, NewValue) = !:VersionArray2d :-
uset(RowNum, ColumnNum, NewValue, !VersionArray2d).
%---------------------------------------------------------------------------%
lists(VersionArray2d) = List :-
VersionArray2d = version_array2d(NumRows, NumColumns, VersionArray),
List = lists_2((NumRows * NumColumns) - 1, NumColumns - 1, NumColumns,
VersionArray, [], []),
acc_rev_rows(VersionArray, NumRows, NumColumns, NumRows - 1, [], ListB),
expect(unify(List, ListB), $pred,
"lists results mismatch" ++ string.string(List) ++
" vs " ++ string.string(ListB)).
% XXX This predicate should be replaced by two predicates.
% One should copy the elements of ONE row, looping over the columns,
% while the other should loop over all the rows.
%
:- func lists_2(int, int, int, version_array(T), list(T),
list(list(T))) = list(list(T)).
lists_2(IJ, J, N, VA, Xs, Xss) =
( if 0 =< IJ then
( if 0 =< J then
lists_2(IJ - 1, J - 1, N, VA, [VA ^ elem(IJ) | Xs], Xss )
else
lists_2(IJ, N - 1, N, VA, [], [Xs | Xss])
)
else
[Xs | Xss]
).
:- pred acc_rev_rows(version_array(T)::in, int::in, int::in, int::in,
list(list(T))::in, list(list(T))::out) is det.
acc_rev_rows(VersionArray, NumRows, NumColumns, RowNum, !Rows) :-
( if 0 =< RowNum then
RowBase = RowNum * NumColumns,
acc_rev_columns(VersionArray, RowBase, NumColumns, NumColumns - 1,
[], Row),
!:Rows = [Row | !.Rows],
acc_rev_rows(VersionArray, NumRows, NumColumns, RowNum - 1, !Rows)
else
true
).
:- pred acc_rev_columns(version_array(T)::in, int::in, int::in, int::in,
list(T)::in, list(T)::out) is det.
acc_rev_columns(VersionArray, RowBase, NumColumns, ColumnNum, !ItemsInRow) :-
( if 0 =< ColumnNum then
lookup(VersionArray, RowBase + ColumnNum, Item),
!:ItemsInRow = [Item | !.ItemsInRow],
acc_rev_columns(VersionArray, RowBase, NumColumns, ColumnNum - 1,
!ItemsInRow)
else
true
).
%---------------------------------------------------------------------------%
copy(VersionArray2d) = CopyVersionArray2d :-
VersionArray2d = version_array2d(NumRows, NumColumns, VA),
CopyVersionArray2d = version_array2d(NumRows, NumColumns, copy(VA)).
%---------------------------------------------------------------------------%
resize(OldVersionArray2d, NewNumRows, NewNumColumns, DefaultValue)
= !:VersionArray2d :-
!:VersionArray2d = init(NewNumRows, NewNumColumns, DefaultValue),
bounds(OldVersionArray2d, OldNumRows, OldNumColumns),
% We cannot copy more rows or columns from OldVersionArray2d
% than it actually has. Any slots in !:VersionArray2d whose
% row and/or column numbers do not exist in OldVersionArray2d
% will have been set to DefaultValue by the call to init above.
CopyNumRows = min(OldNumRows, NewNumRows),
CopyNumColumns = min(OldNumColumns, NewNumColumns),
resize_copy_loop(0, 0, CopyNumRows, CopyNumColumns,
OldVersionArray2d, !VersionArray2d).
% XXX This predicate could be replaced by two predicates,
% with one predicate copying ONE row, looping over the columns,
% while the other loops over the rows themselves.
%
:- pred resize_copy_loop(int::in, int::in, int::in, int::in,
version_array2d(T)::in,
version_array2d(T)::in, version_array2d(T)::out) is det.
resize_copy_loop(RowNum, ColumnNum, CopyNumRows, CopyNumColumns,
OldVersionArray2d, !VersionArray2d) :-
( if RowNum >= CopyNumRows then
% We have copied all the rows that exist in OldVersionArray2d.
% Any rows in !VersionArray2d that are not in OldVersionArray2d
% will have been set to DefaultValue by our caller.
true
else if ColumnNum >= CopyNumColumns then
% We have copied all the columns in this row that exist in
% OldVersionArray2d. Any later columns in !VersionArray2d
% that are not in OldVersionArray2d will have been set
% to DefaultValue by our caller.
resize_copy_loop(RowNum + 1, 0, CopyNumRows, CopyNumColumns,
OldVersionArray2d, !VersionArray2d)
else
lookup(OldVersionArray2d, RowNum, ColumnNum, Item),
set(RowNum, ColumnNum, Item, !VersionArray2d),
resize_copy_loop(RowNum, ColumnNum + 1, CopyNumRows, CopyNumColumns,
OldVersionArray2d, !VersionArray2d)
).
uresize(OldVersionArray2d, NewNumRows, NewNumColumns, DefaultValue)
= VersionArray2d :-
VersionArray2d = resize(OldVersionArray2d,
uint.cast_to_int(NewNumRows), uint.cast_to_int(NewNumColumns),
DefaultValue).
%---------------------------------------------------------------------------%
unsafe_rewind(version_array2d(NumRows, NumColumns, VersionArray)) =
version_array2d(NumRows, NumColumns, unsafe_rewind(VersionArray)).
%---------------------------------------------------------------------------%
% Throw an exception indicating an array bounds error.
%
:- pred out_of_bounds_error(string::in, string::in, int::in, int::in)
is erroneous.
out_of_bounds_error(PredName, RowOrColumn, Index, Max) :-
% Note: we deliberately do not include the array element type name in the
% error message here, for performance reasons: using the type name could
% prevent the compiler from optimizing away the construction of the
% type_info in the caller, because it would prevent unused argument
% elimination.
string.format("%s: %s index %d not in range [0, %d]",
[s(PredName), s(RowOrColumn), i(Index), i(Max)], Msg),
throw(index_out_of_bounds(Msg)).
%---------------------------------------------------------------------------%
:- end_module version_array2d.
%---------------------------------------------------------------------------%