javascript

Is it possible to add a new element to top of Map?


Is it possible to add a new element to top of Map without sorting?

Like as unshift()?

Because Map is it like a object, I each time sort object after adding operation.

I have done this like that:

let map = new Map();

map.set(1, { id: 1 });
map.set(2, { id: 2 });
map.set(3, { id: 3 });
map.set(4, { id: 4 });

let mapCopy = new Map();


for (let i = map.size; i > 0 ; --i) {
    mapCopy.set(i, map.get(i));
}

console.log(map);
console.log(mapCopy);

Solution

  • A Map preserves the insertion order of keys by specification:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    print(map);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }

    If you want to move something to the end, it has to be the last inserted item. You can "move" by deleting and re-adding:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    moveToEnd(map, 3);
    
    print(map);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    //modifies in-place
    function moveToEnd(map, key) {
      //save value
      const value = map.get(key);
      //delete
      map.delete(key);
      //re-insert
      map.set(key, value);
    }

    Note that you must delete, otherwise it doesn't work:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    moveToEnd(map, 3);
    
    print(map);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    //modifies in-place
    function moveToEnd(map, key) {
      //save value
      const value = map.get(key);
      
      //don't delete
      
      //re-insert
      map.set(key, value);
    }

    Another option is to re-create the entire Map and enforce the new insertion order:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    const newMap1 = moveToEnd1(map, 3);
    const newMap2 = moveToEnd2(map, 3);
    
    print(newMap1);
    console.log("------")
    print(newMap2);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    function moveToEnd1(map, key) {
      //create a new Map from the old one
      const result = new Map(map);
      
      //save value
      const value = map.get(key);
      //delete
      result.delete(key);
      //re-insert
      result.set(key, value);
      
      return result;
    }
    
    function moveToEnd2(map, key) {
      return [...map.entries()]                        //get all entries
        .filter(([k,]) => k !== key)                   //remove all but the key that would be last
        .reduce(                                       //create a new Map inserting all other entries
          (acc, [key, value]) => acc.set(key, value), 
          new Map()
        )
        .set(key, map.get(key));                       //add the last entry
    }

    However, a move to front means that you have to shift everything else to the front. Again, you can do the same as before - either move the entries in-place by deleting and re-adding the keys:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    moveToFront(map, 1);
    
    print(map);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    function moveToFront(map, key) {
      //materialise all entries, because the iterator provides a live view
      const entries = Array.from(map.entries());
      
      //move to the back
      for (let [k, v] of entries) {
        //skip moving the target key
        if (k === key) continue; 
        
        //delete
        map.delete(k);
        //re-insert
        map.set(k, v);
      }
    }

    Or re-create the map with the new order. Note that if you insert the desired key in front, you can just use set again with it and it won't move, as long as there is no .delete() called for it, which makes the re-creation easier:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    const newMap = moveToFront(map, 1);
    print(newMap);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    function moveToFront(map, key) {
      return new Map([
        [key, map.get(key)], //key-value to be in front
        ...map               //merge with the entire map
      ]);
    }

    As for actually adding instead of moving - the same applies, you can either shift everything in the map or just re-create it. Assuming you want to treat a repeat inset as "move to front", then you can do something like this:

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    addToFrontInPlace(map, 4, "delta");
    print(map);
    console.log("-------");
    addToFrontInPlace(map, 1, "new alpha");
    print(map);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    function addToFrontInPlace(map, key, value) {
      //add new value
      map.set(key, value);
      
      //materialise all entries, because the iterator provides a live view
      const entries = Array.from(map.entries());
      
      //move to the back
      for (let [k, v] of entries) {
        //skip moving the target key
        if (k === key) continue; 
        
        //delete
        map.delete(k);
        //re-insert
        map.set(k, v);
      }
    
    }

    const map = new Map();
    
    map.set(3, "gamma");
    map.set(1, "alpha");
    map.set(2, "beta");
    
    const newMap1 = addToFrontNewMap(map, 4, "delta");
    print(newMap1);
    console.log("-------");
    const newMap2 = addToFrontNewMap(newMap1, 1, "new alpha");
    print(newMap2);
    
    function print(map) {
      for ([key, value] of map.entries()) 
        console.log(`${key} - ${value}`)
    }
    
    function addToFrontNewMap(map, key, value = 7) {
      //exclude the entry from the old map, so it doesn't overwrite the value
      const entries = [...map.entries()]
          .filter(([k,]) => k !== key);
          
      return new Map([
        [key, value], //key-value to be in front
        ...entries    //merge with the entire map
      ]);
    }