Skip to content
LogoLogo

Iterable set

Maps can't be enumerated. To list members, keep a list and an index map in sync. This is the pattern behind the generic SetEnumerable module.

iterable-set.lithe
(* Maps cannot be iterated. When you need to list members, keep an enumerable set:
   a list for order plus an index map for membership, updated together. Removal
   swaps the last member into the gap, so it costs the same for any member. *)
 
import "std/evm";
 
error OutOfRange of uint;
 
storage is record {
  members : list(address);
  count : uint;
  (* index + 1, so that 0 means absent *)
  positions : map(address, uint);
}
 
external view function contains(const who : address) : bool is
  storage.positions[who] > 0
 
external function join() is {
  if storage.positions[Evm.sender()] = 0 then {
    storage.members[storage.count] := Evm.sender();
    storage.count := storage.count + 1;
    storage.positions[Evm.sender()] := storage.count;
  }
}
 
external function leave() is {
  const position : uint := storage.positions[Evm.sender()];
  if position > 0 then {
    const last : address := storage.members[storage.count - 1];
    storage.members[position - 1] := last;
    storage.positions[last] := position;
    storage.positions[Evm.sender()] := 0;
    storage.count := storage.count - 1;
  }
}
 
external view function length() : uint is
  storage.count
 
(* Clients page through members one index at a time. *)
external view function at(const i : uint) : address
  raises OutOfRange
is {
  assert(i < storage.count, OutOfRange(i));
} with storage.members[i]
  • join and leave cost the same no matter how many members there are.
  • Off-chain clients enumerate with length() and at(i).