I have an array of objects
Something like
const items: item[] = [
{id: 1, type: 'parent'},
{id: 2, type: 'parent'},
{id: 3, type: 'child'},
{id: 4, type: 'child'},
]
Now I want to make a function that if given an item of type child
it returns the first preceding row with type parent
.
I want to do this a more functional way for education purposes. I've done this imperatively as such (pseudo-code, but should work):
function getParentItem(item: item)
let startIndex = items.indexOf(item)
let item = null
while(!item){
if(items[startIndex].type == "parent"){
item = items[startIndex]
}
startIndex--
}
return item
}
getParentItem({id: 4, type:'child'}) //Should return {id: 2, type: 'parent'}
What would be a good, immutable, functional way to do this?
CodePudding user response:
First, ensure you don't recreate the elements of your array as you did in your example. Since you're changing the reference, the indexOf function will not be able to find it. A better approach is to use the findIndex
index, which receives a matcher function. It'll return the index of the first element this function resolves to true
.
Then, you can use findLast
, which does the same, but instead of returning for the first matching element index, it will search for the last matching element and return it instead of the index. Also, the matcher functions receive the index of the current checked element so that you can limit it.
const items = [
{id: 1, type: 'parent'},
{id: 2, type: 'parent'},
{id: 3, type: 'child'},
{id: 4, type: 'child'},
]
function getParentItem(elementId) {
const elementIndex = items.findIndex(el => el.id === elementId);
if (elementIndex === -1) return null;
const parentItem = items.findLast((el, index) => index < elementIndex && el.type === 'parent');
return parentItem || null;
}
console.log(getParentItem(3)) // { id: 2, type: parent }
console.log(getParentItem(4)) // { id: 2, type: parent }
console.log(getParentItem(5)) // null
console.log(getParentItem(2)) // { id: 1, type: parent }
console.log(getParentItem(1)) // null
Another approach may be more performant (haven't tested it), but you can write your find function, which will iterate fewer cycles than the findLast
function, and in fact, it will go backward from the current element.
function findReversed(array, fromIndex, matcherFunction) {
for (let i = fromIndex - 1; i >= 0; i--) {
const currentItem = array[i];
if (matcherFunction(currentItem)) {
return currentItem; // Note how we return the value of the function here, it will not proceed to other items.
}
}
}
And your final result will look something like that.
const items = [
{id: 1, type: 'parent'},
{id: 2, type: 'parent'},
{id: 3, type: 'child'},
{id: 4, type: 'child'},
]
function findReversed(array, fromIndex, matcherFunction) {
for (let i = fromIndex - 1; i >= 0; i--) {
const currentItem = array[i];
if (matcherFunction(currentItem)) {
return currentItem; // Note how we return the value of the function here, it will not proceed to other items.
}
}
}
function getParentItem(elementId) {
const elementIndex = items.findIndex(el => el.id === elementId);
if (elementIndex === -1) return null;
const parentItem = findReversed(
items,
elementIndex,
el => el.type === 'parent'
);
return parentItem || null;
}
console.log(getParentItem(3)) // { id: 2, type: parent }
console.log(getParentItem(4)) // { id: 2, type: parent }
console.log(getParentItem(5)) // null
console.log(getParentItem(2)) // { id: 1, type: parent }
console.log(getParentItem(1)) // null
CodePudding user response:
function getParentItem(item){
let startIndex = items.findIndex((ele,index)=>ele.id==item.id)
let obj=null;
while(obj==null && startIndex>=0){
if(items[startIndex].type=="parent"){
obj=items[startIndex]
break;
}else{
startIndex--
}
}
return obj
}
console.log(getParentItem({id: 4, type:'child'}))
Use this function .