I have an array of object,I want to get the closest previous id from nearest object.I can able to get closest next id,its working fine but for previous is not working.Its taking direct first id of object.Here is the code below.Can anyone please help me on it.
JAVASCRIPT
const array = [{id:4}, {id:10}, {id:15}];
const findClosesPrevtId = (x) => ( array.find( ({id}) => x <= id ) || {} ).id;
const findClosestNextId = (x) => ( array.find( ({id}) => x >= id ) || {} ).id;
console.log(findClosesPrevtId(5));
console.log(findClosestNextId(11));
Cause x <= i will be fullfilled for the first element if you use find to search from left to right. Use findLast to search from right to left.
Unfortunately I just found out that there is actually not a findLast yet (there is reduceRight, lastIndexOf ... :/), so you have to write it yourself:
Object.defineProperty(Array.prototype, "findLast", {
value(cb, context) {
for(let i = this.length - 1; i >= 0; i--)
if(cb.call(context, this[i], i, this))
return this[i];
}
});
const findClosesPrevtId = (x) => ( array.findLast( ({id}) => x <= id ) || {} ).id;
You could store the precious/next element and stop the iteration if id is greater than the wanted id.
const
closestPrevious = id => {
var last = {};
array.some(o => o.id > id || (last = o, false))
return last.id;
},
closestNext = id => {
var next = array[0];
array.some((o, i, { [i + 1]: n = {} }) => o.id > id || (next = n, false))
return next.id;
},
array = [{ id: 4 }, { id: 10 }, { id: 15 }];
console.log(closestNext(5)); // 10
console.log(closestNext(11)); // 15
console.log(closestPrevious(5)); // 4
console.log(closestPrevious(11)); // 10
I find it easier to reverse the array and switch the comparison from >= to <=:
const findClosestNextId = (x, arr) =>
(arr.find ( ({id}) => id >= x) || {} ) .id
const findClosestPrevId = (x, arr) =>
(arr .slice(0) .reverse() .find ( ({id}) => id <= x) || {}) .id
const array = [{ id: 4 }, { id: 10 }, { id: 15 }];
console .log (
findClosestNextId (5, array), //=> 10
findClosestNextId (11, array), //=> 15
findClosestNextId (42, array), //=> undefined
findClosestPrevId (5, array), //=> 4
findClosestPrevId (11, array), //=> 10
findClosestPrevId (2, array), //=> undefined
)
The slice call is there to prevent this from modifying the original array. This will return undefined if there is no element found.
I made some changes to your code and it should work now.
Have a look.
const array = [{id:3}, {id:4}, {id:10}, {id:15}];
// you should order the list by id before you try to search, this incase you have not orginized list.
// filter the list first and get the prev id to 5
// you should get 3 and 4 then
// slice(-1) to get the last element of the array which should be 4
const findClosesPrevtId = (x) =>
(array.filter(({id}) => id <= x ).slice(-1)[0] || {}).id;
const findClosestNextId = (x) =>
(array.filter(({id}) => id >= x )[0] || {}).id;
console.log("Prev to 5:"+ findClosesPrevtId(5));
console.log("Next to 11:" +findClosestNextId(11));
Related
How can i verify how many numbers in a array is in or out a 10-20 interval (10 and 20 included) in one function? I tried but i just got it with two functions, one to verify if its out and one to verify if its in.
let array = [1,3,7,10,14,18,20,23,27]
function inInterval(e){
if (e >= 10 && e <=20) {
return e
}
}
function outInterval(e) {
if (e <10 || e>20) {
return e
}
}
let inIntervalResult = array.filter(inInterval).length
let outIntervalResult = array.filter(outInterval).length
console.log(inIntervalResult, outIntervalResult)
countall = (arr) => {
countin=0
countout=0
arr.forEach(x => (x<20 && x>10)?countin++:countout++)
return {'in':countin,'out':countout}
}
console.log(countall([1,3,5,7,12,15,17,11,20]))
let array = [1,3,7,10,14,18,20,23,27]
let [outIntervalResult, inIntervalResult] =
array.reduce((r,e)=>(r[+(e >= 10 && e <=20)]++, r), [0,0])
console.log(inIntervalResult, outIntervalResult)
Note that + coerces a true/false value to 1/0, and so concisely selects the index of the result to increment.
You just need to find the number of elements in the range and minus this from total number of elements to get the number of elements out of the range.
Irrespective of the logic you use to count the number of elements for in or out of the range, you can return an object (or simply an array) with both counts inside.
var array = [1,3,7,10,14,18,20,23,27]
function countInOutRange(arr, min, max) {
let inRange = arr.filter(e => e >= min && e <= max).length
// Or use a simple for/forEach loop to count
let outRange = arr.length - inRange
return {inRange, outRange} // or [inRange, outRange]
}
console.log(countInOutRange(array, 10, 20))
This should actually be fine!
You can also do
let outIntervalResult = e.length - inIntervalResult
If you don’t want to have two functions or filter twice.
You can simply achieve this with a single Array.forEach() method.
Live Demo :
let arr = [1, 3, 7, 10, 14, 18, 20, 23, 27];
const minVal = 10;
const maxVal = 20;
let inArr = [];
let outArr = [];
arr.forEach(val => {
(val >= minVal && val <= maxVal) ? outArr.push(val) : inArr.push(val)
})
console.log(inArr, inArr.length);
console.log(outArr, outArr.length);
The last part to this exercise is to write a recursive function that takes two parameters, a joined list and an index respectively. The function will find the value in the object within the list at it's respective index. The code i have written works the way i want (i can see it working when i console.log for every occasion the function is called. But on the last occasion it refers undefined as my value. I cannot understand why. Oh and it works for index of 0. code as followed.
and first, list looks like this:
list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
// console.log(value);
// console.log(targetNum);
// console.log(list);
nth(list, targetNum);
}
};
console.log(nth(arrayToList([1,2,3]),2));
below is the code for arrayToList it was the first part of the exercise and if you have any comments that's cool, cause the hints ended up suggesting to build the list from the end.
const arrayToList = (arr) => {
let list = {
value: arr[0],
rest: nestObject()
};
function nestObject() {
let rest = {};
arr.shift();
const length = arr.length;
if (length == 1) {
rest.value = arr[0];
rest.rest = null;
} else {
rest.value = arr[0];
rest.rest = nestObject();
}
return rest;
}
return list;
};
Both solutions are convoluted and unnecessary verbose. Actually, both functions could be one-liners. Here are a few hints:
For the toList thing consider the following:
if the input array is empty, return null (base case)
otherwise, split the input array into the "head" (=the first element) and "tail" (=the rest). For example, [1,2,3,4] => 1 and [2,3,4]
return an object with value equal to "head" and rest equal to toList applied to the "tail" (recursion)
On a more advanced note, the split can be done right in the function signature with destructuring:
const toList = ([head=null, ...tail]) => ...
Similarly for nth(list, N)
if N is zero, return list.value (base case)
otherwise, return an application of nth with arguments list.rest and N-1 (recursion)
Again, the signature can benefit from destructuring:
const nth = ({value, rest}, n) =>
Full code, if you're interested:
const toList = ([value = null, ...rest]) =>
value === null
? null
: {value, rest: toList(rest)}
const nth = ({value, rest}, n) =>
n === 0
? value
: nth(rest, n - 1)
//
let lst = toList(['a', 'b', 'c', 'd', 'e', 'f'])
// or simply toList('abcdef')
console.log(lst)
console.log(nth(lst, 0))
console.log(nth(lst, 4))
You simply need to add a return when recursively calling nth. Otherwise the logic is carried out but no value is returned (unless targetNum is 0)
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
return nth(list, targetNum); // return needed here too
}
};
Or more succinctly:
const nth = (list, n) => n === 0 ? list.value : nth(list.rest, n - 1)
Here's another non-recursive arrayToList that builds the list from the end:
const arrayToList = arr => arr.slice().reverse().reduce((rest, value) => ({value, rest}), null);
(The slice here is just to make a copy of the array so that the original is not reversed in place.)
Georg’s recursive solutions are beautiful!
I’d like to add the hinted “build the list from the end” solution from the book:
const arrayToList => (arr) => {
var list
while (arr.length) {
list = {value: arr.pop(), rest: list}
}
return list
}
I have an array as following
array = [
{
name: 'A'
instructors: [
{
name:'InsA'
}
]
businessUnit: {name:'myBusiness'}
},
{
name: 'B'
instructors: [
{
name:'InsB'
}
]
businessUnit: {name:'myBusinessB'}
}
]
I want to filter this array with the values i have which are also in an array as following
classArr = [A,C,D]
instructorArr = [InsA,InsC,InsZ]
businessName = [myBusinessB,myBusinessX,myBusinessD]
NOTE: These filters can have empty arrays as well. For Ex: businessName = [] .
My current approach is to filter as follows
const filtered = array?.filter(
(activity) =>
classArr?.includes(activity.name) &&
activity.instructors?.some((instructor) =>
instructorArr?.some((instructorFilter) => instructor?.name === instructorFilter),
) &&
businessName?.includes(activity.businessUnit?.name),
);
But the issue is this returns if all conditions are met. Not when just 2 or 1 condition is met. How can i return the filtered array when several or all conditions are met?
const filtered = array?.filter(
(activity) =>
classArr?.includes(activity.name) ||
activity.instructors?.some((instructor) =>
instructorArr?.some((instructorFilter) => instructor?.name === instructorFilter),
) ||
businessName?.includes(activity.businessUnit?.name),
);
In your filtered function
const filtered = array?.filter(
(activity) =>
classArr?.includes(activity.name) &&
activity.instructors?.some((instructor) =>
instructorArr?.some((instructorFilter) => instructor?.name === instructorFilter),
) &&
businessName?.includes(activity.businessUnit?.name),
);
You basically say you want to keep all activities where:
The array includes the class name
AND the array includes the instructor
AND the array includes the business unit
(the && operator is the AND operator).
However you state that you want to 'return the filtered array when several [OR] all conditions are met?'
So to fix this, you should switch out the && (AND) operator with the || (OR) operator like this:
const filtered = array?.filter(
(activity) =>
classArr?.includes(activity.name) ||
activity.instructors?.some((instructor) =>
instructorArr?.some((instructorFilter) => instructor?.name === instructorFilter),
) ||
businessName?.includes(activity.businessUnit?.name),
);
You need to check for the length in your array. Only doing array? with check if its nullish, not the size.
const filtered = array?.filter(
(activity) =>
(
classArr.length ? classArr.includes(activity.name) : false
)
&& (
activity.instructors.length // check for length
? activity.instructors.some((instructor) =>
instructorArr.length // check for length
? instructorArr.some((instructorFilter) => instructor.name === instructorFilter)
: false)
: false
)
&& (
businessName.length ? businessName.includes(activity.businessUnit?.name) : false
)
);
That line is very hard to read and will be hard for anyone else to understand in the future, so lets open it up
let filtered = [];
if (array.length){
for (const index in myArray){
const has_classArr = !!classArr.length;
const has_instructorArr = !!instructorArr.length;
const has_businessName = !!businessName.length; // check for length, if you do "businessName?" it only cheks for its existence
if (has_classArr && has_instructorArr && has_businessName){
const activity = myArray[index];
const has_activityInstructors = activity.instructors && activity.instructors.length
const has_activityBusinessUnit = !!activity.businessUnit
if (has_activityInstructors && has_activityBusinessUnit){
for (const instructor of activity.instructors){
if (instructorArr.includes(instructor.name) && businessName.includes(activity.buninessUnit.name) && classArr.includes(activity.name))
filtered.push(activity)
}
}
}
}
}
I'm trying to find the opposite number in the array.
Let's say we have this array: [1,3,4,-3]
What I want is:
{ '1': 1, '3': 2, '4': 1 } // as we have two 3
and what I tried is :
const arr = [1,3,4,-3]
let holder = {}
arr.map(a => {
holder[a] = (holder[a] ||0)+1
})
console.log(holder)
Any idea how can I acheive this ?
The rules can be summed up as follow:
Let v be the array
Let x a positive number in the array
if x is in v but not -x, keep {[x]:1}
if x is in v and -x also, keep {[x]:2}
Let y a negative number in the array
if y is in v and -y also do nothing (symetry of 2.)
if y is in v but not -y, keep {[y]:1}
Now it is pretty straightforward:
build a set of values of v
check all the positive keys
foreach one, check if there exists its opposite and apply 1. or 2.
delete any opposite keys
for the remaining keys (which are then negative and have no opposite) apply 3.
const v = [1,1,1,-2, 3,3,-3, 4,-4,-4]
const output = {}
const s = new Set(v)
const positives = [...new Set(v.filter(x => x>= 0))]
positives.forEach(p => {
if (s.has(-p)) {
output[p] = 2
s.delete(-p)
} else {
output[p] = 1
}
s.delete(p)
})
;[...s].forEach(negative => {
output[negative] = 1
})
console.log('output', output)
Now we can be a bit more aggressive regarding the symetry:
if x === -y
upon iterating x, setting {keep[x]: 2}
then upon iterating -y, setting {keep[-y]: 2} is equivalent to setting {keep[x]: 2} which has already been done.
So we may or may not set {keep[-y]: 2}, this is equivalent
const v = [1,1,1,-2, 3,3,-3, 4,-4,-4]
const output = {}
const s = new Set(v)
;[...s].forEach(x => {
if (s.has(-x)) {
output[Math.abs(x)] = 2
} else {
output[x] = 1
}
})
console.log('output', output)
Not quite sure if this is really what you trying to achieve. If so, you can do it with a reduce. Please check:
const arr = [1, 3, 4, -3];
const obj = arr.reduce((acc, cur) => ({
...acc,
[Math.abs(cur)]: (acc[Math.abs(cur)] || 0) + 1,
}), {});
console.log(obj);
const arr = [1,3,4,-3];
const allPositive = arr.map(item => Math.abs(item));
const reducer = (acc, item, index) => {
const count = allPositive.filter(x => x===item).length;
acc[item] = count;
return acc
};
const result = allPositive.reduce(reducer, {});
console.log(result)
I have an array of objects in my angular controller.
I want to return the value of the index of the field within the array which has a matching ID to my parameter.
There will only be one object in the array with a matching fieldId..
$scope.indexOfField = function(fieldId) {
return $scope.model.fieldData.filter(function(x) {
if (x.Id === fieldId) return // ???????
});
}
The .findIndex() method returns the index of the first element of the array that satisfies a condition given by a function. If the function returns false for all elements of the array, the result is -1.
See the documentation here.
In my example, x is an item for each iteration and I use cross function for my condition.
const datas = [];
const fieldId = 5;
let index = datas.findIndex( x => x.Id === fieldId );
You can't return index from filter method.
The filter() method creates a new array with all elements that pass
the test implemented by the provided function.
You can use forEach
$scope.indexOfField = function(fieldId) {
var i;
return $scope.model.fieldData.forEach(function(x, index) {
if (x.Id === fieldId) {
i = index;
}
});
// use i
}
or even better to use for as you can't stop forEach when you have found your id.
$scope.indexOfField = function(fieldId) {
var fieldData = $scope.model.fieldData,
i = 0, ii = $scope.model.fieldData.length;
for(i; i < ii; i++) if(fieldData[i].Id === fieldId) break;
// use i
}
From the Array.prototype.filter documentation:
callback is invoked with three arguments:
the value of the element
the index of the element
the Array object being traversed
However you should probably be using the some function if there is only one instance in your array (as it will stop as soon as it finds the first occurrence), and then find the index using indexOf:
var field = $scope.model.fieldData.filter(function(x) {
return x.Id === fieldId;
})[0];
var index = $scope.model.fieldData.indexOf(field);
Or iterate the array until you find the correct element:
var index;
$scope.model.fieldData.some(function(x, i) {
if (x.Id === fieldId) return (index = i);
});
ARRAY (FIND MULTIPLE INDEXES) METHOD
[10, 7, 13, 15, 230].map((e,i) => e > 13 ? i : undefined).filter(x => x)
//returns [3, 4](*** RETURNS multiple indexes ***)
//FILTER (is simply just REMOVING the UNDEFINED elements (which are FALSY and considered the same as FALSE)
otherwise you'll get...
[10, 7, 13, 15, 230].map((e,i) => e > 13 ? i : undefined) //returns [undefined, undefined, undefined, 3, 4]
RETURN MULTIPLE INDEXES (replaces findIndex METHOD)
[1, 1, 2, 2, 2, 3, 4, 5].map((e,i) => e === 2 ? i : undefined).filter(x => x) //returns [2, 3, 4]
RETURN MULTIPLE VALUES (replaces find METHOD)
[5, 12, 8, 130, 44].map((e,i) => e > 13 ? e : undefined).filter(x => x) // returns [130, 44]
The second argument to your callback is the index. I can't quite make out what you want your function to do/return, but if you add , index after function(x, that will give you access to the index for that iteration.
Working from the name of your function, I don't think you want filter at all:
$scope.indexOfField = function(fieldId) {
var result = -1;
$scope.model.fieldData.some(function(x, index) {
if (x.Id === fieldId) {
result = index;
return true;
}
});
return result;
}
Array#some stops as of the first iteration that returns a truthy value, so we'll stop searching the first time we find a match.
You cannot return directly the index but you can set the 'thisArg' parameter and set data inside it. This is cleaner than a global variable.
var data = {
indexes: []
};
var myArray = [1,2,3,4,5,6,7,8,9,10];
myArray.filter(function (element, index) {
if (element%2 == 0) {
this.indexes.push(index);
return true;
}
}, data);
console.log(data.indexes); // [1, 3, 5, 7, 9]
data.indexes.forEach(function(value) {
console.log(myArray[value]);
}); // 2, 4, 6, 8, 10
Use the findIndex method - Array.prototype.findIndex().
When the condition is met first, the index is returned.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
You most definitely can (ahem, guy who got accepted answer)
const polyfillter = (arr,condition) => arr.map( (e,i) => condition ? i : -1).filter(e=>e>=0)
Pass in an array, and your condition, and get back matching keys.
example
mynewstuff = polyfillter(myoldcrud, "e.length > 5")
Some languages can map a collection into an indexed collection where each element is mapped to a pair of {element, index}. That way you can map/filter/etc using any of those two values.
For example, Kotlin has withIndex and Swift has enumerated.
Javascript doesn't have that method, AFAIK. But you can easily build yours or use a workaround.
Workaround (not recommended)
// Turn an array of elements into an array of {value, index}
const indexedArray = array.map((v,i) => ({value:v, index:i}));
// Now I can filter by the elem but keep the index in the result
const found = array.filter(x => x.value === someValue)[0];
if (found) {
console.log(`${found.value} found at index ${found.index}`);
}
// One-liner for the question scenario, using some new js features.
// Note that this will fail at the last ".i" if the object is not found.
const index = fieldData.map((v,i) => ({v,i})).filter(x => x.v.id == fieldId)[0].i
Add a withIndex method to Array (recommended):
This is basically the same as the workaround, but creating a reusable function, which makes it much cleaner.
Array.prototype.withIndex = function() {
return this.map((v,i) => ({value: v, index: i}))
};
// Now the one-liner would be:
const index = fieldData.withIndex().filter(x => x.value.id == fieldId)[0].index;
// Better, with null checking:
const found = fieldData.withIndex().filter(x => x.value.id == fieldId)[0];
if (found) {
console.log(`${found.value} found at index ${found.index}`);
}
Try flatMap(), with or without i.
[5, 12, 8, 130, 44].flatMap((e, i) => (e > 13 ? e : [])); // returns [130, 44]
If there is only one object returned you can simply use the original array to refer to the returned object.
Since filter returns an array, you can do as follows
$scope.indexOfField = function(fieldId) {
var filteredArray = $scope.model.fieldData.filter(function(x) {
return x.Id === fieldId
});
var desiredObject = filteredArray[0]
return $scope.model.fieldData.indexOf(desiredObject);
}
['ab', 'cd', 'ef', 'id', 'junk', 'dummy','name'].map((x, ndx)=>['id', 'name'].includes(x)?ndx:'').filter(e=>e)
result: (2) [3, 6]
Filter will not return the index, but you can do something like this.
$scope.indexOfField = function(fieldId) {
$scope.model.fieldData.filter(function(x, i) {
if (x.Id === fieldId) {
var indexOfField = i;
}
});
return indexOfField;
};
$scope.indexOfField = function(fieldId) {
let index;
$scope.model.fieldData.filter((x, i) => {
if (x.Id === fieldId) index = i;
return x.Id === fieldId;
});
return index;
}
function modifyArray(nums) {
let newNums = nums.filter((num,index) => {
return num = num % 2 ? num * 3 : num * 2;
})
return newNums;
}
Here the index is the increment value you are looking for.