%% The worlds fastest number analyser ?? %% Author: Claes Wikstrom (klacke@erix.eicsson.se) %% Date: Aug 15, 1995 -module(num_anal). -vsn('$Id: num_anal.erl,v 1.7 2002/12/02 18:38:14 scott Exp $ '). -export([new/1, new/2]). -export([print_all/2, check/3, delete/3, insert/4]). -export([delete_list/2, longest_match/3, exact_match/3]). -export([print_match/3]). -record(number_analyser, {num = [], res = null}). %% API %% Create a new analysis table. List would be e.g. black or white new(Type) -> new(Type, [node()]). new(Type, Nodes) -> mnesia:create_table(Type, [{attributes, record_info(fields, number_analyser)}, {record_name, number_analyser}, {disc_copies, Nodes}]). check(Type, Ref, Numberlist) -> case try_lookup(Type, Ref, Numberlist) of {found, Result} -> Result; _ -> no_match end. longest_match(Type, Ref, Numberlist) -> case long_lookup(Type, Ref, Numberlist) of {found, Result} -> Result; _ -> no_match end. exact_match(Type, Ref, Numberlist) -> F = fun() -> case mnesia:read(Type, {Ref, to_hex_integer(process_numberlist(Numberlist))}, read) of [{_,_,get_more_digits}|_] -> mnesia:abort(number_has_no_result); [{_,_,Result}|_] -> Result; _ -> mnesia:abort(no_entry) end end, case mnesia:transaction(F) of {aborted, Reason} -> no_match; {atomic, Res} -> Res end. %% This is lazy. I just tag deleted entries with get_more_digits %% so the tree still works if this was a sublist of another %% entry. If this was the end of a tree then all the data is left hanging %% around. messy, but pretty safe *** fixme *** delete(T, Ref, NumList) -> F = fun() -> case mnesia:read(T, {Ref, to_hex_integer(process_numberlist(NumList))}, read) of [{_,_,get_more_digits}|_] -> mnesia:abort(trying_to_delete_sublist); [] -> ok; _ -> mnesia:write(T, #number_analyser{num = {Ref, to_hex_integer(process_numberlist(NumList))}, res = get_more_digits}, write) end end, case mnesia:transaction(F) of {aborted, Reason} -> {error, Reason}; {atomic, _} -> ok end. delete_list(T, Ref) -> WildPat = mnesia:table_info(T, wild_pattern), Pat = WildPat#number_analyser{num = {Ref, '_'}}, F = fun() -> Entries = mnesia:match_object(T, Pat, read), lists:foreach(fun(X) -> mnesia:delete_object(T, X, write) end, Entries) end, case mnesia:transaction(F) of {aborted, Reason} -> {error, Reason}; {atomic, _} -> ok end. insert(T, Ref, NumberList, Value) -> F = fun() -> insert2(T, [], process_numberlist(NumberList), Value, Ref) end, case mnesia:transaction(F) of {aborted, Reason} -> {error, Reason}; {atomic, _} -> ok end. insert2(T, Prev, [Digit|Tail], Value, Ref) -> Num = to_hex_integer(L = lists:append(Prev, [Digit]), 0), case Tail of [] -> mnesia:write(T, #number_analyser{num = {Ref, Num}, res = Value}, write); _ -> case mnesia:read(T, {Ref, Num}, read) of [#number_analyser{res = get_more_digits}] -> insert2(T, L, Tail, Value, Ref); % Already in the database [#number_analyser{res = _Some_value}] -> insert2(T, L, Tail, Value, Ref); % Already in the database, don't want to overwrite result [] -> mnesia:write(T, #number_analyser{num = {Ref, Num}, res = get_more_digits}, write), insert2(T, L, Tail, Value, Ref) end end. to_hex_integer(HexIntList) -> to_hex_integer(HexIntList, 0). to_hex_integer([H|T], Ack) -> to_hex_integer(T, Ack*16 + H); to_hex_integer([], Ack) -> Ack. process_numberlist([H|T]) -> [hex(H) | process_numberlist(T)]; process_numberlist([]) -> []. %% We make special case on zero so that we catch the %% case with phone numbers beginning with 0 hex(D) -> if 1 =< D, D =< 9 -> D; D == 0 -> 16#a; D == 11 -> 16#b; D == 12 -> 16#c end. print_match(T, Ref, RecPat) -> WildPat = mnesia:table_info(T, wild_pattern), Pat = WildPat#number_analyser{num = {Ref, '_'}, res = RecPat}, F = fun() -> mnesia:match_object(T, Pat, read) end, case mnesia:transaction(F) of {aborted, Reason} -> {error, Reason}; {atomic, List} -> format_numbers(List) end. format_numbers(List) -> format_numbers(List, []). format_numbers([#number_analyser{num = {R, Number}, res = Value}|List], NewList) -> format_numbers(List, [NewList,{shlib:digitlist_to_string(hex_to_phonenumber(Number, [])), Value}]); format_numbers([], NewList) -> lists:flatten([NewList]). %% %% Print all phone numbers in table T with ref Ref %% print_all(T, Ref) -> print_all(T, mnesia:dirty_first(T), [], Ref). print_all(T, '$end_of_table', Result, Ref) -> Result; print_all(T, Key, Result, Ref) -> print_all2(T, mnesia:dirty_read(T, Key), Result, Ref). print_all2(T, [#number_analyser{num = Key, res = get_more_digits}], Result, Ref) -> print_all(T, mnesia:dirty_next(T, Key), Result, Ref); print_all2(T, [#number_analyser{num = {Ref, Telno}, res = Value}], Result, Ref) -> print_all(T, mnesia:dirty_next(T, {Ref, Telno}), [{shlib:digitlist_to_string(hex_to_phonenumber(Telno, [])), Value}|Result], Ref); print_all2(T, [#number_analyser{num = {R, Telno}, res = Value}], Result, Ref) -> print_all(T, mnesia:dirty_next(T, {R, Telno}), Result, Ref). hex_to_phonenumber(0, Ack) -> Ack; hex_to_phonenumber(I, Ack) -> hex_to_phonenumber(I div 16, [un_hex(I rem 16) | Ack]). un_hex(16#a) -> 0; un_hex(16#b) -> 11; un_hex(16#c) -> 12; un_hex(I) -> I. %% Try to look up a phone number try_lookup(Table, Ref, []) -> invalid; try_lookup(Table, Ref, [H|T]) -> case lookup(H, Table, Ref) of {get_more_digits, Sofar} -> try_lookup(Sofar, T, Table, Ref); {found, Result, _} -> {found, Result}; Other -> Other end. try_lookup(Sofar, [H|T], Table, Ref) -> case lookup(Sofar, H, Table, Ref) of {get_more_digits, Sofar2} -> try_lookup(Sofar2, T, Table, Ref); {found, Result, _} -> {found, Result}; Other -> Other end; try_lookup(Sofar, [], Table, Ref) -> not_enough_digits. %% Try to look up a phone number looking for the longest match rather %% than the first match. long_lookup(Table, Ref, []) -> invalid; long_lookup(Table, Ref, [H|T]) -> case lookup(H, Table, Ref) of {get_more_digits, Sofar} -> long_lookup(Sofar, T, Table, Ref, no_entry); {found, Value, Sofar} -> long_lookup(Sofar, T, Table, Ref, {found, Value}); Other -> Other end. long_lookup(Sofar, [H|T], Table, Ref, Result) -> case lookup(Sofar, H, Table, Ref) of {get_more_digits, Sofar2} -> long_lookup(Sofar2, T, Table, Ref, Result); {found, Value, Sofar2} -> long_lookup(Sofar2, T, Table, Ref, {found, Value}); no_entry -> Result end; long_lookup(Sofar, [], Table, Ref, Result) -> Result. lookup(FirstDigit, T, Ref) -> lookup(0, FirstDigit, T, Ref). lookup(Prev, Dig, T, Ref) -> D = (Prev * 16) + hex(Dig), case mnesia:dirty_read({T, {Ref, D}}) of [#number_analyser{num = {Ref, D}, res = get_more_digits}] -> {get_more_digits, D}; [#number_analyser{num = {Ref, D}, res = Value}] -> {found, Value, D}; [] -> no_entry end.