3

I have a map (for example, of character to integer). I insert values into this map one by one. For example, here are four insertions:

1: A -> 1
2: B -> 2
3: C -> 3
4: D -> 1

I would like to sort the map keys based on their associated values. So, at each insertion, I would have the sorted output:

1: A(1)
2: B(2), A(1)
3: C(3), B(2), A(1)
4: C(3), B(2), A(1), D(1)

Furthermore, I would like to be able to overwrite existing mappings to keep the keys (characters) unique. So a fifth insertion:

5: A -> 27

Would cause the sorted output to be:

5: A(27), C(3), B(2), D(1)

One way I can do this is using a multimap. The key of the multimap would be the integer, and the values would be the characters. Each insertion into the multimap would first need to check if the character is already present in the multimap, and remove that mapping before performing the insertion. The multimap keeps the keys ordered, so that takes care of the sorting.

Is there a faster way to do this? Is there a different, more efficient container that I should be using?

EDIT

Here's the C++ STL multimap I'm using. It's handy because it keeps its elements internally ordered.

Here's a related question. I'm trying to avoid doing what's suggested in the accepted solution: creating another map.

Community
  • 1
  • 1
mpenkov
  • 21,621
  • 10
  • 84
  • 126

4 Answers4

2

Generally a map is a shuffled data structure (at least for the associated values). So, sorting elements of a map is meaningless.

Therefore you should use something like list or array to keep elements sorted.

I think the best solution for your issue is the simplest way: Store the elements in a list and sort it. Or you can use heaps.

UPDATE:

Maps are a kind of associative container that stores elements formed by the combination of a key value and a mapped value.

In a map, the key value is generally used to uniquely identify the element, while the mapped value is some sort of value associated to this key. Types of key and mapped value may differ. For example, a typical example of a map is a telephone guide where the name is the key and the telephone number is the mapped value.

Internally, the elements in the map are sorted from lower to higher key value following a specific strict weak ordering criterion set on construction.

As associative containers, they are especially designed to be efficient accessing its elements by their key (unlike sequence containers, which are more efficient accessing elements by their relative or absolute position) [1].

[1] http://www.cplusplus.com/reference/stl/map/

masoud
  • 55,379
  • 16
  • 141
  • 208
  • that is some confusing explanation. A map is generally _ordered_ not shuffled. Sorting elements is not meaningles, but redundant. – sehe Nov 20 '11 at 11:50
  • @Masoud M. You may be right in a general case. However, the C++ STL multimap is internally sorted. I've edited my question to make it more obvious. The list solution is far too inefficient, since I would have to sort after each insertion, and wouldn't have an effective way of keeping the keys unique. – mpenkov Nov 20 '11 at 11:51
  • When we are implementing a map `yes`, we should store keys ordered. but it is transparent from user of a map. – masoud Nov 20 '11 at 11:52
  • 1
    @misha: added some specific hints to keeping a list ordered while inserting – sehe Nov 20 '11 at 11:53
  • @misha, with each insertion to a `list`, you don't need to resort it, you iterate over the list and insert it in the right place, so it's in fact `O(n)` – Shahbaz Nov 20 '11 at 11:54
  • @Shahbaz: what you are suggesting is http://en.wikipedia.org/wiki/Insertion_sort. It's only O(n) in the best case (quadratic in worst/average case). – mpenkov Nov 20 '11 at 11:59
  • @misha, your question title is: `Insertion sort of a map` – Shahbaz Nov 20 '11 at 12:23
  • @Shabaz: touche. I'm beginning to think I'm chasing my tail with this problem. Thank you for your contribution! – mpenkov Nov 20 '11 at 12:39
2

What I would have done would be something like this. Imagine your map maps x to y. I would create a struct (class, whatever):

struct element
{
    map_this_t x;
    to_this_t  y;
    bool operator < (element &rhs)
    {
        return y < rhs.y;       // sorted based on y
    }
};

Then create a set<element> and insert into that. If you iterate over all the data in the set, you well get the data in the order you want. When inserting, first search and see if an element with x value as the one you want exists. If so, just update the y, otherwise insert a new element.

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
  • How would you perform the "search and see" step? The set is sorted based on `y`, and you're looking for some specific value of `x`. I can use a map to take care of this, but it ends up being slower than my original multi-map approach. Also, it isn't possible to simply update the `y` as it `const` -- changing it would invalidate the position of the element in the set. A deletion and re-insert seems necessary, which costs additional time. – mpenkov Nov 20 '11 at 12:31
  • 1
    You are right. Updating it means remove and reinsert which takes `O(log n)` each. To search, you can either search linearly (`O(n)`) or, have a map on the side that maps `x` to `y` and with that, you can identify the `y` corresponding to `x` and remove (x, y) from the set and insert (x, newy) (each of these steps is `O(log n)`) – Shahbaz Nov 20 '11 at 12:55
1

A map is sorted by it's keys, by definition.

The values cannot be used in the ordering predicate for a map, because the values are non-const! Changing them would invalidate the container invariants (ordering).

If you want the entries ordered by 'value', it is implicitely a key, and you likely require a

std::set<std::pair<key, value> >

Update Here is a working demo of that: https://ideone.com/SgbEN

instead. To be able to re-order by a different predicate in place, you'd need a list, vector or any other sequential container.

Edit

The list solution is far too inefficient, since I would have to sort after each insertion, and wouldn't have an effective way of keeping the keys unique.

The solution here is to use the

  • std::make_heap
  • std::push_heap
  • and std::sort_heap

algorithms. They work well with lists (as well as vectors for cheap value_types - or using c++0x move semantics)

If you cannot afford the sort_heap step before using the list as ordered: use

insert_point = std::lower_bound(lst.begin(), lst.end(), insertee);
st.insert(insertion_point, insertee);

to insert directly in ordered position. You can expect push_heap to be faster for many insertions, and lowerbound/insert to be faster for many accesses

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    @misha: looking for the insertion point (with `lower_bound`) is O(log n) because binary search is employed. So keeping it ordered it _not_ quadratic in any case. Note the std::set example which keeps the items in a Btree (in most implementations), just like std::map – sehe Nov 20 '11 at 12:03
0

just use a map <char,int> and your keys will remain unique an pairs of <char,int> will store sorted in it.

to sort the elements based on their associated values store pair<int,char> in a set too.

a-z
  • 1,634
  • 4
  • 16
  • 35