Javascript doesn't sort list items properly - javascript

I have - array of objects - list items, I sort these items by fieldName. Normally it seems it works fine, but on some items it behaves strange and doesn't sort items properly.
Here is the code that I am making sorting:
elements.slice(0).sort((a, b) => {
if (a[fieldName] === '' || a[fieldName] == null) return 1;
if (b[fieldName] === '' || b[fieldName] == null) return -1;
return (
itemSort
? a[fieldName]?.toLowerCase() < b[fieldName]?.toLowerCase()
: a[fieldName]?.toLowerCase() > b[fieldName]?.toLowerCase()
)
? 1
: -1;
})
itemSort is a boolean and I decide to make A-Z or Z-A sorting.
Here is a picture from strange behaviour, I only see the wrong sorting on these items.
Here is an example of elements
[
{
icon: "IssueTracking"
id: "62a0868c2b2b180061ab05d8"
name: "[DEMO ASLC] All Issues"
type: "sheet"
updatedAt: "2022-12-05T15:17:23.072Z"
url: "/admin/documents/edit/62a0868c2b2b180061ab05d8"
},
{
icon: "..."
id: "..."
name: "..."
type: "..."
updatedAt: "..."
url: "..."
},
...
]

.sort() method modifies array itself, so you need to copy the array into a new array if you would like to keep your original array order in place.
const elementArray = [
{ name: "abc" },
{ name: "abb" },
{ name: "cc" },
{ name: "1bb" },
{ name: "4bc" },
{ name: "abb4" },
{ name: "" },
];
const sortItems = (elements, asc = true) => {
const sortedArray = [...elements];
sortedArray.sort((a, b) => {
let sortResult = a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1;
return asc ? sortResult : sortResult * -1
});
return sortedArray;
};
console.log(`descending: ${JSON.stringify(sortItems(elementArray, false))}`);
console.log(`ascending: ${JSON.stringify(sortItems(elementArray))}`);

One of the best way to do this is to use the Lodash sortBy method.
You can install this library with npm or yarn and simply do _.sortBy(elements, 'fieldName')

Related

Ordering based on array of objects

I have to define a property sort of an array, using based an array of other objects that have 2 properties called source and target, where the source is the first element and target will be the right next.
My current array is filled in this way:
[{"id":25075,"sort":1},{"id":25076,"sort":2},{"id":25077,"sort":null}]
But based on the source target that I have it should be like this
[{"id":25075,"sort":1},{"id":25076,"sort":3},{"id":25077,"sort":2}]
For a better understanding the source target I have is it:
[{"source":25075,"target":25077},{"source":25077,"target":25076}]
Does somebody know what would be the best way to handle it?
That's is what you are looking for ?
const array = [
{ source: 25075, target: 25077 },
{ source: 25077, target: 25076 },
];
const result = array.reduce((acc, { source, target }, index) => {
if (array.length && array.length > index + 1) {
return [...acc, { id: source, sort: index + 1 }];
} else if (array.length) {
return [
...acc,
{ id: source, sort: index + 1 },
{ id: target, sort: index + 2 },
];
}
return acc;
}, []);
console.log("result", result);
At the end we will have the { id: value, sort: position } array you are looking for ?
This code doesn't handle all the cases (with duplicate or other stuff ;))
I don't really understand your problem but is that solving the issue ?
const array = [
{ id: 25075, sort: 1 },
{ id: 25076, sort: 3 },
{ id: 25077, sort: 2 },
];
const result = [];
array.sort((a, b) => (a.sort < b.sort ? -1 : 1));
array.map((v, index) => {
if (array.length > index + 1) {
result.push({ source: v.id, target: array[index + 1].id });
}
});
console.log("result", result);
Instead of the map you can also use reduce and fill an accumulator.

Create unique values from duplicates in Javascript array of objects

I have an array of duplicated objects in Javascript. I want to create an array of unique objects by adding the index of occurrence of the individual value.
This is my initial data:
const array= [
{name:"A"},
{name:"A"},
{name:"A"},
{name:"B"},
{name:"B"},
{name:"C"},
{name:"C"},
];
This is expected end result:
const array= [
{name:"A-0"},
{name:"A-1"},
{name:"A-2"},
{name:"B-0"},
{name:"B-1"},
{name:"C-0"},
{name:"C-1"},
];
I feel like this should be fairly simple, but got stuck on it for a while. Can you please advise how I'd go about this? Also if possible, I need it efficient as the array can hold up to 1000 items.
EDIT: This is my solution, but I don't feel like it's very efficient.
const array = [
{ name: "A" },
{ name: "A" },
{ name: "C" },
{ name: "B" },
{ name: "A" },
{ name: "C" },
{ name: "B" },
];
const sortedArray = _.sortBy(array, 'name');
let previousItem = {
name: '',
counter: 0
};
const indexedArray = sortedArray.map((item) => {
if (item.name === previousItem.name) {
previousItem.counter += 1;
const name = `${item.name}-${previousItem.counter}`;
return { name };
} else {
previousItem = { name: item.name, counter: 0};
return item;
}
});
Currently you are sorting it first then looping over it, which may be not the most efficient solution.
I would suggest you to map over it with a helping object.
const a = [{name:"A"},{name:"A"},{name:"A"},{name:"B"},{name:"B"},{name:"C"},{name:"C"},], o = {};
const r = a.map(({ name }) => {
typeof o[name] === 'number' ? o[name]++ : o[name] = 0;
return { name: `${name}-${o[name]}` };
});
console.log(r);
Keep a counter, and if the current name changes, reset the counter.
This version mutates the objects. Not sure if you want a copy or not. You could potentially sort the array by object name first to ensure they are in order (if that's not already an existing precondition.)
const array = [
{ name: "A" },
{ name: "A" },
{ name: "A" },
{ name: "B" },
{ name: "B" },
{ name: "C" },
{ name: "C" },
];
let name, index;
for (let i in array) {
index = array[i].name == name ? index + 1 : 0;
name = array[i].name;
array[i].name += `-${index}`;
}
console.log(array);
Another way, if you don't want to sort, and don't want to mutate any objects, is to use a map and keep track of the current index for each object.
const array = [
// NOTE: I put the items in mixed up order.
{ name: "A" },
{ name: "C" },
{ name: "A" },
{ name: "B" },
{ name: "A" },
{ name: "C" },
{ name: "B" },
];
let index = {};
let next = name => index[name] = index[name] + 1 || 0;
let result = array.map(obj => ({ ...obj, name: obj.name + '-' + next(obj.name) }));
console.log(result);

Remove a particular item from array based on condition and return other items in javascript

I have an array with objects below
selectedOption = [
{
id: 'REPORTORDER',
name: 'reportOrder'
},
{
id: 'REORDER',
name: ‘reorder'
},
{
id: 'RETURNPRODUCTS',
name: 'returnProducts'
},
{
id: 'ENTERPO',
name: 'enterPo'
}
]
Based on a condition that if shipmentStatus is "CANCELLED" and entriesList is [] I want to remove the second item from the above array.
I have tried using filter method as written below:
selectedOption.filter(item => (shipmentStatus == "CANCELLED" && entriesList == []) ? item.name != "reorder" : item.name)
filter returns a new array, so you'll need to reassign selectedOption to the result of filter.
The conditional will always fail since entriesList will never equal a new empty array. It's better to check if the array is empty by comparing the length to zero.
It's better to check the global conditions outside the filter.
if (shipmentStatus === 'CANCELLED' && Array.isArray(entriesList) && entriesList.length === 0) {
selectedOption = selectedOption.filter(
(item) => item.name != 'reorder'
);
}
If I got you right you want to mutate array based on external condition, in that case, you must check condition outside filter like this
let condition = true;
let selectedOption = [{
id: 'REPORTORDER',
name: 'reportOrder'
},
{
id: 'REORDER',
name: 'reorder'
},
{
id: 'RETURNPRODUCTS',
name: 'returnProducts'
},
{
id: 'ENTERPO',
name: 'enterPo'
}
];
if (condition) selectedOption = selectedOption.filter(item => item.name != 'reorder')
console.log(selectedOption)
const selectedOption = [
{
id: 'REPORTORDER',
name: 'reportOrder',
},
{
id: 'REORDER',
name: 'reorder',
},
{
id: 'RETURNPRODUCTS',
name: 'returnProducts',
},
{
id: 'ENTERPO',
name: 'enterPo',
}];
const shipmentStatus = 'cancelled';
const entriesList = [];
console.log(selectedOption.filter((x) => (x.name !== 'reorder') || !(shipmentStatus === 'cancelled' && !entriesList.length)));

sort three items in array when in different order each time

I have an array containing objects which have a name and type.
I am trying to always sort the array so the objects are sorted by type as home , draw, away.
An example of one of the arrays looks like this. The arrays are sent from a backend and are sent in different orders each time. The names are also different each time.
var arr = [
{
type: 'home',
name: 'Liverpool Blues'
}, {
type: 'away',
name: 'Manchester Reds'
},
{
type: 'draw',
name: 'Draw'
}
];
My code looks like this. I thought that draw should get sorted to the middle if home is always pushed to the front, and away is always pushed to the end, although I think there must be an error with how I am sorting the array.
return [...selections].sort((a, b) => {
if (a.type === "HOME") return -1;
if (b.type === "AWAY") return 1;
return 0;
});
You could use an object, which groupes the type property by the wanted order.
var array = [{ type: 'home', name: 'Liverpool Blues' }, { type: 'away', name: 'Manchester Reds' }, { type: 'draw', name: 'Draw' }],
order = { home: 1, draw: 2, away: 3 };
array.sort(function (a, b) { return order[a.type] - order[b.type]; });
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could simply iterate and position them directly:
let arr = [];
selections.forEach(s => {
if (s.type === "HOME") arr[0] = s;
if (s.type === "DRAW") arr[1] = s;
if (s.type === "AWAY") arr[2] = s;
})
Then arr will be on the correct order.
The order you want is reverse alphabetical (home, draw, away), so reverse sorting on the type property will do:
var arr = [
{
type: 'home',
name: 'Liverpool Blues'
}, {
type: 'away',
name: 'Manchester Reds'
},
{
type: 'draw',
name: 'Draw'
}
];
arr.sort((a, b) => b.type.localeCompare(a.type));
console.log(arr);

How do I recursively use Array.prototype.find() while returning a single object?

The bigger problem I am trying to solve is, given this data:
var data = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4, children: [
{ id: 6 },
{ id: 7, children: [
{id: 8 },
{id: 9 }
]}
]},
{ id: 5 }
]
I want to make a function findById(data, id) that returns { id: id }. For example, findById(data, 8) should return { id: 8 }, and findById(data, 4) should return { id: 4, children: [...] }.
To implement this, I used Array.prototype.find recursively, but ran into trouble when the return keeps mashing the objects together. My implementation returns the path to the specific object.
For example, when I used findById(data, 8), it returns the path to { id: 8 }:
{ id: 4, children: [ { id: 6 }, { id: 7, children: [ { id: 8}, { id: 9] } ] }
Instead I would like it to simply return
{ id: 8 }
Implementation (Node.js v4.0.0)
jsfiddle
var data = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4, children: [
{ id: 6 },
{ id: 7, children: [
{id: 8 },
{id: 9 }
]}
]},
{ id: 5 }
]
function findById(arr, id) {
return arr.find(a => {
if (a.children && a.children.length > 0) {
return a.id === id ? true : findById(a.children, id)
} else {
return a.id === id
}
})
return a
}
console.log(findById(data, 8)) // Should return { id: 8 }
// Instead it returns the "path" block: (to reach 8, you go 4->7->8)
//
// { id: 4,
// children: [ { id: 6 }, { id: 7, children: [ {id: 8}, {id: 9] } ] }
The problem what you have, is the bubbling of the find. If the id is found inside the nested structure, the callback tries to returns the element, which is interpreted as true, the value for the find.
The find method executes the callback function once for each element present in the array until it finds one where callback returns a true value. [MDN]
Instead of find, I would suggest to use a recursive style for the search with a short circuit if found.
var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];
function findById(data, id) {
function iter(a) {
if (a.id === id) {
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
console.log(findById(data, 8));
Let's consider the implementation based on recursive calls:
function findById(tree, nodeId) {
for (let node of tree) {
if (node.id === nodeId) return node
if (node.children) {
let desiredNode = findById(node.children, nodeId)
if (desiredNode) return desiredNode
}
}
return false
}
Usage
var data = [
{ id: 1 }, { id: 2 }, { id: 3 },
{ id: 4, children: [
{ id: 6 },
{ id: 7,
children: [
{ id: 8 },
{ id: 9 }
]}]},
{ id: 5 }
]
findById(data, 7 ) // {id: 7, children: [{id: 8}, {id: 9}]}
findById(data, 5 ) // {id: 5}
findById(data, 9 ) // {id: 9}
findById(data, 11) // false
To simplify the picture, imagine that:
you are the monkey sitting on the top of a palm tree;
and searching for a ripe banana, going down the tree
you are in the end and searches aren't satisfied you;
come back to the top of the tree and start again from the next branch;
if you tried all bananas on the tree and no one is satisfied you, you just assert that ripe bananas don't grow on this this palm;
but if the banana was found you come back to the top and get pleasure of eating it.
Now let's try apply it to our recursive algorithm:
Start iteration from the top nodes (from the top of the tree);
Return the node if it was found in the iteration (if a banana is ripe);
Go deep until item is found or there will be nothing to deep. Hold the result of searches to the variable (hold the result of searches whether it is banana or just nothing and come back to the top);
Return the searches result variable if it contains the desired node (eat the banana if it is your find, otherwise just remember not to come back down by this branch);
Keep iteration if node wasn't found (if banana wasn't found keep testing other branches);
Return false if after all iterations the desired node wasn't found (assert that ripe bananas doesn't grow on this tree).
Keep learning recursion it seems not easy at the first time, but this technique allows you to solve daily issues in elegant way.
I would just use a regular loop and recursive style search:
function findById(data, id) {
for(var i = 0; i < data.length; i++) {
if (data[i].id === id) {
return data[i];
} else if (data[i].children && data[i].children.length && typeof data[i].children === "object") {
findById(data[i].children, id);
}
}
}
//findById(data, 4) => Object {id: 4, children: Array[2]}
//findById(data, 8) => Object {id: 8}
I know this is an old question, but as another answer recently revived it, I'll another version into the mix.
I would separate out the tree traversal and testing from the actual predicate that we want to test with. I believe that this makes for much cleaner code.
A reduce-based solution could look like this:
const nestedFind = (pred) => (xs) =>
xs .reduce (
(res, x) => res ? res : pred(x) ? x : nestedFind (pred) (x.children || []),
undefined
)
const findById = (testId) =>
nestedFind (({id}) => id == testId)
const data = [{id: 1}, {id: 2}, {id: 3}, {id: 4, children: [{id: 6}, {id: 7, children: [{id: 8}, {id: 9}]}]}, {id: 5}]
console .log (findById (8) (data))
console .log (findById (4) (data))
console .log (findById (42) (data))
.as-console-wrapper {min-height: 100% !important; top: 0}
There are ways we could replace that reduce with an iteration on our main list. Something like this would do the same:
const nestedFind = (pred) => ([x = undefined, ...xs]) =>
x == undefined
? undefined
: pred (x)
? x
: nestedFind (pred) (x.children || []) || nestedFind (pred) (xs)
And we could make that tail-recursive without much effort.
While we could fold the two functions into one in either of these, and achieve shorter code, I think the flexibility offered by nestedFind will make other similar problems easier. However, if you're interested, the first one might look like this:
const findById = (id) => (xs) =>
xs .reduce (
(res, x) => res ? res : x.id === id ? x : findById (id) (x.children || []),
undefined
)
const data = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{
id: 4,
children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }]
},
{ id: 5 }
];
// use Array.flatMap() and Optional chaining to find children
// then Filter undefined results
const findById = (id) => (arr) => {
if (!arr.length) return null;
return (
arr.find((obj) => obj.id === id) ||
findById(id)(arr.flatMap((el) => el?.children).filter(Boolean))
);
};
const findId = (id) => findById(id)(data);
console.log(findId(12)); /* null */
console.log(findId(8)); /* { id: 8 } */
Based on Purkhalo Alex solution,
I have made a modification to his function to be able to find the ID recursively based on a given dynamic property and returning whether the value you want to find or an array of indexes to recursively reach to the object or property afterwards.
This is like find and findIndex together through arrays of objects with nested arrays of objects in a given property.
findByIdRecursive(tree, nodeId, prop = '', byIndex = false, arr = []) {
for (let [index, node] of tree.entries()) {
if (node.id === nodeId) return byIndex ? [...arr, index] : node;
if (prop.length && node[prop].length) {
let found = this.findByIdRecursive(node[prop], nodeId, prop, byIndex, [
...arr,
index
]);
if (found) return found;
}
}
return false;
}
Now you can control the property and the type of finding and get the proper result.
This can be solved with reduce.
const foundItem = data.reduce(findById(8), null)
function findById (id) {
const searchFunc = (found, item) => {
const children = item.children || []
return found || (item.id === id ? item : children.reduce(searchFunc, null))
}
return searchFunc
}
You can recursively use Array.prototype.find() in combination with Array.prototype.flatMap()
const findById = (a, id, p = "children", u) =>
a.length ? a.find(o => o.id === id) || findById(a.flatMap(o => o[p] || []), id) : u;
const tree = [{id:1}, {id:2}, {id:3}, {id:4, children:[{id: 6}, {id:7, children:[{id:8}, {id:9}]}]}, {id:5}];
console.log(findById(tree, 9)); // {id:9}
console.log(findById(tree, 10)); // undefined
If one wanted to use Array.prototype.find this is the option I chose:
findById( my_big_array, id ) {
var result;
function recursiveFind( haystack_array, needle_id ) {
return haystack_array.find( element => {
if ( !Array.isArray( element ) ) {
if( element.id === needle_id ) {
result = element;
return true;
}
} else {
return recursiveFind( element, needle_id );
}
} );
}
recursiveFind( my_big_array, id );
return result;
}
You need the result variable, because without it, the function would return the top level element in the array that contains the result, instead of a reference to the deeply nested object containing the matching id, meaning you would need to then filter it out further.
Upon looking through the other answers, my approach seems very similar to Nina Scholz's but instead uses find() instead of some().
Here is a solution that is not the shortest, but divides the problem into recursive iteration and finding an item in an iterable (not necessarily an array).
You could define two generic functions:
deepIterator: a generator that traverses a forest in pre-order fashion
iFind: a finder, like Array#find, but that works on an iterable
function * deepIterator(iterable, children="children") {
if (!iterable?.[Symbol.iterator]) return;
for (let item of iterable) {
yield item;
yield * deepIterator(item?.[children], children);
}
}
function iFind(iterator, callback, thisArg) {
for (let item of iterator) if (callback.call(thisArg, item)) return item;
}
// Demo
var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];
console.log(iFind(deepIterator(data), ({id}) => id === 8));
In my opinion, if you want to search recursively by id, it is better to use an algorithm like this one:
function findById(data, id, prop = 'children', defaultValue = null) {
for (const item of data) {
if (item.id === id) {
return item;
}
if (Array.isArray(item[prop]) && item[prop].length) {
const element = this.findById(item[prop], id, prop, defaultValue);
if (element) {
return element;
}
}
}
return defaultValue;
}
findById(data, 2);
But I strongly suggest using a more flexible function, which can search by any key-value pair/pairs:
function findRecursive(data, keyvalues, prop = 'children', defaultValue = null, _keys = null) {
const keys = _keys || Object.keys(keyvalues);
for (const item of data) {
if (keys.every(key => item[key] === keyvalues[key])) {
return item;
}
if (Array.isArray(item[prop]) && item[prop].length) {
const element = this.findRecursive(item[prop], keyvalues, prop, defaultValue, keys);
if (element) {
return element;
}
}
}
return defaultValue;
}
findRecursive(data, {id: 2});
you can use this function:
If it finds the item so the item returns. But if it doesn't find the item, tries to find the item in sublist.
list: the main/root list
keyName: the key that you need to find the result up to it for example 'id'
keyValue: the value that must be searched
subListName: the name of 'child' array
callback: your callback function which you want to execute when item is found
function recursiveSearch(
list,
keyName = 'id',
keyValue,
subListName = 'children',
callback
) {
for (let i = 0; i < list.length; i++) {
const x = list[i]
if (x[keyName] === keyValue) {
if (callback) {
callback(list, keyName, keyValue, subListName, i)
}
return x
}
if (x[subListName] && x[subListName].length > 0) {
const item = this.recursiveSearch(
x[subListName],
keyName,
keyValue,
subListName,
callback
)
if (!item) continue
return item
}
}
},
Roko C. Buljan's solution, but more readable one:
function findById(data, id, prop = 'children', defaultValue = null) {
if (!data.length) {
return defaultValue;
}
return (
data.find(el => el.id === id) ||
findById(
data.flatMap(el => el[prop] || []),
id
)
);
}

Categories

Resources