2

Say I want to delete the first 100 entries of a Map without recreating the map, and also do this in the most efficient way.

Let's say you have a 500 item ES6 Map Object:

let map = new Map(... 500 items)

Currently I do it like this:

const newCache = new Map(
  Array.from(map).slice(100)
)

map.clear()

map = newCache

But this recreates the Map.

The other way is to run over the first 100 keys:

Array.from(map.keys())
     .slice(0, 100)
     .forEach(key => map.delete(key))

But it looks inefficient.

GrayedFox
  • 2,350
  • 26
  • 44
Vitali Zaidman
  • 899
  • 1
  • 9
  • 24
  • 2
    What's wrong with that? – Barmar Aug 17 '17 at 09:00
  • edited the question: i'd want to keep the same object. I also wonder if this is the most efficient way. – Vitali Zaidman Aug 17 '17 at 09:02
  • Possible duplicate of [How to get subarray from array?](https://stackoverflow.com/questions/7538519/how-to-get-subarray-from-array) – Rajesh Aug 17 '17 at 09:03
  • No they don't @NinaScholz... – malifa Aug 17 '17 at 09:06
  • its not an array its a 500 items ES6 Map – Vitali Zaidman Aug 17 '17 at 09:08
  • 1
    Sounds like an XY problem to me. You're trying to mix direct (Map) and sequential ("first N items") access in one data structure. What's your use case? – georg Aug 17 '17 at 09:24
  • a cache is full, so i want to delete the oldest 100 items from the cache but to keep the more recent once. – Vitali Zaidman Aug 17 '17 at 09:25
  • I really don't understand how this question gets 2 downvotes. It's a fair question to ask how to delete a set of elements in a map without recreating it. – malifa Aug 17 '17 at 09:25
  • It might have been down voted by a user without edit permissions who noticed the title didn't accurately reflect the nature of the query - I've updated the title now to reflect the requirements of the OP (just waiting on approval). – GrayedFox Mar 20 '18 at 06:39

5 Answers5

11

Get an array of the first 100 keys of the Map, then delete them.

var keys = Array.from(map.keys()).slice(0, 100);
keys.forEach(k => map.delete(k));

Or you can use a loop so you don't need to create an array to slice.

var i = 0;
for (var k of map.keys()) {
    if (i++ > 100) {
        break;
    }
    map.delete(k);
}

I created a jsperf test with your two methods and this loop. The loop is bar far the most efficient, 5 times faster than slicing the keys.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • it looks inefficient. – Vitali Zaidman Aug 17 '17 at 09:14
  • why would he create an `Array` first anyway` A Map comes with all methods to loop through and delete key/values safely.. – malifa Aug 17 '17 at 09:14
  • 1
    @lexith I wasn't sure if it's safe to delete while using those iterators. Also, can you slice an iterator to get just the first 100? – Barmar Aug 17 '17 at 09:16
  • @Barmar i would just use `Map.forEach` and keep a counter inside and delete the first 100 keys. It's safe, iterations won't be affected by delete, the iteration will continue correctly. Or did i get your question wrong? – malifa Aug 17 '17 at 09:19
  • why run 100 commands if you can do it the first way I suggested. I'd just want to do it the first way but also keep the reference – Vitali Zaidman Aug 17 '17 at 09:20
  • @VitalikZaidman why do you think it's inefficient? You have to use a loop in any way if you want to keep the original Map. – malifa Aug 17 '17 at 09:20
  • I thought of that, but didn't like that the `forEach` would keep going after it gets past 100 -- there's no way to break out of it. – Barmar Aug 17 '17 at 09:20
  • 1
    @VitalikZaidman If you don't want to recreate the map, I don't see any way to do it without doing 100 deletes. `Map` doesn't have a `splice()` operation that can delete multiple elements at once. – Barmar Aug 17 '17 at 09:22
  • 1
    @Barmar urgs correct. But with `for ... of` you can break it. So don't use forEach, but that one. :) ... edit: okay now im unsure, have to test. but it should be interruptable. – malifa Aug 17 '17 at 09:23
  • @lexith OK, added a for-of loop – Barmar Aug 17 '17 at 09:26
  • For of loops, as per [the docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of), can be inerupted with a `break` statement. – GrayedFox Mar 20 '18 at 06:45
2

Based on your comment, you're buildng some kind of a LRU cache. You might consider a better data structure that just a Map. For example,

cache = { deque: Array, index: Map, offset: Int }

When you put element in the cache, you append it to the deque and store its position + offset in the index:

class Cache...

    put(obj) {
        this.deque.push(obj)
        pos = this.deque.length - 1
        this.index.set(key(obj),  pos + this.offset)
    }

When getting element, check it its index of positive

get(obj) {
    pos = this.index.get(key(obj)) - this.offset
    if pos >= 0
       return this.deque[pos]
    // cache miss....

Now, cleaning up the cache won't involve any loops

 clear(count) {
     this.deque.splice(0, count)
     this.offset += count
 }

On a general note, if you want something to be re-created, but need a persistent pointer to it in the same time, you can just wrap the private object into a public one and proxy (some of) private's methods:

class Cache
   this._map = new Map() // feel free to recreate this when needed


    get(x) { return this._map.get(x) }
    set(x, y) { return this._map.set(x, y) }

myCache = new Cache() // this can be saved somewhere else 
georg
  • 211,518
  • 52
  • 313
  • 390
  • accepted the answer because of the wrapping thingie. its a good idea to have something to wrap the map to replace it inside it's own structure. ill go for it. – Vitali Zaidman Aug 17 '17 at 11:04
  • 1
    While this answer has value it should not, IMO, be the accepted answer because it doesn't answer the OP's original question (which is how to delete N keys of an ES6 Map object without recreating the object). While it does apply to the OP's use case, SO users landing here most likely have a query relating to the question at hand and not one of the clarifying comments. I would encourage the OP to consider this and maybe accept @Barmar's [answer](https://stackoverflow.com/a/45730975/3249501), since that will draw the attention of other users to the answer that most likely applies to them. – GrayedFox Mar 20 '18 at 06:52
0

If your Map is very big, you may not want to pull all of the keys back just to find the first few. In that case, entries may be a better option.

let n = 2;
let map = new Map();

map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
map.set('d', 4);
console.log(map) // Map(4) {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}

let iterator = map.entries();
for (let i = 0; i < n; i++) {
  let entry = iterator.next();
  let key = entry.value[0];
  map.delete(key);
}

console.log(map) // Map(2) {'c' => 3, 'd' => 4}
Rich Hildebrand
  • 1,607
  • 17
  • 15
0

Example -

let mm = new Map();
mm.set('10', 'Rina');
mm.set('11', 'Sita');
mm.set('12', 'Gita');

Gives you the first element of Map -

let keys = mm.keys().next() // Gives you 10 -> Rina

mm.delete(keys.value) // Delete from the beginning
Atique Ahmed
  • 308
  • 4
  • 16
0

An one-by-one approach without creating an array of all the keys

const removeFirstItems = (n) => {
    if (map.size >= n) {
        const it = map.keys();
        for (let i = 0; i < n; i++) {
            map.delete(it.next().value);
        }
    }
}

Example:

// array of 105 items 
// using random keys to see that the order of the keys does not matter
const map = new Map();
for (let i = 0; i < 105; i++) {
    map.set(Math.random(), i);
}

removeFirstItems(100);

// the last added 5 items should remain - order of keys does not matter
console.log(Array.from(map.values()).join(',')); // 100,101,102,103,104
elcsiga
  • 170
  • 1
  • 8