views:

171

answers:

3

I created a class Foo that has the method toArray() that returns an Array<Int>.

Now, I have a HashMap mapping Strings to HashMaps, which map Objects to Foo. That is:

HashMap<String,HashMap<Object,Foo>>

And I want to create a new object of type:

HashMap<String,HashMap<Object,Array<Int>>>

That is obtained by calling the function toArray() for every element Foo in the original HashMAp.

To do so I normally would do something like:

    public static HashMap<String,HashMap<Object,Array<Int>>> changeMap(Map mpOld) {
        Object key2;
        String key1;
        Iterator it2;
        HashMap<String,HashMap<Object,Array<Int>>> mpNew= 
            new HashMap<String,HashMap<Object,Array<Int>>>()
        Iterator it1 = mpOld.keySet().iterator();
        while (it1.hasNext()) {
            key1=it1.next();
            it2= mpOld.get(key1).keySet().iterator();
            mpNew.put(key1,new HashMap<Object,Array<Int>>())
            while (it2.hasNext()) {
                key2=it2.next();
                mpNew.get(key1).put(key2,mpOld.get(key1).get(key2).toArray());
                //TODO clear entry mpOld.get(key1).get(key2)
            }
            //TODO clear entry mpOld.get(key1)
        }
        return mpNew;
    }

A similar code works just fine, but the Size of the HashMap is too big to hold two of them in memory. As you can see I added two points where I want to clear some entries. The problem is, if I do, I get either a concurrency error, or the iterator loop just terminates.

I wonder if there is a better way to iterate through the Maps and copy the information.

Also, I'm working in a Scala project but here I have to use Java types for some compatibility issues. Although Java.util.HashMap is not an iterator, maybe Scala has some hidden functinality to deal with this?

Thanks,

+4  A: 

I did not have time to check it, but I guess something like this should work on scala Maps (assuming you use scala 2.8 which is finally here):

mpO.mapValues(_.mapValues(_.toArray))

It would take your outer map, and "replace" all inner maps with a new one, where the values are the Int arrays. Keys, and the general "structure" of the maps remain the same. According to scaladoc "The resulting map wraps the original map without copying any elements.", so it won't be a real replacement.

If you also do an

import scala.collection.JavaConversions._

then the java maps can be used the same way as scala maps: JavaConversions contain a bunch of implicit methods that can convert between scala and java collections.

BTW using a Map < String,HashMap < Object,Array < Int>>> might not be really convenient at the end, if I were you I would consider introducing some classes that would hide the complexity of this construct.

Edit reflecting to your comment

import scala.collection.JavaConversions._
import java.util.Collections._

object MapValues {
  def main(args: Array[String]) {
    val jMap = singletonMap("a",singletonMap("b", 1))
    println(jMap)
    println(jMap.mapValues(_.mapValues(_+1)))
  }
}

prints:

{a={b=1}}
Map(a -> Map(b -> 2))

Showing that the implicits are applied both to the outer and inner map quite nicely. This is the purpose of the JavaConversions object: even if you have a java collection you can use it as a similar scala class (with boosted features).
You don't have to do anything else, just import JavaConversions._

Sandor Murakozi
Thanks, but although I'm using Scala for the project, the HashMaps are Java HashMaps, so you can't call mapVAlues on them. Is tehre a way to solve this using JavaConversions?
Skuge
Were you able to solve your problem? Did my edit help?
Sandor Murakozi
+3  A: 

The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations.

Why don't you call the remove () method on the iterator or set.remove (iterator.next ()) where iterator.next () returns the key, set is the keyset and iterator its iterator.

PS: also try to refactor your data structure, maybe some intermediate classes which handle the data retrieval? A map in a map with arrays as values doesn't say anything and is difficult to keep track of.

+5  A: 

Iterators offer remove(..) methods that safely removes the previously accessed item. Iterate over the Key/Value entries of the map, converting them and adding them to the new map, and removing the old ones as you go.

/**
 * Transfers and converts all entries from <code>map1</code> to 
 * <code>map2</code>.  Specifically, the {@link Foo} objects of the 
 * inner maps will be converted to integer arrays via {@link Foo#toArray}.
 * 
 * @param map1 Map to be emptied.
 * @param map2 Receptacle for the converted entries.
 */
private static void transfer(Map<String, Map<Object, Foo>> map1
        , Map<String, Map<Object, int[]>> map2) {

    final Iterator<Entry<String, Map<Object, Foo>>> mapIt
        = map1.entrySet().iterator();
    while (mapIt.hasNext()) {
        final Entry<String, Map<Object, Foo>> mapEntry = mapIt.next();
        mapIt.remove();
        final Map<Object, int[]> submap = new HashMap<Object,int[]>();
        map2.put(mapEntry.getKey(), submap);
        final Iterator<Entry<Object,Foo>> fooIt 
            = mapEntry.getValue().entrySet().iterator();
        while (fooIt.hasNext()) {
            final Entry<Object,Foo> fooEntry = fooIt.next();
            fooIt.remove();
            submap.put(fooEntry.getKey(), fooEntry.getValue().toArray());
        }
    }
}
Gunslinger47