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.
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.
Share Improve this question edited Mar 20, 2018 at 7:56 GrayedFox 2,55329 silver badges46 bronze badges asked Aug 17, 2017 at 8:59 Vitali ZaidmanVitali Zaidman 9481 gold badge10 silver badges24 bronze badges 9- 3 What's wrong with that? – Barmar Commented Aug 17, 2017 at 9:00
- edited the question: i'd want to keep the same object. I also wonder if this is the most efficient way. – Vitali Zaidman Commented Aug 17, 2017 at 9:02
- Possible duplicate of How to get subarray from array? – Rajesh Commented Aug 17, 2017 at 9:03
- No they don't @NinaScholz... – malifa Commented Aug 17, 2017 at 9:06
- 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 Commented Aug 17, 2017 at 9:24
6 Answers
Reset to default 11Get 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.
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
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}
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
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
I needed the N latest elements from the map, maybe someone will find it useful:
function keepNNewestItems(map, n) {
const elementsToRemoveCount = map.size - n;
const keys = map.keys();
for (let i = 0; i < elementsToRemoveCount; i++) {
map.delete(keys.next().value);
}
}
function removeNOldestItems(map, n) {
const elementsToRemoveCount = Math.min(n, map.size);
const keys = map.keys();
for (let i = 0; i < elementsToRemoveCount; i++) {
map.delete(keys.next().value);
}
}
/* Demo */
const createMap = (length) => new Map(Array.from({ length }, (_, i) => [ Math.random(), i ]));
const printMap = (map) => `[ ${Array.from(map.values()).join(', ')} ]`;
const map1 = createMap(11);
console.log(printMap(map1));
keepNNewestItems(map1, 6);
console.log(`keep 6 newest: ${printMap(map1)}`);
console.log('-'.repeat(36));
const map2 = createMap(11);
console.log(printMap(map2));
removeNOldestItems(map2, 6);
console.log(`remove 6 oldest: ${printMap(map2)}`);