Interval range insert into intervals without merging existing intervals - javascript

Problem description:
The idea is to insert into existing intervals new interval which doesn't merge with existing intervals but fills the missing gaps between intervals. (This is not the interval merging problem)
For example, inserting interval [0, 7] to intervals [[0, 1], [3, 5]] would result new intervals with gaps filled [[0, 1], [1, 3], [3, 5], [5, 7]].
Interval range is already sorted smallest to larger [[0, 1], [3, 5]].
My current solution is a bit "broken", I ended up using too many if checks to cover some special cases which makes everything more complex then needed. I am looking for better ways to simplify the condition part. In the bottom of the code there are test cases included, also cases where my solution fails.
The test cases where my algorithm is failing and producing wrong results:
assert.deepEqual( // Broken
insertIntervalSec([[1, 5], [7, 10]], [4, 12]),
[[1, 5], [5, 7], [7, 10], [10, 12]],
);
assert.deepEqual(insertIntervalSec([[1, 1]], [1, 3]), [[1, 3]]); // Broken
function isOverLapping(a, b) {
return Math.max(a[0], b[0]) <= Math.min(a[1], b[1]);
}
function insertIntervalSec(arr, interval) {
const result = [];
let i = 0;
const contains = (a, b) => {
return a[0] >= b[0] && a[1] <= b[1]
};
if (arr.length <= 0) {
result.push(interval);
return result;
}
if (arr.length === 1 && contains(interval, arr[0])) {
result.push(interval);
return result;
}
// Start point
if (interval[1] >= arr[0][0] && isOverLapping(interval, arr[0])) {
result.push([interval[0], arr[0][0]]);
} else if (interval[1] <= arr[0][0]) {
result.push([interval[0], Math.min(interval[1], arr[0][0])]);
}
while (i < arr.length) {
const current = arr[i];
result.push(arr[i]);
if (!contains(interval, arr[i]) && isOverLapping(arr[i], interval)) {
const next = arr[i + 1];
// Special handling for the last item
if (next !== undefined) {
if (interval[1] > current[1]) {
result.push([current[1], next[0]]);
}
} else {
if (interval[0] <= current[0] && interval[1] <= current[1]) {
// TODO: No action
} else if (interval[0] >= current[0] || interval[1] >= current[0]) {
result.push([current[1], interval[1]]);
}
}
}
i++;
}
// End point
const len = arr.length;
const last = arr[len - 1];
if (last[1] <= interval[0] && !isOverLapping(last, interval)) {
result.push(interval);
}
return result;
}
assert.deepEqual(
insertIntervalSec([[1, 5], [10, 15], [20, 25]], [12, 27]),
[[1, 5], [10, 15], [15, 20], [20, 25], [25, 27]]
);
assert.deepEqual(
insertIntervalSec([[1, 5], [10, 15], [20, 25]], [-3, 0]),
[[-3, 0], [1, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[1, 5], [10, 15], [20, 25]], [-3, 3]),
[[-3, 1], [1, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [15, 15]),
[[0, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [20, 21]),
[[0, 5], [10, 15], [20, 25]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [26, 27]),
[[0, 5], [10, 15], [20, 25], [26, 27]]
);
assert.deepEqual(
insertIntervalSec([[0, 5], [10, 15], [20, 25]], [25, 27]),
[[0, 5], [10, 15], [20, 25], [25, 27]]
);
assert.deepEqual(insertIntervalSec([], [25, 27]), [[25, 27]]);
assert.deepEqual(insertIntervalSec([[1, 1]], [1, 1]), [[1, 1]]);
assert.deepEqual( // Broken
insertIntervalSec([[1, 5], [7, 10]], [4, 12]),
[[1, 5], [5, 7], [7, 10], [10, 12]],
);
assert.deepEqual(insertIntervalSec([[1, 1]], [1, 3]), [[1, 3]]); // Broken
assert.deepEqual(
insertIntervalSec2([[5, 5]], [6, 6]),
[[5, 5], [6, 6]]
);
assert.deepEqual(
insertIntervalSec2([[1, 3]], [6, 6]),
[[1, 3], [6, 6]]
);

With the exception of the last test case (see comment on question), this passes all the tests. The basic idea is you just keep track of start variable that indicated where how much of the inserted range you have used. This allows you to narrow it down to three cases:
the inserted interval fits entirely before the the current item
the current item in the iteration fits completely before the inserted interval
the item in the iteration overlaps.
After iterating the items, you can check if the inserted range has anything left to insert:
function insertIntervalSec(arr, insert) {
let start = insert[0]
let res = []
for (i = 0; i < arr.length; i++) {
let a = arr[i]
// smaller item in range
if (a[0] <= start) {
res.push(a)
start = Math.max(a[1], start)
continue
}
// moved past inserted interval add rest of arr
if (start >= insert[1]) {
res.push(...arr.splice(i))
break
}
// fill in spaces
let end = Math.min(insert[1], a[0])
res.push([start, end], a)
start = a[1]
}
// clean up left over range
if (start < insert[1]) res.push([start, insert[1]])
return res
}
console.log(insertIntervalSec([ [1, 5],[10, 15],[20, 25]], [-2, 27]))

Related

Filter each inner property arrays with Ramda

I have an object like this:
const arrays = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
As you can see each first number is equal:
1 for each first inner array
2 for each second inner array
3 for each third inner array
etc...
I want to filter based on the first number of each array
and some comparator number e.g. [3]
If we have a filter number [3] (smaller or equal to 3),
the wanted result would be:
const arrays = {
one: [[1,33,41], [2,0,27], [3,7,9]],
two: [[1,77,2], [2,6,3], [3,0,0]],
three: [[1,4,6], [2,0,0], [3,5,6]],
};
Since all first numbers of inner arrays are smaller than or equal to 3.
The arrays starting with 4,5... are ignored.
What would be the ramda's way to have this functionality?
I like Ramda's map function because it can iterate over the properties of an object (and so avoid Object.fromEntries & Object.entries) and apply a function to each one of them. That function is filter which will take as argument the inner arrays. The function given to filter is itself a composition of gte and head; takes the first element of an array and compare it with 3:
const arrays =
{ one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]]
, two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]]
, three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]] };
map(filter(compose(gte(3), head)), arrays);
// ^ ^ ^ ^ ^
// A B C D E
//=> { one: [[ 1, 33, 41], [2, 0, 27], [3, 7, 9]]
//=> , two: [[ 1, 77, 2], [2, 6, 3], [3, 0, 0]]
//=> , three: [[ 1, 4, 6], [2, 0, 0], [3, 5, 6]] }
map over each property (A); each array is passed to filter (B)
Each inner array is passed to compose (C)
Take the head of each inner array (E) and compare with 3 (D)
Scott Christopher rightly pointed out in the comments that gte can be confusing when partially applied. In fact the whole composition can be replaced with this simple lambda: ([x]) => x <= 3.
Alternative solution which I like too:
map(filter(([x]) => x <= 3), arrays);
I'd totally subscribe for #customcommander's approach,
just wanted to add that you can also pass numerical indexes to R.propSatisfies.
const headIs3OrBelow = R.propSatisfies(R.gte(3), 0);
const fn = R.map(R.filter(headIs3OrBelow));
// ===
const data = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
console.log(
fn(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Also agree that gte and other similar methods are very difficult to read, because they kind of read backwards as is 3 gte than x... in Haskell you could do something like:
3 `gte` x
Vanilla approach:
const headIs3OrBelow = ([head]) => head <= 3;
const fn = (data) => Object.entries(data).reduce(
(res, [k, lists]) => ({ ...res, [k]: lists.filter(headIs3OrBelow) }),
{},
);
// ===
const data = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
console.log(
fn(data),
);
I understand you would like to use Ramda, this is not a solution using the library but accomplishes the same. You could create an object from entries that are filtered by comparing the first array value a[0] to the maxVal passed to the function.
const arrays = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
const filterArrays = (arsObj, maxVal) => {
return Object.fromEntries(Object.entries(arsObj).map(([k, v]) => {
return [k, v.filter((a) => a[0] <= maxVal)];
}));
}
const result = filterArrays(arrays, 3);
console.log(result);

Filtering an array of arrays JS

I'm trying to write a function, that accepts an array of arrays as argument and filtering this array under certain condition. I will explain this condition later.
For example I have such array:
const arr = [
[1, 1, 20],
[2, 1, 15],
[3, 1.5, 15],
[4, 1, 15],
[5, 1, 20],
[6, 1.5, 15],
[7, 1, 25],
[8, 1, 15],
[9, 0, 15],
[10, 0, 15],
[11, 0, 15],
]
and the condition. I want to filter it by the value of the third column, but only if it's at least three in a sequence. The function should accept two arguments (or more if needed): value and array
So for example
const filterByValue = (array, val) => { //definition
return filtredArr
}
const newArr = filterByValue(array,15)
and now newArr should equal:
[
[2, 1, 15],
[3, 1.5, 15],
[4, 1, 15],
[8, 1, 15],
[9, 0, 15],
[10, 0, 15],
[11, 0, 15],
]
For now I only made:
const filterByValue = (arr, value) => {
const newArr = arr.filter((elem, index) => elem[2] === value)
return newArr
}
but this function returns
[
[2, 1, 15],
[3, 1.5, 15],
[4, 1, 15],
[6, 1.5, 15],
[8, 1, 15],
[9, 0, 15],
[10, 0, 15],
[11, 0, 15],
]
there shouldn't be [6,1.5,15]. I have no idea how it could be done to return only fragments of an array that contain at least three internal arrays in a row. Maybe any of you have any idea?
edit more explanation
This sequence means to me that I want only output which internal arrays that contain a set value in the third value (value argument in a function), but that they also have internal arrays that follow each other (at least three). Let's assume that the function accepts arr (first array in my post) and value = 15. The first internal array contains 20 as the third value, so it falls off. The second is okey, the third also and the fourth also - and should return in this situation 2,3,4 internal board (because they follow three in succession). Then the fifth value is 20, the sixth value is 15, but the next (seventh) value 25 again, so the function should not take into account the sixth table (because it is not followed by at least two tables that have the value 15). In the table 8,9,10,11 we have the value 15, and these are four tables (i.e. at least three). Therefore, the function should return an array containing the following tables 2,3,4 and 8,9,10,11. The sixth array contains the value 15, but not followed by two more that would have this value as well. The expected output is the second board in my post.
const filterByValue = (arr,value) => {
let newArray = [];
let cache = [];
arr.forEach(elem => {
if (elem[2] === value) {
cache.push(elem);
} else {
if (cache.length >= 3) {
newArray = newArray.concat(cache);
}
cache = [];
}
});
if (cache.length >= 3) {
newArray = newArray.concat(cache);
}
return newArray;
}
You could take a closure over an index and check if the values are in a sequence of three.
const
array = [[1, 1, 20], [2, 1, 15], [3, 1.5, 15], [4, 1, 15], [5, 1, 20], [6, 1.5, 15], [7, 1, 25], [8, 1, 15], [9, 0, 15], [10, 0, 15], [11, 0, 15]],
value = 15,
result = array.filter(
(index => ({ 2: v }, i, a) => {
if (v !== value) return false;
if (index === i || a[i + 1][2] === value && a[i + 2][2] === value) {
index = i + 1;
return true;
}
})
(-1)
);
console.log(result);
Try with filter and conditions based on whether match the value or not.
const arr = [
[1, 1, 20],
[2, 1, 15],
[3, 1.5, 15],
[4, 1, 15],
[5, 1, 20],
[6, 1.5, 15],
[7, 1, 25],
[8, 1, 15],
[9, 0, 15],
[10, 0, 15],
[11, 0, 15],
];
const filterByValue = (array, val) =>
array.filter((ar, i, items) => {
if (ar[2] !== val) {
return true;
} else {
return items[i - 1][2] === val || items[i + 1][2] === val;
}
});
console.log(filterByValue(arr, 15));
Without using the cache array.
Check if element contains number 15 if yes increment the counter if that counter reaches at least 3 it means you can store those 3 element into a new list. Use the the difference between the current index and the counter to get from the first sequence element to the last one. Repeat the process until end of the array is reached.
const arr = [
[1, 1, 20],
[2, 1, 15],
[3, 1.5, 15],
[4, 1, 15],
[5, 1, 20],
[6, 1.5, 15],
[7, 1, 25],
[8, 1, 15],
[9, 0, 15],
[10, 0, 15],
[11, 0, 15]
];
let cnt = 0;
let list = [];
for (let i = 0; i < arr.length; i++) {
// check if elements meet criteria
while (i < arr.length && arr[i][arr[i].length - 1] === 15) {
++i;
++cnt;
}
// acumulate into your array if at least 3 elm
if (cnt >= 3) {
for (let j = i - cnt; j < i; j++) {
list.push(arr[j]);
}
}
cnt = 0;
}
console.log(list)

How to find a max value for any quantity of arrays?

I need to find Max values for any number of arrays within the function. For example, user can add 1 array or 10 arrays. Output should show max values for each of the arrays.
Currently, my code works only if 1 array is provided.
Please let me know what is wrong.
function getMaxs(args){
array = args;
var max = array[0];
for(i = 0; i < array.length; i++){
if(max < array[i]){
max = array[i];
}
}
console.log(max);
return max;
}
getMaxs([5, 6, 7], [18, 19, 20], [5, 7, 3, 10]); // output is 7
getMaxs([5, 6, 7]); // output is 7
getMaxs([18, 19, 20], [5, 10, 74, 394]); // output is 20
If you need a max for every provided array:
const getMaxs = (...args) => args.map(arr => Math.max(...arr));
console.log( getMaxs([5, 6, 7], [18, 19, 20], [5, 7, 3, 10]) ); // [7, 20, 10]
console.log( getMaxs([5, 6, 7]) ); // [7]
console.log( getMaxs([18, 19, 20], [5, 10, 74, 394]) ); // [20, 394]
If you don't care which sub-array the result comes from, you can use Array.prototype.flat:
function getMaxs(array) {
const flatArray = array.flat();
return Math.max(...flatArray);
}
const arr = [[5, 6, 7], [18, 19, 20], [5, 7, 3, 10]];
const max = getMaxs(arr);
console.log(max);
I believe you're looking for rest parameters.
Something along these lines:
function getMaxs(...arrays) {
return Math.max(...arrays.flat());
}
// usage
getMaxs([5, 6, 7], [18, 19, 20], [5, 7, 3, 10]); // output is 20
getMaxs([5, 6, 7]); // output is 7
getMaxs([18, 19, 20], [5, 10, 74, 394]); // output is 394
Related: The arguments object in case you can't use rest parameters. In that case, you probably don't support flat either, which in that case I'll recommend you to look for a polyfill somewhere.
You can use the .flat() function, so the arrays would merge and become one, like this:
function getMaxs(args){
array = args.flat();
//it will now see it as one big array
....
}
But, the .flat() function may not work on older browsers, so you may also do this:
array = args.reduce((acc, val) => acc.concat(val), [])
The effect will be the same.
Iraklis solution is fine, but it suppose you give an array of arrays. However, if you want to pass arrays as separated arguments, you could use the Spread syntax and Rest parameters:
function getMaxs(...arrays) {
const max = Math.max(...arrays.flat())
console.log(max) // DEBUG
return max
}
getMaxs([5, 6, 7], [18, 19, 20], [5, 7, 3, 10])
getMaxs([5, 6, 7])
getMaxs([18, 19, 20], [5, 10, 74, 394])
Of course, you should be careful about browser compatibility:
function getMaxs(...args) {
array = args.flat();
var max = array[0];
for (i = 0; i < array.length; i++) {
if (max < array[i]) {
max = array[i];
}
}
// console.log(max);
return max;
}
console.log(getMaxs([5, 6, 7], [18, 19, 20], [5, 7, 3, 10]));
console.log(getMaxs([5, 6, 7]));
console.log(getMaxs([18, 19, 20], [5, 10, 74, 394]));
the above one will be the solution for your question. You can check the below screenshots for your reference.
For this input i have added screen shots for your reference how it will process the code
getMaxs([5, 6, 7], [18, 19, 20], [5, 7, 3, 10])
now it will take the whole
[![enter image description here][1]][1]

Find if two arrays are repeated in array and then select them

I have multiple arrays in a main/parent array like this:
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
here are the array's for simpler reading:
[1, 17]
[1, 17]
[1, 17]
[2, 12]
[5, 9]
[2, 12]
[6, 2]
[2, 12]
[2, 12]
I want to select the arrays that are repeated 3 or more times (> 3) and assign it to a variable. So in this example, var repeatedArrays would be [1, 17] and [2, 12].
So this should be the final result:
console.log(repeatedArrays);
>>> [[1, 17], [2, 12]]
I found something similar here but it uses underscore.js and lodash.
How could I it with javascript or even jquery (if need be)?
Try this
array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
var r= array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))
console.log(JSON.stringify(r));
Time complexity O(n) (one array pass by filter function). Inspired by Nitish answer.
Explanation
The (r={}, a=>...) will return last expression after comma (which is a=>...) (e.g. (5,6)==6). In r={} we set once temporary object where we will store unique keys. In filter function a=>... in a we have current array element . In r[a] JS implicity cast a to string (e.g 1,17). Then in !(2-(r[a]=++r[a]|0)) we increase counter of occurrence element a and return true (as filter function value) if element a occurred 3 times. If r[a] is undefined the ++r[a] returns NaN, and further NaN|0=0 (also number|0=number). The r[a]= initialise first counter value, if we omit it the ++ will only set NaN to r[a] which is non-incrementable (so we need to put zero at init). If we remove 2- as result we get input array without duplicates - or alternatively we can also get this by a=>!(r[a]=a in r). If we change 2- to 1- we get array with duplicates only.
UPDATE
Even shorter version based on #ken comment can be written (it should always work with arrays of numbers). The original longer version of #ken code is in snippet and shows how #ken uses in clever way second argument of .filter to avoid usage global variable r.
array.filter(a=>!(2-(this[a]=++this[a]|0)))
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
var r= array.filter(a=>!(2-(this[a]=++this[a]|0)), {})
console.log(JSON.stringify(r));
You could take a Map with stringified arrays and count, then filter by count and restore the arrays.
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = Array
.from(array.reduce(
(map, array) =>
(json => map.set(json, (map.get(json) || 0) + 1))
(JSON.stringify(array)),
new Map
))
.filter(([, count]) => count > 2)
.map(([json]) => JSON.parse(json));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Filter with a map at wanted count.
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = array.filter(
(map => a =>
(json =>
(count => map.set(json, count) && !(2 - count))
(1 + map.get(json) || 1)
)
(JSON.stringify(a))
)
(new Map)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Unique!
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = array.filter(
(s => a => (j => !s.has(j) && s.add(j))(JSON.stringify(a)))
(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Object.reduce, Object.entries for this like below
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
let res = Object.entries(
array.reduce((o, d) => {
let key = d.join('-')
o[key] = (o[key] || 0) + 1
return o
}, {}))
.flatMap(([k, v]) => v > 2 ? [k.split('-').map(Number)] : [])
console.log(res)
OR may be just with Array.filters
var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
let temp = {}
let res = array.filter(d => {
let key = d.join('-')
temp[key] = (temp[key] || 0) + 1
return temp[key] == 3
})
console.log(res)
For a different take, you can first sort your list, then loop through once and pull out the elements that meet your requirement. This will probably be faster than stringifying keys from the array even with the sort:
var arr = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]]
arr.sort((a, b) => a[0] - b[0] || a[1] - b[1])
// define equal for array
const equal = (arr1, arr2) => arr1.every((n, j) => n === arr2[j])
let GROUP_SIZE = 3
first = 0, last = 1, res = []
while(last < arr.length){
if (equal(arr[first], arr[last])) last++
else {
if (last - first >= GROUP_SIZE) res.push(arr[first])
first = last
}
}
if (last - first >= GROUP_SIZE) res.push(arr[first])
console.log(res)
You could also do this with a single Array.reduce where you would only push to a result property if the length is equal to 3:
var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
console.log(array.reduce((r,c) => {
let key = c.join('-')
r[key] = (r[key] || 0) + 1
r[key] == 3 ? r.result.push(c) : 0 // if we have a hit push to result
return r
}, { result: []}).result) // print the result property
ES6:
const repeatMap = {}
array.forEach(arr => {
const key = JSON.stringify(arr)
if (repeatMap[key]) {
repeatMap[key]++
} else {
repeatMap[key] = 1
}
})
const repeatedArrays = Object.keys(repeatMap)
.filter(key => repeatMap[key] >= 3)
.map(key => JSON.parse(key))

Get some set of array based on condition from two arrays of array in Javascript

I have two arrays of array in Javascript like
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]];
var array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]];
I want to get the set of arrays from array1 which match the first element of each array of the array2
in my example case both array1 and array2 have array with first element as 10 11 and 12, so it should return
[[10, 2], [11, 4], [12, 30]];
is there any easy and efficient way using pure javscript or lodash, underscor framework or something like that. Without iterate over and match one by one of this two array ?
In ES6, you could use Set.
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]],
array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]],
set = new Set(array2.map(a => a[0])),
result = array1.filter(a => set.has(a[0]));
console.log(result);
Version with an object as hash table
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]],
array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]],
result = array1.filter(function (a) {
return this[a[0]];
}, array2.reduce(function (r, a) {
r[a[0]] = true;
return r;
}, Object.create(null)));
console.log(result);
You can use lodash _.intersectWith function in order to solve this problem in an inline.
_.intersectionWith(array1, array2, function(a, b) {
return a[0] === b[0];
});
I don't know about performance cos I haven't had the chance to have a look at the source code of this function. Anyway, I like it for its simplicity. Here's the fiddle in case you want to check it out.
If you can make use of Set, then you can compute the a set of numbers to look for first and use .filter to only get the arrays whose first element is in that set:
var haystack = new Set(array2.map(x => x[0]));
var newArray = array1.filter(x => haystack.has(x[0]));
Of course you can also use the lodash or underscore versions of .map and .filter.
Alternatives to using Set would be:
Create an array of numbers instead and use indexOf to test existence. That will scale linearly with the number of elements:
var haystack = array2.map(x => x[0]);
var newArray = array1.filter(x => haystack.indexOf(x[0]) > -1);
Create an object with number -> true entries to test existence with in, hasOwnProperty or just object access:
var haystack = array2.reduce((obj, x) => (obj[x[0]] = true, obj), {});
var newArray = array1.filter(x => haystack[x[0]]);
Which one performs better depends on the number of elements you have and the environment the code is running in.
You can do this with filter() and find()
var array1 = [
[10, 2],
[11, 4],
[12, 30],
[13, 17],
[14, 28]
];
var array2 = [
[8, 13],
[9, 19],
[10, 6],
[11, 7],
[12, 1]
];
var result = array1.filter(function(ar) {
return array2.find(function(e) {
return e[0] == ar[0]
})
})
console.log(result)
I would do this with a Map anf filter combo in ES6 as follows;
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]],
array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]],
m = new Map(array2),
array3 = array1.filter(a => m.has(a[0]));
console.log(array3);
If you need backwards compatibility other answers are pretty good.

Categories

Resources