Is ordering of keys and values preserved in Elixir when you operate on a map?

Let's check the Erlang's code.

Elixir use Erlang's map as base implementation,it defines a MAP_SMALL_MAP_LIMIT macro.

erts/emulator/beam/erl_map.h-#ifdef DEBUG
erts/emulator/beam/erl_map.h:#define MAP_SMALL_MAP_LIMIT    (3)
erts/emulator/beam/erl_map.h-#else
erts/emulator/beam/erl_map.h:#define MAP_SMALL_MAP_LIMIT    (32)
erts/emulator/beam/erl_map.h-#endif

When you create a map, it will use the function below.

Eterm erts_map_from_ks_and_vs(ErtsHeapFactory *factory, Eterm *ks0, Eterm *vs0, Uint n)
{
    if (n < MAP_SMALL_MAP_LIMIT) {
        Eterm *ks, *vs, *hp;
    flatmap_t *mp;
    Eterm keys;

        hp    = erts_produce_heap(factory, 3 + 1 + (2 * n), 0);
    keys  = make_tuple(hp);
    *hp++ = make_arityval(n);
    ks    = hp;
    hp   += n;
    mp    = (flatmap_t*)hp;
    hp   += MAP_HEADER_FLATMAP_SZ;
    vs    = hp;

        mp->thing_word = MAP_HEADER_FLATMAP;
    mp->size = n;
    mp->keys = keys;

        sys_memcpy(ks, ks0, n * sizeof(Eterm));
        sys_memcpy(vs, vs0, n * sizeof(Eterm));

        erts_validate_and_sort_flatmap(mp);

        return make_flatmap(mp);
    } else {
        return erts_hashmap_from_ks_and_vs(factory, ks0, vs0, n);
    }
    return THE_NON_VALUE;
}

Notice that when the size of the map is less than MAP_SMALL_MAP_LIMIT, it will call erts_validate_and_sort_flatmap, which use bubble sort to sort your map.

Otherwise, it will use hashmap.


The implementation of Maps in Elixir and Erlang has some confusing properties. For small values of entries it is a sorted key list, and thus appears to have the properties you see in simple experiments.

Above a certain number of entries (32 I think), the implementation switches to Hash Array Mapped Trie and all the properties you see disappear. You can not depend on the order of either the keys or the values of a map in the general case. See

https://en.wikipedia.org/wiki/Hash_array_mapped_trie

for an explaination of the underlying structure of Map.

 iex(7)> 1..33 |> Enum.reduce(%{}, fn(x, acc) -> Map.put(acc,x,x) end )
%{11 => 11, 26 => 26, 15 => 15, 20 => 20, 17 => 17, 25 => 25, 13 => 13, 8 => 8,   7 => 7, 1 => 1, 32 => 32, 3 => 3, 6 => 6, 2 => 2, 33 => 33, 10 => 10, 9 => 9,   19 => 19, 14 => 14, 5 => 5, 18 => 18, 31 => 31, 22 => 22, 29 => 29, 21 => 21,   27 => 27, 24 => 24, 30 => 30, 23 => 23, 28 => 28, 16 => 16, 4 => 4, 12 => 12} 

iex(8)> Map.keys(v(7)) [11, 26, 15, 20, 17, 25, 13, 8, 7, 1, 32, 3, 6, 2, 33, 10, 9, 19, 14, 5, 18, 31,  22, 29, 21, 27, 24, 30, 23, 28, 16, 4, 12] 

iex(9)> Map.values(v(7)) [11, 26, 15, 20, 17, 25, 13, 8, 7, 1, 32, 3, 6, 2, 33, 10, 9, 19, 14, 5, 18, 31,  22, 29, 21, 27, 24, 30, 23, 28, 16, 4, 12]

From the Elixir website:

Compared to keyword lists, we can already see two differences:

  • Maps allow any value as a key.
  • Maps’ keys do not follow any ordering.

While the Elixir website clearly states that Maps do not follow any ordering, they do follow a specific order after they've created (but do not preserve their order of creation). It seems that the Maps are organized alphabetically according to their keys (but I have nothing to back this up except a few experiments in IEx):

map = %{c: 3, a: 1, b: 2}

map                       # => %{a: 1, b: 2, c: 3}
Map.keys(map)             # => [:a, :b, :c]
Map.values(map)           # => [1, 2, 3]

Since you asked about preserving the original order, The answer is NO.


Better Option: Keyword Lists

A better alternative would be to use Keyword lists (which are a linked-list of two element tuples underneath). Because of this, the order of their creation is maintained:

kw = [c: 3, a: 1, b: 2]

kw                       # => [c: 3, a: 1, b: 2]
Keyword.keys(kw)         # => [:c, :a, :b]
Keyword.values(kw)       # => [3, 1, 2]

Tags:

Elixir