%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 1997,1999-2000,2002-2003,2005-2006 The University of Melbourne. % Copyright (C) 2014-2022, 2024-2026 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %---------------------------------------------------------------------------% % % File: bt_array.m % Main author: bromage. % Stability: medium. % % This file contains a set of predicates for generating and manipulating a % bt_array data structure. This implementation allows O(log n) access and % update time, and does not require the bt_array to be unique. If you need % O(1) access/update time, use the array datatype instead. (`bt_array' is % supposed to stand for either "binary tree array" or "backtrackable array".) % % Implementation obscurity: This implementation is biased towards larger % indices. The access/update time for a bt_array of size N with index I is % actually O(log(N-I)). The reason for this is so that the resize operations % can be optimised for a (possibly very) common case, and to exploit % accumulator recursion in some operations. See the documentation of resize % and shrink for more details. % %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- module bt_array. :- interface. :- import_module list. :- type bt_array(T). %---------------------------------------------------------------------------% % % Creating arrays. % % init(Low, High, Init, Array) is true if-and-only-if Array is a bt_array % with bounds from Low to High whose elements each equal Init. % :- func init(int, int, T) = bt_array(T). :- pred init(int::in, int::in, T::in, bt_array(T)::out) is det. % make_empty_array(Low, Array) is true if-and-only-if Array is a bt_array % of size zero starting at index Low. % :- func make_empty_array(int) = bt_array(T). :- pred make_empty_array(int::in, bt_array(T)::out) is det. %---------------------------------------------------------------------------% % % Reading array elements. % % lookup returns the N'th element of a bt_array. % It is an error if the index is out of bounds. % :- func lookup(bt_array(T), int) = T. :- pred lookup(bt_array(T)::in, int::in, T::out) is det. % semidet_lookup is like lookup except that it fails if the index is out of % bounds. % :- pred semidet_lookup(bt_array(T)::in, int::in, T::out) is semidet. % Field selection for arrays. % Array ^ elem(Index) = lookup(Array, Index). % :- func elem(int, bt_array(T)) = T. %---------------------------------------------------------------------------% % % Writing array elements. % % set sets the N'th element of a bt_array, and returns the resulting % bt_array. It is an error if the index is out of bounds. % :- func set(bt_array(T), int, T) = bt_array(T). :- pred set(bt_array(T)::in, int::in, T::in, bt_array(T)::out) is det. % semidet_set sets the N'th element of a bt_array, and returns the % resulting bt_array (good opportunity for destructive update ;-). % It fails if the index is out of bounds. % :- pred semidet_set(bt_array(T)::in, int::in, T::in, bt_array(T)::out) is semidet. % Field update for arrays. % (Array ^ elem(Index) := Value) = set(Array, Index, Value). % :- func 'elem :='(int, bt_array(T), T) = bt_array(T). %---------------------------------------------------------------------------% % Returns the lower bound of the array. % :- func min(bt_array(_T)) = int. :- pred min(bt_array(_T)::in, int::out) is det. % Returns the upper bound of the array. % Returns lower bound - 1 for an empty array. % :- func max(bt_array(_T)) = int. :- pred max(bt_array(_T)::in, int::out) is det. % Returns the length of the array, % i.e. upper bound - lower bound + 1. % :- func size(bt_array(_T)) = int. :- pred size(bt_array(_T)::in, int::out) is det. % bounds(Array, Min, Max) returns the lower and upper bounds of a bt_array. % The upper bound will be the lower bound - 1 for an empty array. % :- pred bounds(bt_array(_T)::in, int::out, int::out) is det. % in_bounds checks whether an index is in the bounds of a bt_array. % :- pred in_bounds(bt_array(_T)::in, int::in) is semidet. %---------------------------------------------------------------------------% % % Resizing arrays. % % resize(Lo, Hi, Item, BtArray0, BtArray): % % Expand or shrink BtArray0 to fit the bounds (Lo, Hi), returning % the result as BtArray. If the new bounds are not wholly contained within % the bounds of BtArray0, Item is used to fill out the other places. % % Note: This operation is optimised for the case where the lower bound % of the new bt_array is the same as that of the old bt_array. In that % case, the operation takes time proportional to the absolute difference % in size between the two bt_arrays. If this is not the case, it may take % time proportional to the larger of the two bt_arrays. % :- func resize(bt_array(T), int, int, T) = bt_array(T). :- pred resize(int::in, int::in, T::in, bt_array(T)::in, bt_array(T)::out) is det. % shrink(Lo, Hi, BtArray0, BtArray): % % Shrink BtArray0 to fit the bounds (Lo, Hi), returning the result % as BtArray. It is an error if the new bounds are not wholly within % the bounds of BtArray0. % % Note: This operation is optimised for the case where the lower bound % of the new bt_array is the same as that of the old bt_array. In that % case, the operation takes time proportional to the absolute difference % in size between the two bt_arrays. If this is not the case, it may take % time proportional to the larger of the two bt_arrays. % :- func shrink(bt_array(T), int, int) = bt_array(T). :- pred shrink(int::in, int::in, bt_array(T)::in, bt_array(T)::out) is det. %---------------------------------------------------------------------------% % % Conversions between bt_arrays and lists. % % from_list(Low, List, BtArray) takes a list (of possibly zero % length), and returns a bt_array containing those elements in the same % order that they occurred in the list. The lower bound of the new array % is Low. % :- func from_list(int, list(T)) = bt_array(T). :- pred from_list(int::in, list(T)::in, bt_array(T)::out) is det. % to_list takes a bt_array and returns a list containing the elements % of the bt_array in the same order that they occurred in the bt_array. % :- func to_list(bt_array(T)) = list(T). :- pred to_list(bt_array(T)::in, list(T)::out) is det. % fetch_items takes a bt_array and a lower and upper index, % and places those items in the bt_array between these indices into a list. % It is an error if either index is out of bounds. % :- func fetch_items(bt_array(T), int, int) = list(T). :- pred fetch_items(bt_array(T)::in, int::in, int::in, list(T)::out) is det. %---------------------------------------------------------------------------% % bsearch takes a bt_array, an element to be matched and a % comparison predicate, and returns the position of the first occurrence % in the bt_array of an element which is equivalent to the given one % in the ordering provided. Assumes the bt_array is sorted according % to this ordering. Fails if the element is not present. % :- pred bsearch(bt_array(T)::in, T::in, comparison_pred(T)::in(comparison_pred), int::out) is semidet. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module int. :- import_module ra_list. :- import_module require. :- type bt_array(T) ---> bt_array(int, int, ra_list(T)). % The lower bound, the upper bound, and the elements. :- pred actual_position(int::in, int::in, int::in, int::out) is det. :- pragma inline(pred(actual_position/4)). actual_position(Low, High, Index, Pos) :- Pos = High - Low - Index. %---------------------------------------------------------------------------% init(N1, N2, T) = BTA :- init(N1, N2, T, BTA). init(Low, High, Item, bt_array(Low, High, List)) :- ra_list.init(List0), ElemsToAdd = High - Low + 1, add_elements(ElemsToAdd, Item, List0, List). :- pred add_elements(int::in, T::in, ra_list(T)::in, ra_list(T)::out) is det. add_elements(ElemsToAdd, Item, RaList0, RaList) :- ( if ElemsToAdd =< 0 then RaList0 = RaList else ra_list.cons(Item, RaList0, RaList1), ElemsToAdd1 = ElemsToAdd - 1, add_elements(ElemsToAdd1, Item, RaList1, RaList) ). make_empty_array(N) = BTA :- make_empty_array(N, BTA). make_empty_array(Low, bt_array(Low, High, List)) :- High = Low - 1, ra_list.init(List). %---------------------------------------------------------------------------% lookup(BTA, N) = T :- lookup(BTA, N, T). lookup(bt_array(Low, High, RaList), Index, Item) :- actual_position(Low, High, Index, Pos), ( if ra_list.index0(RaList, Pos, Item0) then Item = Item0 else unexpected($pred, "array subscript out of bounds") ). semidet_lookup(bt_array(Low, High, RaList), Index, Item) :- actual_position(Low, High, Index, Pos), ra_list.index0(RaList, Pos, Item). elem(Index, Array) = lookup(Array, Index). %---------------------------------------------------------------------------% set(BT1A, N, T) = BTA2 :- set(BT1A, N, T, BTA2). set(BtArray0, Index, Item, BtArray) :- ( if semidet_set(BtArray0, Index, Item, BtArray1) then BtArray = BtArray1 else unexpected($pred, "index out of bounds") ). semidet_set(BtArray0, Index, Item, BtArray) :- BtArray0 = bt_array(Low, High, RaList0), actual_position(Low, High, Index, Pos), ra_list.update(Pos, Item, RaList0, RaList), BtArray = bt_array(Low, High, RaList). 'elem :='(Index, Array0, Value) = Array :- set(Array0, Index, Value, Array). %---------------------------------------------------------------------------% min(BTA) = N :- min(BTA, N). min(bt_array(Low, _, _), Low). max(BTA) = N :- max(BTA, N). max(bt_array(_, High, _), High). size(BTA) = N :- size(BTA, N). size(bt_array(Low, High, _), Size) :- Size = High - Low + 1. bounds(bt_array(Low, High, _), Low, High). in_bounds(bt_array(Low, High, _), Index) :- Low =< Index, Index =< High. %---------------------------------------------------------------------------% resize(BT1A, N1, N2, T) = BTA2 :- resize(N1, N2, T, BT1A, BTA2). resize(L, H, Item, Array0, Array) :- Array0 = bt_array(L0, H0, RaList0), ( if L = L0 then % Optimise the common case where the lower bounds are % the same. ( if H < H0 then SizeDiff = H0 - H, ( if ra_list.drop(SizeDiff, RaList0, RaList1) then RaList = RaList1 else unexpected($pred, "can't resize to a less-than-empty array") ), Array = bt_array(L, H, RaList) else if H > H0 then SizeDiff = H - H0, add_elements(SizeDiff, Item, RaList0, RaList), Array = bt_array(L, H, RaList) else Array = Array0 ) else int.max(L, L0, L1), int.min(H, H0, H1), fetch_items(Array0, L1, H1, Items), init(L, H, Item, Array1), insert_items(Array1, L1, Items, Array) ). shrink(BT1A, N1, N2) = BTA2 :- shrink(N1, N2, BT1A, BTA2). shrink(L, H, Array0, Array) :- Array0 = bt_array(L0, H0, RaList0), ( if ( L < L0 ; H > H0 ) then unexpected($pred, "new bounds are larger than old ones") else if L = L0 then % Optimise the common case where the lower bounds are the same. SizeDiff = H0 - H, ( if ra_list.drop(SizeDiff, RaList0, RaList1) then RaList = RaList1 else unexpected($pred, "can't resize to a less-than-empty array") ), Array = bt_array(L, H, RaList) else ( if ra_list.head(RaList0, Item0) then Item = Item0 else unexpected($pred, "can't shrink an empty array") ), int.max(L, L0, L1), int.min(H, H0, H1), fetch_items(Array0, L1, H1, Items), init(L, H, Item, Array1), insert_items(Array1, L1, Items, Array) ). %---------------------% :- pred insert_items(bt_array(T)::in, int::in, list(T)::in, bt_array(T)::out) is det. insert_items(Array, _N, [], Array). insert_items(Array0, N, [Head|Tail], Array) :- set(Array0, N, Head, Array1), N1 = N + 1, insert_items(Array1, N1, Tail, Array). %---------------------------------------------------------------------------% from_list(N, Xs) = BTA :- from_list(N, Xs, BTA). from_list(Low, List, bt_array(Low, High, RaList)) :- list.length(List, Len), High = Low + Len - 1, init(RaList0), reverse_into_ra_list(List, RaList0, RaList). :- pred reverse_into_ra_list(list(T)::in, ra_list(T)::in, ra_list(T)::out) is det. reverse_into_ra_list([], RaList, RaList). reverse_into_ra_list([X | Xs], RaList0, RaList) :- cons(X, RaList0, RaList1), reverse_into_ra_list(Xs, RaList1, RaList). %---------------------% to_list(BTA) = Xs :- to_list(BTA, Xs). to_list(bt_array(_, _, RaList), List) :- reverse_from_ra_list(RaList, [], List). :- pred reverse_from_ra_list(ra_list(T)::in, list(T)::in, list(T)::out) is det. reverse_from_ra_list(RaList0, Xs0, Xs) :- ( if head_tail(RaList0, X, RaList1) then reverse_from_ra_list(RaList1, [X | Xs0], Xs) else Xs0 = Xs ). %---------------------% fetch_items(BTA, N1, N2) = Xs :- fetch_items(BTA, N1, N2, Xs). fetch_items(bt_array(ALow, AHigh, RaList0), Low, High, List) :- ( if Low > High then List = [] else if actual_position(ALow, AHigh, High, Drop), ra_list.drop(Drop, RaList0, RaList), Take = High - Low + 1, reverse_from_ra_list_count(Take, RaList, [], List0) then List = List0 else List = [] ). :- pred reverse_from_ra_list_count(int::in, ra_list(T)::in, list(T)::in, list(T)::out) is det. reverse_from_ra_list_count(I, RaList0, Xs0, Xs) :- ( if ra_list.head_tail(RaList0, X, RaList1), I >= 0 then I1 = I - 1, reverse_from_ra_list_count(I1, RaList1, [X | Xs0], Xs) else Xs0 = Xs ). %---------------------------------------------------------------------------% bsearch(A, SearchX, Compare, I) :- bounds(A, Lo, Hi), Lo =< Hi, bsearch_loop(A, Lo, Hi, SearchX, Compare, I). % XXX Would we gain anything by traversing the ra_list instead % of doing a vanilla binary chop? % :- pred bsearch_loop(bt_array(T)::in, int::in, int::in, T::in, pred(T, T, comparison_result)::in(pred(in, in, out) is det), int::out) is semidet. bsearch_loop(A, Lo, Hi, SearchX, Compare, I) :- Width = Hi - Lo, % If Width < 0, there is no range left. Width >= 0, % If Width == 0, we may just have found our element. % Do a Compare to check. ( if Width = 0 then lookup(A, Lo, LoX), Compare(SearchX, LoX, (=)), I = Lo else % We calculate Mid this way to avoid overflow, and because it works % even if Lo, and maybe Hi, is negative. Mid = Lo + ((Hi - Lo) `unchecked_right_shift` 1), % The right shift by one bit is a fast implementation of division by 2. lookup(A, Mid, MidX), Compare(MidX, SearchX, Comp), ( Comp = (<), bsearch_loop(A, Mid + 1, Hi, SearchX, Compare, I) ; Comp = (=), bsearch_loop(A, Lo, Mid, SearchX, Compare, I) ; Comp = (>), bsearch_loop(A, Lo, Mid - 1, SearchX, Compare, I) ) ). %---------------------------------------------------------------------------% :- end_module bt_array. %---------------------------------------------------------------------------%