aboutsummaryrefslogblamecommitdiffstats
path: root/lib/hipe/misc/hipe_segment_trees.erl
blob: cbee328125c48ae6e2670769985feb361dd69805 (plain) (tree)





















































































































































                                                                                
%%%
%%% %CopyrightBegin%
%%%
%%% Copyright Ericsson AB 2016. All Rights Reserved.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%% %CopyrightEnd%
%%%
%%% Segment trees.
%%%
%%% Keys are the (0-based) indices into the list passed to build/1.
%%%
%%% Range bounds are inclusive.
%%%
%%% TODO: Change the shape of the tree to a perfect binary tree, and pack it as
%%% an implicit data structure into tuples (like a binary heap would be) for
%%% improved efficiency.

-module(hipe_segment_trees).

-export([build/1, intersect/2]).

-record(segment_tree, {
	  lo            :: integer(),
	  hi            :: integer(),
	  root          :: tnode()
	 }).

%% X =< Mid belongs in Left
-define(NODE(Left, Right, Mid, Segments), {Left, Right, Mid, Segments}).

-define(POINT_LEAF(Val), Val).
-define(RANGE_LEAF(Lo, Hi), {Lo, Hi}).

-type segments() :: [non_neg_integer()].
-type leaf()     :: segments().
-type tnode()    :: ?NODE(tnode(), tnode(), integer(), segments()) | leaf().

-opaque tree() :: #segment_tree{} | nil.
-export_type([tree/0]).

%% @doc Builds a segment tree of the given intervals.
-spec build([{integer(), integer()}]) -> tree().
build(ListOfIntervals) ->
    case
	lists:usort(
	  lists:append(
	    [[Lo, Hi] || {Lo, Hi} <- ListOfIntervals, Lo =< Hi]))
    of
	[] -> nil;
	Endpoints ->
	    Tree0 = empty_tree_from_endpoints(Endpoints),
	    [Lo|_] = Endpoints,
	    Hi = lists:last(Endpoints),
	    Tree1 = insert_intervals(0, ListOfIntervals, Lo, Hi, Tree0),
	    Tree = squash_empty_subtrees(Tree1),
	    #segment_tree{lo=Lo, hi=Hi, root=Tree}
    end.

empty_tree_from_endpoints(Endpoints) ->
    Leaves = leaves(Endpoints),
    {T, [], _, _} = balanced_bst(Leaves, length(Leaves)),
    T.

leaves([Endpoint]) -> [?POINT_LEAF(Endpoint)];
leaves([A | [B|_] = Tail]) ->
    %% We could omit the range leaf if it's empty, but we want to pack this data
    %% structure into an array (tuple) eventually, and then we *really* want
    %% every other leaf to be a range
    case A<B-1 of
	true  -> [?POINT_LEAF(A),?RANGE_LEAF(A+1,B-1) | leaves(Tail)];
	false -> [?POINT_LEAF(A) | leaves(Tail)]
    end.

balanced_bst(L, S) when S > 1 ->
    Sm = S, %% - 1
    S2 = Sm div 2,
    S1 = Sm - S2,
    {Left, L1, LeftLo, LeftHi} = balanced_bst(L, S1),
    {Right, L2, _, RightHi} = balanced_bst(L1, S2),
    T = ?NODE(Left, Right, LeftHi, []),
    {T, L2, LeftLo, RightHi};
balanced_bst([?RANGE_LEAF(Lo, Hi) | L], 1) ->
    {[], L, Lo, Hi};
balanced_bst([?POINT_LEAF(Val) | L], 1) ->
    {[], L, Val, Val}.

insert_intervals(_Ix, [], _Lo, _Hi, Tree) -> Tree;
insert_intervals(Ix, [Int|Ints], Lo, Hi, Tree) ->
    insert_intervals(Ix + 1, Ints, Lo, Hi,
		     insert_interval(Ix, Int, Lo, Hi, Tree)).

insert_interval(_, {Lo, Hi}, _, _, Node) when Lo > Hi -> Node;
insert_interval(I, Int={Lo,Hi}, NLo, NHi,
		?NODE(Left0, Right0, Mid, Segments)) ->
    if Lo =< NLo, NHi =< Hi ->
	    ?NODE(Left0, Right0, Mid, [I|Segments]);
       true ->
	    Left = case intervals_intersect(Lo, Hi,    NLo, Mid) of
		       true -> insert_interval(I, Int, NLo, Mid, Left0);
		       false -> Left0
		   end,
	    Right = case intervals_intersect(Lo, Hi,    Mid+1, NHi) of
			true -> insert_interval(I, Int, Mid+1, NHi, Right0);
			false -> Right0
		   end,
	    ?NODE(Left, Right, Mid, Segments)
    end;
insert_interval(I, {_Lo,_Hi}, _NLo, _NHi, Leaf) -> [I|Leaf].

intervals_intersect(ALo, AHi, BLo, BHi) ->
    (ALo =< AHi) andalso (BLo =< BHi) %% both nonempty
	andalso (BLo =< AHi) andalso (ALo =< BHi).

%% Purely optional optimisation
squash_empty_subtrees(?NODE(Left0, Right0, Mid, Segs)) ->
    build_squash_node(squash_empty_subtrees(Left0),
		      squash_empty_subtrees(Right0),
		      Mid, Segs);
squash_empty_subtrees(Leaf) -> Leaf.

build_squash_node([], [], _, Segs) -> Segs;
build_squash_node(Left, Right, Mid, Segs) ->
    ?NODE(Left, Right, Mid, Segs).

%% @doc Returns the indices of the intervals in the tree that contains Point.
-spec intersect(integer(), tree()) -> [non_neg_integer()].
intersect(Point, nil) when is_integer(Point) -> [];
intersect(Point, #segment_tree{lo=Lo, hi=Hi, root=Root})
  when is_integer(Point) ->
    case Lo =< Point andalso Point =< Hi of
	false -> [];
	true -> intersect_1(Point, Root, [])
    end.

intersect_1(Point, ?NODE(Left, Right, Mid, Segs), Acc0) ->
    Child = if Point =< Mid -> Left; true -> Right end,
    intersect_1(Point, Child, Segs ++ Acc0);
intersect_1(_, LeafSegs, Acc) -> LeafSegs ++ Acc.