1:- module(utils_ssardina,
    2    [
    3        downcase_term/2,
    4        % MATH TOOLS
    5        round/3,
    6        % DICT, JSON, YAML
    7        serialize_term/2,
    8        flatten_dict/2
    9    ]).
 downcase_term(+Term:term, -LowerTerm:term) is det
Recursively downcase all atoms in Term, including those nested within lists and dicts. Useful for normalizing identifiers to lowercase atoms in Prolog.
   17downcase_term(Term, LowerTerm) :- atom(Term), !,
   18    downcase_atom(Term, LowerTerm).
   19downcase_term(Term, Term) :- atomic(Term), !.
   20downcase_term(Term, LowerTerm) :-
   21	is_list(Term),
   22	!,
   23	maplist(downcase_term, Term, LowerTerm).
   24downcase_term(Term, LowerTerm) :-
   25    compound(Term),
   26	Term =.. [Functor|Args],
   27	maplist(downcase_term, Args, LowerArgs),
   28	LowerTerm =.. [Functor|LowerArgs].
   29downcase_term(Term, _) :-
   30    format(atom(Msg), 'Cannot downcase term: ~w', [Term]),
   31    throw(error(Msg)).
   32
   33
   34
   35/* ****************************************************************************
   36 MATH UTILITIES
   37 *************************************************************************** */
 round(+X:float, -R:float, +N:int) is det
R is X rounded to N decimal places.
   42round(X, R, N) :-
   43    E is 10^N,
   44	R is round(X * E) / E.
   45
   46
   47
   48
   49/* ****************************************************************************
   50 DICTIONARIES, JSON, YAML
   51 *************************************************************************** */
 serialize_term(+Term, -JSON) is det
serialize a Prolog term to a JSON-compatible format (numbers, strings, lists, dicts)

?- serialize_term([complex(111,aaa), other(f(1),c), complex(2222,bbb)], T). T = [_{complex:[111, aaa]}, _{other:[_{f:1}, c]}, _{complex:[2222, bbb]}].

   60serialize_term(Term, JSON) :-
   61	var(Term),
   62	JSON = null.
   63serialize_term(Term, Term) :-
   64	(number(Term) ; string(Term) ; atom(Term)), !.
   65serialize_term(Key-Value, JSON) :- !,
   66	dict_create(JSON, _, [Key-Value]).
   67serialize_term(Term, JSON) :-
   68	is_list(Term), !,
   69	maplist(serialize_term, Term, JSON).
   70serialize_term(Term, JSON) :-
   71	compound(Term),
   72	Term =.. [Functor|[Args]],
   73	!,
   74	serialize_term(Args, JSONArgs),
   75	dict_create(JSON, _, [Functor - JSONArgs]).
   76serialize_term(Term, JSON) :-
   77	compound(Term),
   78	!,
   79	Term =.. [Functor|Args],
   80	maplist(serialize_term, Args, JSONArgs),
   81    dict_create(JSON, _, [Functor-JSONArgs]).
   82% Fallback (should rarely be needed)
   83serialize_term(Term, JSON) :-
   84	term_string(Term, JSON).
Flatten a list of dicts with single key-value pairs into a single dict with lists of values for each key

?- serialize_term([complex(111,aaa), other(f(1),c), complex(2222,bbb)], T), flatten_dict(T, X). T = [_{complex:[111, aaa]}, _{other:[_A{f:1}, c]}, _{complex:[2222, bbb]}], X = _{complex:[[111, aaa], [2222, bbb]], other:[[_A{f:1}, c]]}.

   94flatten_dict(L, Dict) :-
   95	flatten_dict(L, _{}, Dict).
   96flatten_dict([], Dict, Dict).
   97flatten_dict([Dict|Rest], AccDict, FinalDict) :-
   98	get_dict(Key, Dict, Value),
   99	 \+ get_dict(Key, AccDict, _),
  100	!,
  101	put_dict(Key, AccDict, [Value], NewAcc),
  102	flatten_dict(Rest, NewAcc, FinalDict).
  103flatten_dict([Dict|Rest], AccDict, FinalDict) :-
  104	get_dict(Key, Dict, Value),
  105	get_dict(Key, AccDict, OldValue),
  106	!,
  107	append(OldValue, [Value], NewValue),
  108	put_dict(Key, AccDict, NewValue, NewAcc),
  109	flatten_dict(Rest, NewAcc, FinalDict)