How to resolve the algorithm Associative array/Creation step by step in the ATS programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm Associative array/Creation step by step in the ATS programming language

Table of Contents

Problem Statement

The goal is to create an associative array (also known as a dictionary, map, or hash).

Related tasks:

Let's start with the solution:

Step by Step solution about How to resolve the algorithm Associative array/Creation step by step in the ATS programming language

Source code in the ats programming language

(*------------------------------------------------------------------*)

#define ATS_DYNLOADFLAG 0

#include "share/atspre_staload.hats"

(*------------------------------------------------------------------*)
(* Interface                                                        *)

(* You can put the interface in a .sats file. You will have to remove
   the word "extern". *)

typedef alist_t (key_t  : t@ype+,
                 data_t : t@ype+,
                 size   : int) =
  list (@(key_t, data_t), size)
typedef alist_t (key_t  : t@ype+,
                 data_t : t@ype+) =
  [size : int]
  alist_t (key_t, data_t, size)

extern prfun
lemma_alist_t_param :
  {size  : int} {key_t : t@ype} {data_t : t@ype}
  alist_t (key_t, data_t, size) - [0 <= size] void

extern fun {key_t : t@ype}     (* Implement key equality with this. *)
alist_t$key_eq : (key_t, key_t) -<> bool

(* alist_t_nil: create an empty association list. *)
extern fun
alist_t_nil :
  {key_t : t@ype} {data_t : t@ype}
  () -<> alist_t (key_t, data_t, 0)

(* alist_t_set: add an association, deleting old associations with an
   equal key. *)
extern fun {key_t  : t@ype}
           {data_t : t@ype}
alist_t_set {size : int}
            (alst : alist_t (key_t, data_t, size),
             key  : key_t,
             data : data_t) :<>
    [sz : int | 1 <= sz]
    alist_t (key_t, data_t, sz)

(* alist_t_get: find an association and return its data, if
   present. *)
extern fun {key_t  : t@ype}
           {data_t : t@ype}
alist_t_get {size : int}
            (alst : alist_t (key_t, data_t, size),
             key  : key_t) :<>
    Option data_t

(* alist_t_delete: delete all associations with key. *)
extern fun {key_t  : t@ype}
           {data_t : t@ype}
alist_t_delete {size : int}
               (alst : alist_t (key_t, data_t, size),
                key  : key_t ) :<>
    [sz : int | 0 <= sz]
    alist_t (key_t, data_t, sz)

(* alist_t_make_pairs_generator: make a closure that returns
   the association pairs, one by one. This is a form of iterator.
   Analogous generators can be made for the keys or data values
   alone. *)
extern fun {key_t  : t@ype}
           {data_t : t@ype}
alist_t_make_pairs_generator
          {size : int}
          (alst : alist_t (key_t, data_t, size)) :
    () - Option @(key_t, data_t)

(*------------------------------------------------------------------*)
(* Implementation                                                   *)

#define NIL list_nil ()
#define :: list_cons

primplement
lemma_alist_t_param alst =
  lemma_list_param alst

implement
alist_t_nil () =
  NIL

implement {key_t} {data_t}
alist_t_set (alst, key, data) =
  @(key, data) :: alist_t_delete (alst, key)

implement {key_t} {data_t}
alist_t_get (alst, key) =
  let
    fun
    loop {n : nat}
         ..                  (* <-- proof of termination *)
         (lst : alist_t (key_t, data_t, n)) :<>
        Option data_t =
      case+ lst of
      | NIL => None ()
      | head :: tail =>
        if alist_t$key_eq (key, head.0) then
          Some (head.1)
        else
          loop tail

    prval _ = lemma_alist_t_param alst
  in
    loop alst
  end

implement {key_t} {data_t}
alist_t_delete (alst, key) =
  let
    fun
    delete {n : nat}
           ..                (* <-- proof of termination *)
           (lst : alist_t (key_t, data_t, n)) :<>
        [m : nat] alist_t (key_t, data_t, m) =
      (* This implementation is *not* tail recursive, but has the
         minor advantage of preserving the order of entries without
         doing a lot of work. *)
      case+ lst of
      | NIL => lst
      | head :: tail =>
        if alist_t$key_eq (key, head.0) then
          delete tail
        else
          head :: delete tail

    prval _ = lemma_alist_t_param alst
  in
    delete alst
  end

implement {key_t} {data_t}
alist_t_make_pairs_generator alst =
  let
    typedef alist_t = [sz : int] alist_t (key_t, data_t, sz)

    val alst_ref = ref alst

    (* Cast the ref to a pointer so it can be enclosed in the
       closure. *)
    val alst_ptr = $UNSAFE.castvwtp0{ptr} alst_ref
  in
    lam () =>
      let
        val alst_ref = $UNSAFE.castvwtp0{ref alist_t} alst_ptr
      in
        case+ !alst_ref of
        | NIL => None ()
        | head :: tail =>
          begin
            !alst_ref := tail;
            (* For a keys generator, change the following line to
               "Some (head.0)"; for a data values generator, change
               it to "Some (head.1)". *)
            Some head
          end
      end
  end

(*------------------------------------------------------------------*)
(* Demonstration program                                            *)

implement
alist_t$key_eq (s, t) =
  s = t

typedef s2i_alist_t = alist_t (string, int)

fn
s2i_alist_t_set (map  : s2i_alist_t,
                 key  : string,
                 data : int) :<> s2i_alist_t =
  alist_t_set (map, key, data)

fn
s2i_alist_t_set_ref (map  : &s2i_alist_t >> _,
                     key  : string,
                     data : int) : void =
  (* Update a reference to a persistent alist. *)
  map := s2i_alist_t_set (map, key, data)

fn
s2i_alist_t_get (map  : s2i_alist_t,
                 key  : string) :<> Option int =
  alist_t_get (map, key)

extern fun {}        (* {} = a template without template parameters *)
s2i_alist_t_get_dflt$dflt :<> () -> int
fn {}                (* {} = a template without template parameters *)
s2i_alist_t_get_dflt (map  : s2i_alist_t,
                      key  : string) : int =
  case+ s2i_alist_t_get (map, key) of
  | Some x => x
  | None () => s2i_alist_t_get_dflt$dflt<> ()

overload [] with s2i_alist_t_set_ref
overload [] with s2i_alist_t_get_dflt

implement
main0 () =
  let
    implement s2i_alist_t_get_dflt$dflt<> () = 0
    var map = alist_t_nil ()
    var gen : () - @(string, int)
    var pair : Option @(string, int)
  in
    map["one"] := 1;
    map["two"] := 2;
    map["three"] := 3;
    println! ("map[\"one\"] = ", map["one"]);
    println! ("map[\"two\"] = ", map["two"]);
    println! ("map[\"three\"] = ", map["three"]);
    println! ("map[\"four\"] = ", map["four"]);
    gen := alist_t_make_pairs_generator map;
    for (pair := gen (); option_is_some pair; pair := gen ())
      println! (pair)
  end

(*------------------------------------------------------------------*)

(*------------------------------------------------------------------*)

#define ATS_DYNLOADFLAG 0

#include "share/atspre_staload.hats"

(*------------------------------------------------------------------*)
(* String hashing using XXH3_64bits from the xxHash suite.          *)

#define ATS_EXTERN_PREFIX "hashmaps_postiats_"

%{^ /* Embedded C code. */

#include 

ATSinline() atstype_uint64
hashmaps_postiats_mem_hash (atstype_ptr data, atstype_size len)
{
  return (atstype_uint64) XXH3_64bits (data, len);
}

%}

extern fn mem_hash : (ptr, size_t) -<> uint64 = "mac#%"

fn
string_hash (s : string) :<> uint64 =
  let
    val len = string_length s
  in
    mem_hash ($UNSAFE.cast{ptr} s, len)
  end

(*------------------------------------------------------------------*)
(* A trimmed down version of the AVL trees from the AVL Tree task.  *)

datatype bal_t =
| bal_minus1
| bal_zero
| bal_plus1

datatype avl_t (key_t  : t@ype+,
                data_t : t@ype+,
                size   : int) =
| avl_t_nil (key_t, data_t, 0)
| {size_L, size_R : nat}
  avl_t_cons (key_t, data_t, size_L + size_R + 1) of
    (key_t, data_t, bal_t,
     avl_t (key_t, data_t, size_L),
     avl_t (key_t, data_t, size_R))
typedef avl_t (key_t  : t@ype+,
               data_t : t@ype+) =
  [size : int] avl_t (key_t, data_t, size)

extern fun {key_t : t@ype}
avl_t$compare (u : key_t, v : key_t) :<> int

#define NIL avl_t_nil ()
#define CONS avl_t_cons
#define LNIL list_nil ()
#define :: list_cons
#define F false
#define T true

typedef fixbal_t = bool

prfn
lemma_avl_t_param {key_t : t@ype} {data_t : t@ype} {size : int}
                  (avl : avl_t (key_t, data_t, size)) :
    [0 <= size] void =
  case+ avl of NIL => () | CONS _ => ()

fn {}
minus_neg_bal (bal : bal_t) :<> bal_t =
  case+ bal of
  | bal_minus1 () => bal_plus1
  | _ => bal_zero ()

fn {}
minus_pos_bal (bal : bal_t) :<> bal_t =
  case+ bal of
  | bal_plus1 () => bal_minus1
  | _ => bal_zero ()

fn
avl_t_is_empty {key_t : t@ype} {data_t : t@ype} {size   : int}
               (avl : avl_t (key_t, data_t, size)) :<>
    [b : bool | b == (size == 0)] bool b =
  case+ avl of
  | NIL => T
  | CONS _ => F

fn
avl_t_isnot_empty {key_t : t@ype} {data_t : t@ype} {size   : int}
                  (avl : avl_t (key_t, data_t, size)) :<>
    [b : bool | b == (size <> 0)] bool b =
  ~avl_t_is_empty avl

fn {key_t : t@ype} {data_t : t@ype}
avl_t_search_ref {size  : int}
                 (avl   : avl_t (key_t, data_t, size),
                  key   : key_t,
                  data  : &data_t? >> opt (data_t, found),
                  found : &bool? >> bool found) :
    #[found : bool] void =
  let
    fun
    search (p     : avl_t (key_t, data_t),
            data  : &data_t? >> opt (data_t, found),
            found : &bool? >> bool found) :
        #[found : bool] void =
      case+ p of
      | NIL =>
        {
          prval _ = opt_none {data_t} data
          val _ = found := F
        }
      | CONS (k, d, _, left, right) =>
        begin
          case+ avl_t$compare (key, k) of
          | cmp when cmp < 0 => search (left, data, found)
          | cmp when cmp > 0 => search (right, data, found)
          | _ =>
            {
              val _ = data := d
              prval _ = opt_some {data_t} data
              val _ = found := T
            }
        end
  in
    $effmask_ntm search (avl, data, found)
  end

fn {key_t : t@ype} {data_t : t@ype}
avl_t_search_opt {size : int}
                 (avl  : avl_t (key_t, data_t, size),
                  key  : key_t) :<>
    Option (data_t) =
  let
    var data : data_t?
    var found : bool?
    val _ = $effmask_wrt avl_t_search_ref (avl, key, data, found)
  in
    if found then
      let
        prval _ = opt_unsome data
      in
        Some {data_t} data
      end
    else
      let
        prval _ = opt_unnone data
      in
        None {data_t} ()
      end
  end

fn {key_t : t@ype} {data_t : t@ype}
avl_t_insert_or_replace {size : int}
                        (avl  : avl_t (key_t, data_t, size),
                         key  : key_t,
                         data : data_t) :<>
    [sz : pos] (avl_t (key_t, data_t, sz), bool) =
  let
    fun
    search {size   : nat}
           (p      : avl_t (key_t, data_t, size),
            fixbal : fixbal_t,
            found  : bool) :
        [sz : pos]
        (avl_t (key_t, data_t, sz), fixbal_t, bool) =
      case+ p of
      | NIL => (CONS (key, data, bal_zero, NIL, NIL), T, F)
      | CONS (k, d, bal, left, right) =>
        case+ avl_t$compare (key, k) of
        | cmp when cmp < 0 =>
          let
            val (p1, fixbal, found) = search (left, fixbal, found)
          in
            case+ (fixbal, bal) of
            | (F, _) => (CONS (k, d, bal, p1, right), F, found)
            | (T, bal_plus1 ()) =>
              (CONS (k, d, bal_zero (), p1, right), F, found)
            | (T, bal_zero ()) =>
              (CONS (k, d, bal_minus1 (), p1, right), fixbal, found)
            | (T, bal_minus1 ()) =>
              let
                val+ CONS (k1, d1, bal1, left1, right1) = p1
              in
                case+ bal1 of
                | bal_minus1 () =>
                  let
                    val q = CONS (k, d, bal_zero (), right1, right)
                    val q1 = CONS (k1, d1, bal_zero (), left1, q)
                  in
                    (q1, F, found)
                  end
                | _ =>
                  let
                    val p2 = right1
                    val- CONS (k2, d2, bal2, left2, right2) = p2
                    val q = CONS (k, d, minus_neg_bal bal2,
                                  right2, right)
                    val q1 = CONS (k1, d1, minus_pos_bal bal2,
                                   left1, left2)
                    val q2 = CONS (k2, d2, bal_zero (), q1, q)
                  in
                    (q2, F, found)
                  end
              end
          end
        | cmp when cmp > 0 =>
          let
            val (p1, fixbal, found) = search (right, fixbal, found)
          in
            case+ (fixbal, bal) of
            | (F, _) => (CONS (k, d, bal, left, p1), F, found)
            | (T, bal_minus1 ()) =>
              (CONS (k, d, bal_zero (), left, p1), F, found)
            | (T, bal_zero ()) =>
              (CONS (k, d, bal_plus1 (), left, p1), fixbal, found)
            | (T, bal_plus1 ()) =>
              let
                val+ CONS (k1, d1, bal1, left1, right1) = p1
              in
                case+ bal1 of
                | bal_plus1 () =>
                  let
                    val q = CONS (k, d, bal_zero (), left, left1)
                    val q1 = CONS (k1, d1, bal_zero (), q, right1)
                  in
                    (q1, F, found)
                  end
                | _ =>
                  let
                    val p2 = left1
                    val- CONS (k2, d2, bal2, left2, right2) = p2
                    val q = CONS (k, d, minus_pos_bal bal2,
                                  left, left2)
                    val q1 = CONS (k1, d1, minus_neg_bal bal2,
                                   right2, right1)
                    val q2 = CONS (k2, d2, bal_zero (), q, q1)
                  in
                    (q2, F, found)
                  end
              end
          end
        | _ => (CONS (key, data, bal, left, right), F, T)
  in
    if avl_t_is_empty avl then
      (CONS (key, data, bal_zero, NIL, NIL), F)
    else
      let
        prval _ = lemma_avl_t_param avl
        val (avl, _, found) = $effmask_ntm search (avl, F, F)
      in
        (avl, found)
      end
  end

fn {key_t : t@ype} {data_t : t@ype}
avl_t_insert {size : int}
             (avl  : avl_t (key_t, data_t, size),
              key  : key_t,
              data : data_t) :<>
    [sz : pos] avl_t (key_t, data_t, sz) =
  (avl_t_insert_or_replace (avl, key, data)).0

fun {key_t : t@ype} {data_t : t@ype}
push_all_the_way_left (stack : List (avl_t (key_t, data_t)),
                       p     : avl_t (key_t, data_t)) :
    List0 (avl_t (key_t, data_t)) =
  let
    prval _ = lemma_list_param stack
  in
    case+ p of
    | NIL => stack
    | CONS (_, _, _, left, _) =>
      push_all_the_way_left (p :: stack, left)
  end

fun {key_t : t@ype} {data_t : t@ype}
update_generator_stack (stack     : List (avl_t (key_t, data_t)),
                        right     : avl_t (key_t, data_t)) :
    List0 (avl_t (key_t, data_t)) =
  let
    prval _ = lemma_list_param stack
  in
    if avl_t_is_empty right then
      stack
    else
      push_all_the_way_left (stack, right)
  end

fn {key_t : t@ype} {data_t : t@ype}
avl_t_make_data_generator {size : int}
                          (avl  : avl_t (key_t, data_t, size)) :
    () - Option data_t =
  let
    typedef avl_t = avl_t (key_t, data_t)

    val stack = push_all_the_way_left (LNIL, avl)
    val stack_ref = ref stack

    (* Cast stack_ref to its (otherwise untyped) pointer, so it can be
       enclosed within ‘generate’. *)
    val p_stack_ref = $UNSAFE.castvwtp0{ptr} stack_ref

    fun
    generate () : Option data_t =
      let
        (* Restore the type information for stack_ref. *)
        val stack_ref =
          $UNSAFE.castvwtp0{ref (List avl_t)} p_stack_ref

        var stack : List0 avl_t = !stack_ref
        var retval : Option data_t
      in
        begin
          case+ stack of
          | LNIL => retval := None ()
          | p :: tail =>
            let
              val- CONS (_, d, _, left, right) = p
            in
              retval := Some d;
              stack :=
                update_generator_stack (tail, right)
            end
        end;
        !stack_ref := stack;
        retval
      end
  in
    generate
  end

(*------------------------------------------------------------------*)
(* Hashmaps implemented with AVL trees and association lists.       *)

(* The interface  - - - - - - - - - - - - - - - - - - - - - - - - - *)

typedef hashmap_t (key_t  : t@ype+,
                   data_t : t@ype+) =
  avl_t (uint64, List1 @(key_t, data_t))

(* For simplicity, let us support only 64-bit hashes. *)
extern fun {key_t : t@ype}  (* Implement a hash function with this. *)
hashmap_t$hashfunc : key_t -<> uint64

extern fun {key_t : t@ype}     (* Implement key equality with this. *)
hashmap_t$key_eq : (key_t, key_t) -<> bool

extern fun
hashmap_t_nil :
  {key_t : t@ype} {data_t : t@ype}
  () -<> hashmap_t (key_t, data_t)

extern fun {key_t  : t@ype}
           {data_t : t@ype}
hashmap_t_set (map  : hashmap_t (key_t, data_t),
               key  : key_t,
               data : data_t) :<>
    hashmap_t (key_t, data_t)

extern fun {key_t  : t@ype}
           {data_t : t@ype}
hashmap_t_get (map : hashmap_t (key_t, data_t),
               key : key_t) :<>
    Option data_t

(*
  Notes:
    * Generators for hashmap_t produce their output in unspecified
      order.
    * Generators for keys and data values can be made by analogy to
      the following generator for pairs, or can be written in terms
      of the generator for pairs. (The former approach seems better;
      it might copy less data.)
*)
extern fun {key_t  : t@ype}
           {data_t : t@ype}
hashmap_t_make_pairs_generator (map : hashmap_t (key_t, data_t)) :
    () - Option @(key_t, data_t)

(* The implementation - - - - - - - - - - - - - - - - - - - - - - - *)

implement
avl_t$compare (u, v) =
  if u < v then
    ~1
  else if v < u then
    1
  else
    0

implement
hashmap_t_nil () =
  avl_t_nil ()

fun {key_t  : t@ype}
    {data_t : t@ype}
remove_association {n : nat} ..
                   (lst : list (@(key_t, data_t), n),
                    key : key_t) :<>
    List0 @(key_t, data_t) =
  (* This implementation uses linear stack space, and so presumes the
     list is not extremely long. It preserves the order of the list,
     although doing so is not necessary for persistence. (You might
     wish to think about that, taking into account that the
     order of traversal through a hashmap usually is considered
     "unspecified".) *)
  case+ lst of
  | list_nil () => lst
  | list_cons (head, tail) =>
    if hashmap_t$key_eq (key, head.0) then
      tail                      (* Assume there is only one match. *)
    else
      list_cons (head, remove_association (tail, key))

fun {key_t  : t@ype}
    {data_t : t@ype}
find_association {n : nat} ..
                 (lst : list (@(key_t, data_t), n),
                  key : key_t) :<>
    List0 @(key_t, data_t) =
  (* This implementation is tail recursive. It will not build up the
     stack. *)
  case+ lst of
  | list_nil () => lst
  | list_cons (head, tail) =>
    if hashmap_t$key_eq (key, head.0) then
      lst
    else
      find_association (tail, key)

implement {key_t} {data_t}
hashmap_t_set (map, key, data) =
  let
    typedef lst_t = List1 @(key_t, data_t) (* Association list. *)
    val hash = hashmap_t$hashfunc key
    val lst_opt = avl_t_search_opt (map, hash)
    val lst =
      begin
        case+ lst_opt of
        | Some lst =>
          (* There is already an association list for this hash value.
             Remove any association already in it. *)
          remove_association (lst, key)
        | None () =>
          (* Start a new association list. *)
          list_nil ()
      end : List0 @(key_t, data_t)
    val lst = list_cons (@(key, data), lst)
  in
    avl_t_insert (map, hash, lst)
  end

implement {key_t} {data_t}
hashmap_t_get (map, key) =
  let
    typedef lst_t = List1 @(key_t, data_t) (* Association list. *)
    val hash = hashmap_t$hashfunc key
    val lst_opt = avl_t_search_opt (map, hash)
  in
    case+ lst_opt of
    | None () => None{data_t} ()
    | Some lst =>
      begin
        case+ find_association (lst, key) of
        | list_nil () => None{data_t} ()
        | list_cons (@(_, data), _) => Some{data_t} data
      end
  end

implement {key_t} {data_t}
hashmap_t_make_pairs_generator (map) =
  let
    typedef pair_t = @(key_t, data_t)
    typedef lst_t = List1 pair_t
    typedef lst_t_0 = List0 pair_t

    val avl_gen = avl_t_make_data_generator (map)

    val current_alist_ref : ref lst_t_0 = ref (list_nil ())
    val current_alist_ptr =
      $UNSAFE.castvwtp0{ptr} current_alist_ref
  in
    lam () =>
      let
        val current_alist_ref =
          $UNSAFE.castvwtp0{ref lst_t_0} current_alist_ptr
      in
        case+ !current_alist_ref of
        | list_nil () =>
          begin
            case+ avl_gen () of
            | None () => None ()
            | Some lst =>
              begin
                case+ lst of
                | list_cons (head, tail) =>
                  begin
                    !current_alist_ref := tail;
                    Some head
                  end
              end
          end
        | list_cons (head, tail) =>
          begin
            !current_alist_ref := tail;
            Some head
          end
      end
  end

(*------------------------------------------------------------------*)

implement
hashmap_t$hashfunc (s) =
  string_hash s

implement
hashmap_t$key_eq (s, t) =
  s = t

typedef s2i_hashmap_t = hashmap_t (string, int)

fn
s2i_hashmap_t_set (map  : s2i_hashmap_t,
                   key  : string,
                   data : int) :<> s2i_hashmap_t =
  hashmap_t_set (map, key, data)

fn
s2i_hashmap_t_set_ref (map  : &s2i_hashmap_t >> _,
                       key  : string,
                       data : int) : void =
  (* Update a reference to a persistent hashmap. *)
  map := s2i_hashmap_t_set (map, key, data)

fn
s2i_hashmap_t_get (map  : s2i_hashmap_t,
                   key  : string) :<> Option int =
  hashmap_t_get (map, key)

extern fun {}        (* {} = a template without template parameters *)
s2i_hashmap_t_get_dflt$dflt :<> () -> int
fn {}                (* {} = a template without template parameters *)
s2i_hashmap_t_get_dflt (map  : s2i_hashmap_t,
                        key  : string) : int =
  case+ s2i_hashmap_t_get (map, key) of
  | Some x => x
  | None () => s2i_hashmap_t_get_dflt$dflt<> ()

overload [] with s2i_hashmap_t_set_ref
overload [] with s2i_hashmap_t_get_dflt

implement
main0 () =
  let
    implement s2i_hashmap_t_get_dflt$dflt<> () = 0
    var map = hashmap_t_nil ()
    var gen : () - @(string, int)
    var pair : Option @(string, int)
  in
    map["one"] := 1;
    map["two"] := 2;
    map["three"] := 3;
    println! ("map[\"one\"] = ", map["one"]);
    println! ("map[\"two\"] = ", map["two"]);
    println! ("map[\"three\"] = ", map["three"]);
    println! ("map[\"four\"] = ", map["four"]);
    gen := hashmap_t_make_pairs_generator map;
    for (pair := gen (); option_is_some pair; pair := gen ())
      println! (pair)
  end

(*------------------------------------------------------------------*)

  

You may also check:How to resolve the algorithm FizzBuzz step by step in the XLISP programming language
You may also check:How to resolve the algorithm Knuth's algorithm S step by step in the Phix programming language
You may also check:How to resolve the algorithm Simulate input/Mouse step by step in the Raku programming language
You may also check:How to resolve the algorithm Draw a sphere step by step in the FutureBasic programming language
You may also check:How to resolve the algorithm Write float arrays to a text file step by step in the Java programming language