I want to find duplicated objects and add hasDuplicate: true property, but not the first one. Methods should be run after one element.
The example array
items: [
{
checked: false,
desc: "",
id: "396",
value: "Lorem",
},
{
checked: false,
desc: "",
id: "230",
value: "Lorem"
},
{
checked: false,
desc: "",
id: "396",
value: "Lorem",
hasDuplicate: true
},
{
checked: false,
desc: "",
id: "396",
value: "Lorem",
hasDuplicate: true
},
{
checked: false,
desc: "",
id: "230",
value: "Lorem",
hasDuplicate: true
},
]
What is an efficient way to detect duplicate items in an array with ES6?
Use Array.prototype.map() to traverse your array and check whether the hash (Object.entries() concatenated) is already seen:
const src = [{checked:false,desc:"",id:"396",value:"Lorem",},{checked:false,desc:"",id:"230",value:"Lorem"},{checked:false,desc:"",id:"396",value:"Lorem"},{checked:false,desc:"",id:"396",value:"Lorem"},{desc:"",id:"230",checked:false,value:"Lorem"}],
dedupe = (a, hashMap=[]) => a.map(o => {
const hash = Object
.entries(o)
.sort(([a],[b]) =>
a.localeCompare(b))
.flat()
.join('\ud8ff')
return !hashMap.includes(hash) ?
(hashMap.push(hash), o) :
{...o, hasDuplicate: true}
})
console.log(dedupe(src))
.as-console-wrapper{min-height:100%;}
isIdentical() method will compare two objects and returns true if they are identical.
const isIdentical = (obj1, obj2) => {
let flag = true;
Object.keys(obj1).forEach(key => {
if(obj1[key] !== obj2[key]){
flag = false;
return false;
}
});
return flag;
};
Now reduce method will help us loop through the array.
items.reduce((unique, item) => {
const index = unique.findIndex( u => isIdentical(u, item));
if(index < 0){
return [...unique, item];
} else {
item.hasDuplicate = true;
return unique;
}
}, []);
console.log(items);
This sets the hasDuplicate property of every duplicate item except the first one based on their id
items.forEach((d, i) => {
if(i == items.map(d => d.id).indexOf(d.id)) {
d.hasDuplicate = true;
}
})
Related
I am having an object that looks like this:
const obj = {
"cat1" : {
id: "1",
name: "Category1",
tiles: [{ tileName: "abc", searchable: true}, { tileName: "def", searchable: true}]
},
"cat2" : {
id: "2",
name: "Category2",
tiles: [{ tileName: "ab", searchable: true}, { tileName: "lmn", searchable: true}]
},
"cat3" : {
id: "3",
name: "Category3",
tiles: [{ tileName: "pqr", searchable: true}, { tileName: "", searchable: false}]
}
}
Based on the search input , I need to check if the search item is included in each object basically inside two fields. One is name and the other is tileName inside tile array ( should be searched only if that object has searchable true ). It should be searched across name and tiles array
When search is "ab", the output should be
const obj = {
"cat1" : {
id: "1",
name: "Category1",
tiles: [{ tileName: "abc", searchable: true}]
},
"cat2" : {
id: "2",
name: "Category2",
tiles: [{ tileName: "ab", searchable: true}]
},
}
Code that I tried
function handleSearch(search)
{
return Object.values(obj).map((item) => {
if(item["name"].toLowerCase().includes(item.toLowerCase()))
return item;
})
})
}
I would personally create a generic filterMap() helper that combines the functionalities of both filter() and map()
// Combines filter() and map(). If no value is returned from the callback
// function (undefined) then the value is removed from the result. If a
// non-undefined value is returned, then that value is used as the map
// value. Returns a new array with the filtered/mapped values.
//
// filterMap([1,2,3,4,5], (n) => { if (n % 2) return n * n })
// //=> [1,9,25]
//
function filterMap(iterable, fn) {
const filterMapped = [];
for (const item of iterable) {
const mapped = fn(item);
if (mapped === undefined) continue; // skip current iteration
filterMapped.push(mapped);
}
return filterMapped;
}
With the above helper defined you can get the desired functionality with relative ease
function search(searchString, object) {
const searchIn = (whole, part) => whole.toLowerCase().includes(part.toLowerCase());
return Object.fromEntries(
filterMap(Object.entries(object), ([key, { tiles, ...category }]) => {
// If the category name matches, return the whole category as is,
// without filtering the tiles.
if (searchIn(category.name, searchString)) {
return [key, { ...category, tiles }];
}
// If the category name did not match, filter the tiles.
const matchingTiles = tiles.filter((tile) => (
tile.searchable && searchIn(tile.tileName, searchString)
));
// If there are one or more matching tiles found, return the
// category with only the matching tiles.
if (matchingTiles.length) {
return [key, { ...category, tiles: matchingTiles }];
}
// If neither the category name nor one of the tiles matched,
// nothing (undefined) is returned, thus the entry is removed
// from the result.
})
);
}
function search(searchString, object) {
const searchIn = (whole, part) => whole.toLowerCase().includes(part.toLowerCase());
return Object.fromEntries(
filterMap(Object.entries(object), ([key, { tiles, ...category }]) => {
// If the category name matches, return the whole category as is,
// without filtering the tiles.
if (searchIn(category.name, searchString)) {
return [key, { ...category, tiles }];
}
// If the category name did not match, filter the tiles.
const matchingTiles = tiles.filter((tile) => (
tile.searchable && searchIn(tile.tileName, searchString)
));
// If there are one or more matching tiles found, return the
// category with only the matching tiles.
if (matchingTiles.length) {
return [key, { ...category, tiles: matchingTiles }];
}
// If neither the category name nor one of the tiles matched,
// nothing (undefined) is returned, thus the entry is removed
// from the result.
})
);
}
const obj = {
"cat1": {
id: "1",
name: "Category1",
tiles: [
{ tileName: "abc", searchable: true },
{ tileName: "def", searchable: true },
],
},
"cat2": {
id: "2",
name: "Category2",
tiles: [
{ tileName: "ab", searchable: true },
{ tileName: "lmn", searchable: true },
],
},
"cat3": {
id: "3",
name: "Category3",
tiles: [
{ tileName: "pqr", searchable: true },
{ tileName: "", searchable: false },
],
},
};
console.log('search "ab"', search("ab", obj));
console.log('search "3"', search("3", obj));
// helper
// Combines filter() and map(). If no value is returned from the callback
// function (undefined) then the value is removed from the result. If a
// non-undefined value is returned, then that value is used as the map
// value. Returns a new array with the filtered/mapped values.
//
// filterMap([1,2,3,4,5], (n) => { if (n % 2) return n * n })
// //=> [1,9,25]
//
function filterMap(iterable, fn) {
const filterMapped = [];
for (const item of iterable) {
const mapped = fn(item);
if (mapped === undefined) continue; // skip current iteration
filterMapped.push(mapped);
}
return filterMapped;
}
In the above code we use Object.entries() to convert the object to an array. The array is then transformed using our newly defined filterMap() method. Finally the resulting array is transformed back into an object using Object.fromEntries().
The code makes use of destructuring, the spread syntax in object literals, and the property definition shorthand.
For those using TypeScript, filterMap() should be defined like:
function filterMap<A, B>(iterable: Iterable<A>, fn: (item: A) => undefined | B): B[] {
const filterMapped: B[] = [];
for (const item of iterable) {
const mapped = fn(item);
if (mapped === undefined) continue; // skip current iteration
filterMapped.push(mapped);
}
return filterMapped;
}
The answer might still leave you with:
Not all code paths return a value. (7030)
In which case you must either explicitly return from the callback function.
// ...
// If neither the category name nor one of the tiles matched,
// nothing (undefined) is returned, thus the entry is removed
// from the result.
return; // <- explicit return at the end
})
Or alternatively set "noImplicitReturns": false in your TypeScript settings, to allow implicit returns.
i have an array A
const arrayA = [
{
id:a,
check:false
},
{
id:b,
check:false
},
{
id:c,
check:false
}
and an array B
const arrayB = [
{
id:a,
},
{
id:b,
}
]
and i want to check if arrayB is exist arrayA by id, then change check to true. Using lodash or js array methods
Hopefully I understood your question correctly but this is the solution I came up with.
arrayA.map((item) => ({ ...item, check: arrayB.some(({ id: idB }) => item.id === idB ) }))
You can use nested forEach loops, and check, if id matches then set check to true.
const arrayA = [{
id: "a",
check: false
},
{
id: "b",
check: false
},
{
id: "c",
check: false
}
]
const arrayB = [{
id: "a",
},
{
id: "b",
}
]
arrayB.forEach((b)=>{
arrayA.forEach((a)=>{
if(b.id == a.id){
a.check = true;
}
})
})
console.log(arrayA);
You could create an array containing the ids of arrayB and then check the objects in arrayA like
const arrayA = [
{
id: 'a',
check:false
},
{
id:'b',
check:false
},
{
id:'c',
check:false
} ];
const arrayB = [
{
id:'a',
},
{
id:'b',
}
];
const idsB = arrayB.map( obj => obj.id);
arrayA.forEach(obj => { if(idsB.indexOf(obj.id) > -1) obj.checked = true; } );
arrayA.forEach(obj => {console.log(JSON.stringify(obj))});
I could come up with this, which is not different than double loop, but may read easier.
arrayA.map((a) => {
a.check = arrayB.findIndex((b) => b.id === a.id) != -1;
return a;
});
Try this code it may help you
const arrayA = [
{id:'a',check:false},
{id:'b',check:false},
{id:'c',check:false}
]
const arrayB = [
{id:'a',},
{id:'b',}
]
arrayB.map(i => {
return i.check = arrayA.find(item => i.id == item.id)?.check;
});
console.log(arrayB)
I have an array of objects, I need to delete a complete object based on the id
Input :
filters: [
{
key: "status",
label: "En attente",
value: "waiting",
id: 0
},
{
key: "dateDue[min]",
label: "15/12/2019",
value: "15/12/2019",
id: 1
},
{
key: "dateDue[max]",
label: "02/02/2020",
value: "02/02/2020",
id: 2
},
{
key: "bien",
values: [
{
label: "Studio Bordeaux",
value: 36,
id: 3
},
{
label: "Studio 2",
value: 34,
id: 184
}
]
},
{
key: "type",
values: [
{
type: "receipts",
label: "Loyer",
value: "loyer",
id: 4
},
{
type: "receipts",
label: "APL",
value: "apl",
id: 5
},
{
type: "spending",
label: "taxes",
value: "taxes",
id: 6
}
]
}
]
So I created a removeItem method with the id that must be deleted in parameters
removeItem method :
removeItem = (e, id) => {
const { filters } = this.state;
const remove = _.reject(filters, el => {
if (!_.isEmpty(el.values)) {
return el.values.find(o => o.id === id);
}
if (_.isEmpty(el.values)) {
return el.id === id;
}
});
this.setState({
filters: remove
});
};
I use lodash to make my job easier and more specifically _.reject
My issue is the following :
I manage to correctly delete the classic objects for example
{
key: "status",
label: "En attente",
value: "waiting",
id: 0
}
but my method however does not work for objects of the following form
{
key: "bien",
values: [
{
label: "Studio Bordeaux",
value: 36,
id: 3
},
{
label: "Studio 2",
value: 34,
id: 184
}
]
},
currently the whole object is deleted and not only the object in the values array according to its id
Here is my codesandbox!
thank you in advance for your help
EDIT
I found a solution with lodash (compact), I share my solution here :
removeIdFromCollection = id => {
const { filters } = this.state;
const newFilters = [];
_.map(filters, filter => {
if (filter.values) {
const valuesTmp = _.compact(
_.map(filter.values, value => {
if (value.id !== id) return value;
})
);
if (!_.isEmpty(valuesTmp)) {
return newFilters.push({
key: filter.key,
values: valuesTmp
});
}
}
if (filter.id && filter.id !== id) return newFilters.push(filter);
});
return newFilters;
};
removeItem = id => e =>
this.setState({
filters: this.removeIdFromCollection(id)
});
The values false, null, 0, "", undefined, and NaN are removed with lodash compact (_.compact(array))
Here is my updated codesandbox
You will need to filter the filters array and each values separately. Below is a recursive function which will remove items with the given id from the filters array and from the values property.
PS. This example is not using Lodash as I think it is not needed in this case.
removeIdFromCollection = (collection, id) => {
return collection.filter(datum => {
if (Array.isArray(datum.values)) {
datum.values = this.removeIdFromCollection(datum.values, id);
}
return datum.id !== id;
});
}
removeItem = (e, id) => {
const { filters } = this.state;
this.setState({
filters: this.removeIdFromCollection(filters, id),
});
};
The problem would be the structure of the object. You'll need to refactor for that inconvenient array out of nowhere for uniformity:
// Example
filters: [
...
{
key: "type",
values: [
{
type: "receipts",
label: "Loyer",
value: "loyer",
id: 4
},
...
]
...
}
// could be
filters: [
...
{
key: "type-receipts",
label: "Loyer",
value: "loyer",
id: 4
}
...
]
Repeat the pattern on all of it so you could just use the native array filter like this:
const newFilters = filters.filter(v => v.id !== id);
this.setState({
filters: newFilters,
});
I found a solution with lodash, I share it with you here :
removeIdFromCollection = id => {
const { filters } = this.state;
const newFilters = [];
_.map(filters, filter => {
if (filter.values) {
const valuesTmp = _.compact(
_.map(filter.values, value => {
if (value.id !== id) return value;
})
);
if (!_.isEmpty(valuesTmp)) {
return newFilters.push({
key: filter.key,
values: valuesTmp
});
}
}
if (filter.id && filter.id !== id) return newFilters.push(filter);
});
return newFilters;
};
removeItem = id => e =>
this.setState({
filters: this.removeIdFromCollection(id)
});
Here is my updated codesandbox
I have an array of objects like below;
const arr1 = [
{"name": "System.Level" },
{"name": "System.Status" },
{"name": "System.Status:*" },
{"name": "System.Status:Rejected" },
{"name": "System.Status:Updated" }
]
I am trying to split name property and create an object. At the end I would like to create an object like;
{
"System.Level": true,
"System.Status": {
"*": true,
"Rejected": true,
"Updated": true
}
}
What I have done so far;
transform(element){
const transformed = element.split(/:/).reduce((previousValue, currentValue) => {
previousValue[currentValue] = true;
}, {});
console.log(transofrmed);
}
const transofrmed = arr1.foreEach(element => this.transform(element));
The output is;
{System.Level: true}
{System.Status: true}
{System.Status: true, *: true}
{System.Status: true, Rejected: true}
{System.Status: true, Updated: true}
It is close what I want to do but I should merge and give a key. How can I give first value as key in reduce method? Is it possible to merge objects have same key?
You could reduce the splitted keys adn check if the last level is reached, then assign true, otherwise take an existent object or a new one.
const
array = [{ name: "System.Level" }, { name: "System.Status" }, { name: "System.Status:*" }, { name: "System.Status:Rejected" }, { name: "System.Status:Updated" }],
object = array.reduce((r, { name }) => {
var path = name.split(':');
last = path.pop();
path.reduce((o, k) => o[k] = typeof o[k] === 'object' ? o[k] : {}, r)[last] = true;
return r;
}, {});
console.log(object);
Use Array.reduce() on the list of properties. After splitting the path by :, check if there is second part. If there is a second part assign an object. Use object spread on the previous values, because undefined or true values would be ignored, while object properties would be added. If there isn't a second part, assign true as value:
const array = [{ name: "System.Level" }, { name: "System.Status" }, { name: "System.Status:*" }, { name: "System.Status:Rejected" }, { name: "System.Status:Updated" }];
const createObject = (arr) =>
arr.reduce((r, { name }) => {
const [first, second] = name.split(':');
r[first] = second ? { ...r[first], [second]: true } : true;
return r;
}, {});
console.log(createObject(array));
This is my array:
[{
name: "Test",
skills: {
agile: true,
good: true
}
},
{
name: "Test 2",
skills: {
agile: false,
good: false
}
}]
I need to find the last element (his index) who has the skill good set to true. The only way I know to fix this is to use a for/if combination. Is there any other faster/optimal way to do it ?
Use filter:
const goodSkills = myArray.filter(x => x.skills.good)
Then get the last item:
goodSkills[goodSkills.length - 1]
Or if you only need the index, and we treat name as a unique key:
const lastGoodIndex = myArray.findIndex(x => x.name === goodSkills[goodSkills.length - 1].name)
You can then use lastGoodIndex for whatever nefarious purpose you have in mind.
Alternatively if name is not a unique key, I suggest just using forEach:
let lastGoodIndex;
myArray.forEach((x, i) => lastGoodIndex = x.skills.good ? i : lastGoodIndex);
console.log(lastGoodIndex);
The fastest way is to us a for loop:
var arr = [{
name: "Test",
skills: {
agile: true,
good: true
}
},
{
name: "Test 2",
skills: {
agile: false,
good: false
},
}]
function findLastGoodIndex(arr) {
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i].skills.good) {
return i;
}
}
}
console.log(findLastGoodIndex(arr));
Or if the list isn't that large you can combine reverse with findIndex:
arr.reverse().findIndex(x => x.skills.good));
You can do it in 3 rows:
var arr = [
{
name: "Test",
skills: {
agile: true,
good: true
}
},
{
name: "Test 2",
skills: {
agile: false,
good: false
},
}
]
var lastIndex;
arr.forEach(function(item, i) {
if(item.skills.good) lastIndex = i;
})
console.log(lastIndex);
If you want the index of last element that has good = true your best option is to use for/if (perhaps going backwards, i.e. i-- if you can guess where can you expect the good item?).
filter will find the item(s) too, and it also has index property, but generally it's slower than for/if.
forEach will also generally be slower than for/if.
edit:styling.
Since you need to search the last value, you should search in descending order. This would prevent any unnecessary iterations.
var data = [{
name: "Test",
skills: {
agile: true,
good: true
}
},
{
name: "Test 2",
skills: {
agile: false,
good: false
}
}
];
var i = data.length;
var result = null;
while (i--) {
if (data[i].skills.good) {
result = i;
break;
}
}
console.log(result);