JavaScript - get range of entries Map entries - javascript

Since a JavaScript Map remembers the original insertion order I was wondering if there was a (clean) way to get a range of entries? Specifically, I want to get all entries after a certain entry. I thought I could just use the map or forEach function but you cannot use map with Map's and forEach does not pass the index (Map docs)

You don't really have any option but to loop through the entries, either explicitly or using loops in function calls.
Specifically, I want to get all entries after a certain entry.
I'm going to assume you mean a certain value (rather than key), but the below is easily adjusted for keys instead.
Unless theMap has hundreds of thousands or even millions of entries, you can use fairly concise code by using spread to get an array of the entries, indexOf to find the target value, and slice to get only the ones after it:
const values = [...theMap.values()];
const index = values.indexOf(theDesiredStartingValue);
const valuesAfter = index === -1 ? [] : values.slice(index + 1);
If you're concern that that's two-three loops through the data (but again, that's really unlikely to matter), you could use a single loop:
const valuesAfter = [];
let seen = false;
for (const value of theMap.values()) {
if (seen) {
valuesAfter.push(value);
} else if (value === theDesiredStartingValue) {
seen = true;
}
}
If you meant by key, I'm thinking you probably want a Map as a result:
const entries = [...theMap.entries()];
const index = entries.findIndex(([key]) => key === theDesiredStartingKey);
const entriesAfter = new Map(index === -1 ? [] : entries.slice(index + 1));
If you're concern that that's two-three loops through the data (but again, that's really unlikely to matter), you could use a single loop:
const entriesAfter = new Map();
let seen = false;
for (const [key, value] of theMap.entries()) {
if (seen) {
entriesAfter.set(key, value);
} else if (key === theDesiredStartingKey) {
seen = true;
}
}

I would be tempted to make this some sort of iterator function as below
function* entriesAfter(map, entry) {
let found = false
for(let e of map.values()){
if(found)
yield e;
else if(e == entry)
found = true
}
}
var input = new Map([['foo', 1], ['bar', 2], ['baz', 3], ['bing', 4]]);
for(let item of entriesAfter(input,2))
console.log(item)

Related

How to check if an element is present in all of the subarrays passed into the function? JS

function intersection(...bigArr)
{
let results = [];
let compiledArr = [];
for(let i = 0; i < bigArr.length; i++) {//bigArr.length is the number of subarrays
compiledArr.push(...bigArr[i]);//takes each subarray, deconstructs it and the elements are pushed into compiledArr.
}
const frequencyObj = {};
let endBound = bigArr[0].length;
let k = 1
for(let i = 0; i < compiledArr.length; i++)
{
//if the element exists, increase its frequency by 1. If it doesn't, create it and initialize it to 1. After, check if its frequency value === bigArr.length. If so, push to results
let currentElement = compiledArr[i];
if(i === endBound)//the program has reached the next subarray
{
endBound += bigArr[k].length;
k++;
turnTrue(frequencyObj) //turn the boolean value for each property to true. This is because we are analyzing a different subarray.
}
if(!frequencyObj[compiledArr[i]])//if the element DNE in the object, then add it
frequencyObj[compiledArr[i]] = [1, false];
else if( frequencyObj[currentElement] && frequencyObj[currentElement][1] === true)//if the element already exists, we need to make sure that we only increment its frequency iff we are in a different subarray within compiledArr
{
frequencyObj[currentElement][0] += 1;
frequencyObj[currentElement][1] = false;
//check if the frequency of that element === bigArr.length, it means it appears in all subarrays.
if(frequencyObj[currentElement][0] === bigArr.length)
results.push(currentElement);
}
}
return results;
}
function turnTrue(obj)
{
for(let key in obj)
obj[key][1] = true;
}
let result = intersection([1,2,1], [4,1,3,1,4], [3, 1, 2,6]);
console.log(result);
The program above has the the purpose of outputting an array with elements that are present in all of the subarrays passed into the intersection function.
This program accounts for duplicates within one subarray. The frequency is only marked when an element appears for the first time in a subarray. For example, given the three test subarrays [1,2,1], [4,1,3,1,4], [3, 1, 2,6], any element can only have a max frequency of the number of subarrays(in this case 3). An element is only marked once per subarray.
This is the only solution I could think of and I know that there exists a more simpler solution. Can anyone rate this code and indicate what could be a better solution?
It sounds like you're asking for a basic Set intersection, which can be done using the standard JavaScript Set object. Please see Set for more info.
You could however be asking for a count of how many times each element appears, but your question isn't explicit about this (and can only be either 0 if the set is empty, or the .length of your data array)
data = [ [1,2,1], [4,1,3,1,4], [3, 1, 2,6] ]
// intersection is taken verbatim from MDN docs linked
function intersection(setA, setB) {
const _intersection = new Set();
for (const elem of setB) {
if (setA.has(elem)) {
_intersection.add(elem);
}
}
return _intersection;
}
let set = data.reduce( (a, e) => {
// test includes the possibility that the leading elements are empty
return (a && a.size)? intersection(a, new Set(e)) : new Set(e)
}, null)
console.log([...set])

JavaScript - Filter array with mutation

I want to filter a array by keeping the same array without creating a new one.
with Array.filter() :
getFiltersConfig() {
return this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
}
what is the best way to get the same result by filtering by value without returning a new array ?
For completeness, I thought it might make sense to show a mutated array variant.
Below is a snippet with a simple function mutationFilter, this will filter the array directly, notice in this function the loop goes in reverse, this is a technique for deleting items with a mutated array.
Also a couple of tests to show how Array.filter creates a new array, and mutationFilter does not.
Although in most cases creating a new array with Array.filter is normally what you want. One advantage of using a mutated array, is that you can pass the array by reference, without you would need to wrap the array inside another object. Another advantage of course is memory, if your array was huge, inline filtering would take less memory.
let arr = ['a','b','a'];
let ref = arr; //keep reference of original arr
function mutationFilter(arr, cb) {
for (let l = arr.length - 1; l >= 0; l -= 1) {
if (!cb(arr[l])) arr.splice(l, 1);
}
}
const cond = x => x !== 'a';
const filtered = arr.filter(cond);
mutationFilter(arr, cond);
console.log(`ref === array -> ${ref === arr}`);
console.log(arr);
console.log(`ref === filtered -> ${ref === filtered}`);
console.log(filtered);
I want to filter a array by keeping the same array without creating a new one.
what is the best way to get the same result by filtering by value without returning a new array ?
I have an answer for the second criterion, but violates the first. I suspect that you may want to "not create a new one" specifically because you only want to preserve the reference to the array, not because you don't want to create a new array, necessarily (e.g. for memory concerns).
What you could do is create a temp array of what you want
var temp = this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
Then set the length of the original array to 0 and push.apply() the values "in-place"
this.config.length = 0; //clears the array
this.config.push.apply(this.config, temp); //adds what you want to the array of the same reference
You could define you custom method like so:
if(!Array.prototype.filterThis){
Array.prototype.filterThis = function (callBack){
if(typeof callBack !== 'function')
throw new TypeError('Argument must of type <function>');
let t = [...this];
this.length = 0;
for(let e of t) if(callBack(e)) this.push(e);
return this;
}
}
let a = [1,2,3,4,5,5,1,5];
a.filterThis(x=>x!=5);
console.log(a);
Warning: Be very cautious in altering built in prototypes. I would even say unless your making a polyfill don't touch. The errors it can cause can be very subtle and very hard to debug.
Not sure why would you want to do mutation but if you really want to do it, maybe assign it back to itself?
let arr = ['a','b','a'];
arr = arr.filter(x => x !== 'a');
console.log(arr)

Using map reduce etc, how would you find the first item matching a certain criteria in a nested array, and stop once found?

How would you find the first item matching a certain criteria in a nested array, and stop once found?
In a 1D array, this is what the Array.find function is for, but how would you do it for a 2D array, and, even neater, for n-dimension array?
Also, I'm trying to come up with a neat solution using es6 and array functions such as find, map, reduce etc, rather than using more traditional loops and variables to maintain state (see one such old-school solution below).
The data may look something like this
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
]
I'm hoping I can do something similar to array.find (and its predicate / testing function), but I need to go deeper and find eg the first item with val=5. For the data above, I'd expect to get the item with name 'nnn' (not 'ooo'), and have the process end once the first item is found. Similar to Array.find, I want to avoid processing the rest of the data once a matching item is found.
One boring old way to do it would be something like this, with a loop, but that's... boring, and not as neat as the lovely array functions :)
let found
// loop through all data entries in the outer array
for (const d of data) {
// attempt to find a matching item in the inner array.
// using array.find means we stop at the first match, yay!
const theItem = d.arr.find(item => {
return myPredicate(item)
})
// we also need to break out of the loop. ugh!
if (theItem) {
found = theItem
break
}
}
// return what we found (may be undefined)
return found
Now, I realise that I can do something with find() and some(), say, similar to the answer here ES6 - Finding data in nested arrays, but the problem is that using find on the outer array means that we get back the first item of the outer data array, whereas I want an item from the inner arr array.
const outer = data.find(d => {
return d.arr.some(item => {
return myPredicate(item)
})
})
I would then have to process outer AGAIN to find the item in outer.arr, something like
outer.arr.find(item => myPredicate(item))
This doesn't sit well with me, as the call to some(...) has already gone through and found the matching inner item!
I thought this would be straight forward, and maybe it is, but for one reason or another I got stuck on this little challenge.
I've also looked at the nice traverse library (https://www.npmjs.com/package/traverse), but again that seems to be more about traversing through a whole tree rather than stopping and returning once a particular node is found.
Anyone up for a challenge? ;)
The easiest (though slightly ugly) solution would be to assign the matching item to an outer variable when found:
let foundNested;
data.some(subarr => (
subarr.some((item) => {
if (myPredicate(item)) {
foundNested = item;
return true;
}
});
});
You might use .reduce to avoid assigning to an outer variable:
const myPredicate = ({ val }) => val === 5;
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
const found = data.reduce((a, { arr }) => (
a ||
arr.find(myPredicate)
), null);
console.log(found);
Problem is, the reduce won't short-circuit - it'll fully iterate over the outer array regardless. For true short-circuiting, I think I'd prefer using a for..of loop:
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
function findNested(outerArr, myPredicate) {
for (const { arr } of outerArr) {
for (const item of arr) {
if (myPredicate(item)) {
return item;
}
}
}
}
const myPredicate = ({ val }) => val === 5;
console.log(findNested(data, myPredicate));
You'll want to write your own find function that doesn't take a predicate but a result-producing callback:
function find(iterable, callback) {
for (const value of iterable) {
const result = callback(value);
if (result !== undefined)
return result;
}
}
With that, you can write
const data = [
{arr: [{val:6,name:'aaa'},{val:4,name:'bbb'},{val:8,name:'ccc'}]},
{arr: [{val:3,name:'mmm'},{val:5,name:'nnn'},{val:9,name:'ppp'},{val:5,name:'ooo'}]}
];
console.log(find(data, ({arr}) => find(arr, o => o.val == 5 ? o : undefined)));
Alternatively, if you want to get all results, flatMap is the perfect tool:
data.flatMap(({arr}) => arr.filter(({val}) => val == 5));
Sure, why not. I'm up for it. This can probably be improved upon. But this will work. Let's say you are trying to find an object with id of 5 in a multidimensional array.
const arr = [[[{id: 1}], [{id: 2}]], [[{id: 3}]], [[{id: 4}], [{id: 5}], [{id: 6}]]]
function findObject (obj) {
if (Array.isArray(obj)) {
const len = obj.length
for (let i = 0; i < len; i++) {
const found = findObject(obj[i])
if (found) {
return found
}
}
} else if (obj.id === 5) { // Put your search condition here.
return obj
}
}
const obj = findObject(arr)
console.log('obj: ', obj)
This seems to work, but, in my opinion, it's still not clean with that 'found' variable sitting outside the main block and being assigned from inside the nested find block. It's better though. Thoughts?
let found
data.find(d =>
d.arr.find(item => {
found = myPredicate(item) ? item : void 0
return found !== void 0
}) !== void 0
)
return found

Recursive sort in JS

I was asked in an interview to write a program/algo to sort an array of number using recursion.
Though I vaguely answered it, I tried and came up with following code:
You can use following JSFiddle link to play around.
function sort(arr) {
if (arr.length === 2) {
const v1 = arr[0];
const v2 = arr[1];
const isGreater = (
(isString(v1) && isString(v2) && v1.toString().toLocaleCompare(v2) > 0) ||
(isNumber(v1) && isNumber(v2) && v1 > v2)
);
return isGreater ? [ v2, v1 ] : [ v1, v2 ];
} else {
const last = arr.pop();
const ret = sort(arr);
const newLast = ret.peekLast();
if (newLast < last) {
return [ ...ret, last ];
} else {
return sort( [ last, ...ret ] );
}
}
}
function isString(value) { return typeof value === 'string'; }
function isNumber(value) { return Number.isFinite(value); }
Array.prototype.peekLast = function () { return this.slice().pop(); }
//console.log(sort([1,2,3,4,5]))
console.log(sort([5,4,3,2,1]))
The algo I implemented is:
Take the array and check if its length is greater than 2.
If yes,
Remove last element and store it in a variable.
Again call same function without last element till it has 2 items.
Accept array returned from recursive call and peek the last element.
If newLast value is greater than previousLast
Push previousLast as first element and again call itself with this array.
If not, push previousLast to array and return it.
Else,
For number and string check equality and return correct order.
For anything else, return same value
Question is, is there a better way to implement (algo wise)?
Note: I'm not expecting code improvements. Objective of this question is improvement in algo part or any general stuff I have missed.
I also know, current code does not support:
Sort order. It will sort ascending only.
May break for Date objects, and does not support Objects in general.
Thanks!
I think most interviewers would expect you to respond with quicksort or merge sort (or both) given that question. Of the two, quicksort, is easier to remember and recreate in a pinch because the merge step of merge sort is easy to mess up.
Quicksort is a really beautiful algorithm and is a natural fit for javascript's functional tools. It is worth really understanding if you'll be doing interviews:
const arr = [6, 1, 5, 3, 9, 6, 7, 10, 16, 4, 0, 12, 2]
function qsort(arr){
if (arr.length < 2) return arr
// choose a pivot, p
// the choice of pivot can effect worst-case performance
// for this, we'll just use the first element.
const [p, ...rest] = arr
// partition array into element greater and lesser that the pivot
// this can be optimized so you don't loop through the array twice
const low = rest.filter(n => n <= p)
const high = rest.filter(n => n > p)
// recurse on both partitions and reassemble as recursion unwinds
return [...qsort(low), p, ...qsort(high)]
}
console.log(qsort(arr).join(', '))
I see a vein of intermediate value creation that is not inconsequential.
peekLast calls Array.prototype.slice which makes a copy of the array. You copy an entire array just to return the last element.
Array.prototype.peekLast = function () { return this.slice().pop(); }
Array.prototype.peekLast = function () { return this[this.length]; }
This gives you the same result every time without the need to copy.
Use of spread arguments in expressions like [ ...arr, x ] copies arr entirely.
arr.concat([ x ]) does the same thing without making copy (or mutation) of arr
You call peekLast and use ...x once per element in the input. Calling sort on a list of just 100 items will copy over 10,000 elements, for these operations alone. A list of just 1,000 items will copy over 1,000,000 elements. Room for algorithm improvment? For sure.
Mark Meyer starts you off on the right foot. If you're going to use recursion, it's best writing your program in functional style, as it will yield the best results. Mixing imperative style (statements, mutations, reassignments, other side effects, etc) with recursion is a recipe for a migraine.
Mark's algorithm, however great a "code improvement", your question is asking for "algorithm improvements". Under this lens, Mark's algorithm suffers from similar intermediate value creation by use of many ...x expressions.
Another lurking offense is the double use of .filter on the same array, rest. This creates an inefficient process as it iterates entirely through rest two (2) times per element. This is a symptom of reaching for low-hanging built-in functions that do close to what you want, but not exactly what you want. A better function would iterate through the array once and return both results.
The inefficiencies in Mark's program are mostly forgivable because of the dramatic improvement in code quality. His program is much more readable than yours because he's using functional style, which is where recursion comes from. The inefficiencies are also very easy to fix, so maybe that's an exercise for you?
Let's see if that gets your brain going. We'll see what answers other people submit before smothering you with too much information.
Your code will fail if we have duplicate elements because of this line.
if (newLast < last) {
It will go into infinite recursion
Refer the snippet with the duplicate array passed as input
function sort(arr) {
if (arr.length === 2) {
const v1 = arr[0];
const v2 = arr[1];
const isGreater = (
(isString(v1) && isString(v2) && v1.toString().toLocaleCompare(v2) > 0) ||
(isNumber(v1) && isNumber(v2) && v1 > v2)
);
return isGreater ? [ v2, v1 ] : [ v1, v2 ];
} else {
const last = arr.pop();
const ret = sort(arr);
const newLast = ret.peekLast();
debugger;
if (newLast < last) {
return [ ...ret, last ];
} else {
return sort( [ last, ...ret ] );
}
}
}
function isString(value) { return typeof value === 'string'; }
function isNumber(value) { return Number.isFinite(value); }
Array.prototype.peekLast = function () { return this.slice().pop(); }
//console.log(sort([1,2,3,4,5]))
console.log(sort([3,3,5,2]))
this one work for me to sort an array recursively:
var array = [3,1,8,2,4,9,16,28];
const sum = (arr, i=0)=> {
if(i === arr.length) return arr;
if(arr[i+1] < arr[i]){
const x = arr[i+1];
arr[i+1] = arr[i];
arr[i] = x;
}
return sum(arr,i+1);
}
console.log(sum(array))
function swap(arr, firstIndex, secondIndex){
let a= arr[firstIndex];
arr[firstIndex] = arr[secondIndex];
arr[secondIndex] = a;
return arr;
}
function sortArr(arr, index=0){
if(index == arr.length) return arr;
for(let i=0;i<arr.length; i++){
if(arr[i] > arr[i+1]){
arr = swap(arr, i, i+1);
}
}
return sortArr(arr, index+1);
}
console.log(sortArr([4,1,3,2,0]));
function quicksort(num){
if (num.length < 2){
return num
}
let pivot = num[0];
let slicedArr = num.slice(1);
let left = [];
let right = [];
for(let i = 0; i < slicedArr.length; i++){
if(slicedArr[i] <= pivot){
left.push(slicedArr[i])
}else{
right.push(slicedArr[i])
}
}
return [...quicksort(left), pivot, ...quicksort(right)]
}

Compare two arrays and create a new array with the differences, accounting for duplicates

Lets say I have two arrays containing numbers..
[1,1,7,6],
[1,7]
I need to create a new array out of the numbers that do not appear in the second array, but also account for there being two 1s.
I have tried using lodash's _.difference([1,1,7,6],[1,7])
as well as some other plain Javascript functions, but they do not account for the there being two 1s, so I just end up with [6]
My desired outcome would be [1,6]
Order does not matter
Iterate over the first array. For each element in the first array, search the second. If it exists, insert null into that index. If it doesn't exist, then push it to a new array with your differences
const difference = (arr1, arr2) => {
const differenceArr = []
for (let i = 0; i < arr1.length; i++) {
const matchingIdx = arr2.indexOf(arr1[i]);
if (matchingIdx !== -1) {
arr2[matchingIdx] = null
} else {
differenceArr.push(arr1[i])
}
}
return differenceArr
}
console.log(difference([1, 1, 7, 7, 6],[1, 1, 7]))
A shorter script:
function differenceWithDuplicates(a, b) {
while(b.length) a.splice(a.indexOf(b[0]), +a.includes(b.shift()));
return a;
}
console.log(differenceWithDuplicates([1, 1, 7, 6],[1, 7]));
Or just make the while, without call it inside a function.
You could do something along the lines of:
const diff = (array1, array2) => {
const _array2 = Array.from(array2);
const difference = [];
array1.forEach((array1Item) => {
const matchedIndex = _array2.findIndex(array2Item => array2Item === array1Item);
if (matchedIndex > -1) {
// Remove from second array
_array2.splice(matchedIndex, 1);
} else {
// No match found, add to difference array
difference.push(array1Item);
}
});
// Return the remaining items in _array2 and difference
return [ ..._array2, ...difference ];
}
By copying the second array, you can remove any matches from it. And by combining that with matching array1 > array2, you get differences in both directions, including duplicates.
Working fiddle: https://jsfiddle.net/3v34tjnq/

Categories

Resources