3

say I have a map in Scala - key: String, value: String.

Is there an easy way to get the arrays of keys and values in corresponding order? E.g. the i-th element of the key array should be the key related to the i-th value of the values array.

What I've tried is iterating through the map and getting them one by one:

valuesMap.foreach{keyVal => keys.append(keyVal.1); values.append(keyVal.2); // the idea, not the actual code

Is there a simplier way?

The question could probably be asked: is there any way to guarantee a specific order of map.keys/map.values?

For example, when generating an SQL query it may be convenient to have arrays of column names and values separately, but with the same order.

hauron
  • 4,550
  • 5
  • 35
  • 52
  • 3
    You do know that Map, as well as Set, has no well defined order, right? – om-nom-nom Aug 29 '14 at 12:04
  • om-nom-nom, I do, that's why I'm asking this question. To put it differently: getting all the keys as a set (or even as an array) does not guarantee any order (possibly unless I explicitly ask for one). Hence I doubt there's any guarantee that i-th key will map to i-th value – hauron Aug 29 '14 at 12:22
  • 1
    then [how about using a proper Map implementation](http://stackoverflow.com/q/3835743/298389)? I don't think your problem is solveable, in case you tied somehow to a generic Map – om-nom-nom Aug 29 '14 at 12:23
  • om-nom-nom, seems like LinkedHashMap solves the problem, thank you once again – hauron Aug 29 '14 at 12:27
  • Why don't you just look up the values array given the keys array? `val keys = valuesMap.keySet.toSeq; val values = keys.map{key=>valuesMap(key)}` Sure, lots of lookups, but there are hidden lookups in other ways of retrieving the values anyway. – The Archetypal Paul Aug 29 '14 at 14:44
  • It sounds like you should ask for the pairs (guaranteed correspondence), and then unzip the resulting collection of pairs. `Map("a" -> 5, "b" -> 3).unzip` will give you a pair of `Iterables` whose keys and values are guaranteed to correspond, even though the overall order is unspecified. I'd question your need to do this, though, since keeping context in one place is generally desirable. If you possibly can, just operate on the iterable of pairs for maximum safety. – Mysterious Dan Aug 29 '14 at 16:03

2 Answers2

7

You can use toSeq to get a sequence of pairs that has a fixed order, and then you can sort by the key (or any other function of the pairs). For example:

scala> val pairs = Map(3 -> 'c, 1 -> 'a, 4 -> 'd, 2 -> 'b).toSeq.sortBy(_._1)
res0: Seq[(Int, Symbol)] = ArrayBuffer((1,'a), (2,'b), (3,'c), (4,'d))

Now you can use unzip to turn this sequence of pairs into a pair of sequences:

scala> val (keys, vals) = pairs.unzip
keys: Seq[Int] = ArrayBuffer(1, 2, 3, 4)
vals: Seq[Symbol] = ArrayBuffer('a, 'b, 'c, 'd)

The keys and values will line up, and you've only performed the sorting operation once.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • It's not guaranteed to be a fixed order until after the sortBy, I think? Or, as a weaker statement, the order returned by toSeq is irrelevant because you immediately sort it. – The Archetypal Paul Aug 29 '14 at 14:40
1

If all you want is that key- and value-lists line up, then it's really easy:

val (keys, vals) = yourMap.toSeq.unzip

All the information of the original map is preserved the ordering. You can get it back like so

val originalMap = (keys zip values).toMap
assert(originalMap == yourMap)
Robert Jack Will
  • 10,333
  • 1
  • 21
  • 29