Remove element from an array based on it's multiple peoperties - javascript

I've an array of elements as follows
entities
[
{
"name":"tiger",
"imageurl":"https://someurl.com",
"type":"animal"
},
{
"name":"cat",
"imageurl":"https://someurl.com",
"type":"animal"
},
{
"name":"parrot",
"imageurl":"https://someurl.com",
"type":"bird"
},{
"name":"potato",
"imageurl":"https://someurl.com",
"type":"vegetable"
},
{
"name":"orange",
"imageurl":"https://someurl.com",
"type":"fruit"
},
{
"name":"orange",
"imageurl":"https://someurl.com",
"type":"colour"
}
]
I've another array which is as follows
elemToRemove
[orange#fruit,cat#animal,tiger#animal]
I want to remove the elements having name=orange and type=fruit, name=cat and type=animal, name=tiger and type=animal
It is easily possible to remove the element based on single property by using filter over the array but in this case I am not able to put up map/filter/reduce to remove these elements.
I used split to create a name and type array and tried to do this but as we've type repeating the condition always returned false.
let nameArray = elemToRemove.map(function (elem) {
return elem.split('#')[0];
});
let typeArray= elemToRemove.map(function (elem) {
return elem.split('#')[1];
});
var reqData= entities.filter(function (obj) {
return (nameArray.indexOf(obj.name) === -1 && typeArray.indexOf(obj['env']) === -1);
});
Thus always giving me an empty reqData array. I do not have a provision to have an id or else I could've used id to delete the elements.
Expected Output
[
{
"name":"parrot",
"imageurl":"https://someurl.com",
"type":"bird"
},{
"name":"potato",
"imageurl":"https://someurl.com",
"type":"vegetable"
},
{
"name":"orange",
"imageurl":"https://someurl.com",
"type":"colour"
}
]
What is most elegant way to achieve this?

Map tends to be useful for these sorts of problems with the bonus of sublinear value retrieval.
// Input.
const input = [{"name":"tiger","imageurl":"https://someurl.com","type":"animal"},{"name":"cat","imageurl":"https://someurl.com","type":"animal"},{"name":"parrot","imageurl":"https://someurl.com","type":"bird"},{"name":"potato","imageurl":"https://someurl.com","type":"vegetable"},{"name":"orange","imageurl":"https://someurl.com","type":"fruit"},{"name":"orange","imageurl":"https://someurl.com","type":"colour"}]
// Tags.
const tags = ["orange#fruit", "cat#animal", "tiger#animal"]
// Clean.
const clean = (array, tags) => {
const map = new Map(array.map(x => [`${x.name}#${x.type}`, x])) // Create Map.
tags.forEach(tag => map.delete(tag)) // Remove each tag from Map.
return Array.from(map.values()) // Return Array from Map.values().
}
// Output.
const output = clean(input, tags)
// Proof.
console.log(output)

You can use array.filter:
var arr = [
{"name":"tiger","imageurl":"https://someurl.com","type":"animal"},
{"name":"cat","imageurl":"https://someurl.com","type":"animal"},
{"name":"parrot","imageurl":"https://someurl.com","type":"bird"},
{"name":"potato","imageurl":"https://someurl.com","type":"vegetable"},
{"name":"orange","imageurl":"https://someurl.com","type":"fruit"},
{"name":"orange","imageurl":"https://someurl.com","type":"colour"}
];
var toRemove = ['orange#fruit', 'cat#animal', 'tiger#animal'];
var filterOut = toRemove.map(e => {
var [name, type] = e.split('#');
return {name, type};
});
arr = arr.filter(e => !filterOut.find(({name, type}) => e.name === name && e.type === type));
console.log(arr);

You can use filter() to select only those objects which doesn't match desired criteria. We will test each object using .some() to find if there any match found between object and the array having strings to check.
let data = [{"name":"tiger", "imageurl":"https://someurl.com", "type":"animal"}, {"name":"cat", "imageurl":"https://someurl.com", "type":"animal"}, {"name":"parrot", "imageurl":"https://someurl.com", "type":"bird"}, { "name":"potato", "imageurl":"https://someurl.com", "type":"vegetable"}, { "name":"orange", "imageurl":"https://someurl.com", "type":"fruit"}, { "name":"orange", "imageurl":"https://someurl.com", "type":"colour"}];
let arr = ['orange#fruit', 'cat#animal', 'tiger#animal'];
let result = data.filter(o => !arr.some(s => (
[name, type] = s.split('#'),
o['name'] === name && o['type'] === type
)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Docs:
Array.prototype.filter()
Array.prototype.some()
String.prototype.split()

If your definition of elegant is to have the least possible code (to avoid human error), and reutilize elements that have been already created by others, I recommend using an external library like Lodash that already have a function to do this.
The first part if a bit complex since I'm parting from having a string:
[orange#fruit,cat#animal,tiger#animal]
that needs to be parsed, instead of having already an array of values like the other answers.
// First we need to convert the filter to a proper Json representation.
// This is needed since the _.remove function takes a Json object.
// This could be simplified if your filter string were already a
// Json object.
var filter = "[orange#fruit,cat#animal,tiger#animal]";
filter = filter.replace(/(\w+)#(\w+)[,\]]/g, (m, p1, p2, offset, string) => {
return `{"name":"${p1}","type":"${p2}"}${m.includes(']')?']':','}`;
});
filter = JSON.parse(filter);
// Next, apply the filter to the remove function from Lodash.
// Once you have a Json object, it's only two lines of code.
const rm = _.partial(_.remove, obj);
filter.forEach(rm)
var obj = [
{
"name":"tiger",
"imageurl":"https://someurl.com",
"type":"animal"
},
{
"name":"cat",
"imageurl":"https://someurl.com",
"type":"animal"
},
{
"name":"parrot",
"imageurl":"https://someurl.com",
"type":"bird"
},{
"name":"potato",
"imageurl":"https://someurl.com",
"type":"vegetable"
},
{
"name":"orange",
"imageurl":"https://someurl.com",
"type":"fruit"
},
{
"name":"orange",
"imageurl":"https://someurl.com",
"type":"colour"
}
];
// First we need to convert the filter to a proper Json representation.
// This is needed since the _.remove function takes a Json object.
// This could be simplified if your filter string were already a
// Json object.
var filter = "[orange#fruit,cat#animal,tiger#animal]";
filter = filter.replace(/(\w+)#(\w+)[,\]]/g, (m, p1, p2, offset, string) => {
return `{"name":"${p1}","type":"${p2}"}${m.includes(']')?']':','}`;
});
filter = JSON.parse(filter);
// Next, apply the filter to the remove function from Lodash.
// Once you have a Json object, it's only two lines of code.
const rm = _.partial(_.remove, obj);
filter.forEach(rm)
console.log(obj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>

Related

Can destructuring an array be used to map properties of each elements?

Suppose there is an array like this:
const a = [ {p:1}, {p:2}, {p:3} ];
Is it possible to destructure this array in order to obtain p = [1, 2, 3] ?
Because this does not work :
const [ ...{ p } ] = a; // no error, same as const p = a.p;
// p = undefined;
Edit
In response to all the answers saying that I need to use Array.prototype.map, I am aware of this. I was simply wondering if there was a way to map during the destructuring process, and the answer is : no, I need to destructure the array itself, then use map as a separate step.
For example:
const data = {
id: 123,
name: 'John',
attributes: [{ id:300, label:'attrA' }, { id:301, label:'attrB' }]
};
function format(data) {
const { id, name, attributes } = data;
const attr = attributes.map(({ label }) => label);
return { id, name, attr };
}
console.log( format(data) };
// { id:123, name:'John', attr:['attrA', 'attrB'] }
I was simply wondering if there was a way, directly during destructuring, without using map (and, respectfully, without the bloated lodash library), to retrive all label properties into an array of strings.
Honestly I think that what you are looking for doesn't exist, normally you would map the array to create a new array using values from properties. In this specific case it would be like this
const p = a.map(element => element.p)
Of course, there are some packages that have many utilities to help, like Lodash's map function with the 'property' iteratee
you can destructure the first item like this :
const [{ p }] = a;
but for getting all values you need to use .map
and the simplest way might be this :
const val = a.map(({p}) => p)
Here's a generalized solution that groups all properties into arrays, letting you destructure any property:
const group = (array) => array.reduce((acc,obj) => {
for(let [key,val] of Object.entries(obj)){
acc[key] ||= [];
acc[key].push(val)
}
return acc
}, {})
const ar = [ {p:1}, {p:2}, {p:3} ];
const {p} = group(ar)
console.log(p)
const ar2 = [{a:2,b:1},{a:5,b:4}, {c:1}]
const {a,b,c} = group(ar2)
console.log(a,b,c)

creating object from multiple array properties

I am looping through an object that contains multiple telephone numbers as keys and arrays with objects as values.
I have written a reduce method that groups all of the schedules together, except for one issue.
if you run the snippet you see that res is:
{trackingNumber: [ [Array] ]}
I need the object to look like:
{trackingNumber: [Array]}
The issue I continue to run into is trying to pop or slice or do anything by initial index makes the first array that is concatted (Object.values(res)) basically it enumerates the first object of that array as the first 7 elements of the value associated with tracking number.
{trackingNumber: [0:string, 1:string, 2:string, 3: {object of strings in 0 1 and 2}]}
Any help would be appreciated.
let todayNewTollFree = [
{paymentSchedule: [{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},],
tracking: "+18003160182"},
{paymentSchedule: [{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},],
tracking: "+18003160182"
},
{
paymentSchedule: [],
tracking: "+12134105385"
},
{
paymentSchedule: [{amount:500},{amount:500},{amount:500},{amount:500},],
tracking: "+18007084605"
},
{
paymentSchedule:[{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},],
tracking: "+18007100629"
}
]
let test = todayNewTollFree.reduce(function (res, obj) {
let key = obj.tracking;
if (res[key]) {
res[key] = res[key].map((key) =>
[key].flat().concat(obj.paymentSchedule)
);
} else res[key] = [obj.paymentSchedule];
for (const tracking in res) {
let values = Object.values(res[key]).flat();
console.log(tracking);
console.log(values);
}
return res;
}, {});
console.log(test)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The for in loop creates a flat array correctly but every attempt I have to go back and call or assign tracking to the newly created array isn't working.
When adding a new key to the object, you should not place it inside an array literal [...] (as that creates an array whose first element is an array) and should simply assign the array itself to the property. Furthermore, when adding to an array, Array#map is not necessary and Array#concat will do the job.
let todayNewTollFree = [ {paymentSchedule: [{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},], tracking: "+18003160182"}, {paymentSchedule: [{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},], tracking: "+18003160182" }, { paymentSchedule: [], tracking: "+12134105385" }, { paymentSchedule: [{amount:500},{amount:500},{amount:500},{amount:500},], tracking: "+18007084605" }, { paymentSchedule:[{amount:500},{amount:500},{amount:500},{amount:500},{amount:500},], tracking: "+18007100629" } ]
let test = todayNewTollFree.reduce(function (res, obj) {
let key = obj.tracking;
if (res[key]) {
res[key] = res[key].concat(obj.paymentSchedule)
} else res[key] = obj.paymentSchedule;
return res;
}, {});
console.log(test)

How to remove the repeating `place` name which contains the same `name`?

I find the following answer help me a lot in removing duplicate object array which contains duplicates.
I've made a fork of the example which I modified.
The function related:
const uniqueArray = things.thing.filter((thing,index) => {
return index === things.thing.findIndex(obj => {
return JSON.stringify(obj) === JSON.stringify(thing);
});
});
For example I have:
[
{"place":"here","name":"stuff"},
{"place":"there","name":"morestuff"},
{"place":"there","name":"morestuff"},
{"place":"herehere","name":"stuff"}
]
It would return:
[
{"place":"here","name":"stuff"},
{"place":"there","name":"morestuff"},
{"place":"herehere","name":"stuff"}
]
How to remove the repeating place name which contains the same name?
Expected output:
[
{"place":"here","name":"stuff"},
{"place":"there","name":"morestuff"}
]
You can reduce over the array of objects. Simply, if an object with a key value the same as the current object already exists in the accumulator, don't add it again.
Here's a function that allows you specify which key you want to dedupe:
const arr = [
{"place":"here","name":"stuff"},
{"place":"there","name":"morestuff"},
{"place":"there","name":"morestuff"},
{"place":"herehere","name":"stuff"}
];
// Accepts an array and a key that should have the
// duplicates removed
function remove(arr, key) {
// Iterate over the array passing in the accumulator
// and the current element
return arr.reduce((acc, c) => {
// If there is an object in the accumulator with the
// same key value as the current element simply return the
// accumulator
if (acc.find(obj => obj[key] === c[key])) return acc;
// Otherwise add the current element to the accumulator
// and return it
return acc.concat(c);
}, []);
}
function showJSON(arr, id) {
const json = JSON.stringify(arr, null, 2);
document.querySelector(`#${id} code`).textContent = json;
}
// remove duplicate places
showJSON(remove(arr, 'place'), 'places');
// remove duplicate names
showJSON(remove(arr, 'name'), 'names');
<div id="places">
Removed duplicate places
<pre><code></code></pre>
</div>
<div id="names">
Removed duplicate names
<pre><code></code></pre>
</div>
Check this
const things = [
{"place":"here","name":"stuff"},
{"place":"there","name":"morestuff"},
{"place":"there","name":"morestuff"},
{"place":"herehere","name":"stuff"}
]
const uniqueArray = things.reduce((accumulator, currentValue) => {
if (accumulator.find(a => a.name === currentValue.name))
return accumulator;
else
return (accumulator.push(currentValue), accumulator);
}, []);
Output
[ { place: 'here', name: 'stuff' },
{ place: 'there', name: 'morestuff' } ]
You can use array reduce with filter
let data=[
{"place":"here","name":"stuff"},
{"place":"there","name":"morestuff"},
{"place":"there","name":"morestuff"},
{"place":"herehere","name":"stuff"}
]
// Using reduce() to separate the contents we want
let result=data.reduce((acc,value)=>{
if(acc.filter(val=>val.name==value.name).length==0) // checking the accumulator if it already containsa the value
{
acc.push(value); // if the array returned is of length==0 we can push in it
}
return acc;
},[])
console.log(result);
See Array Filter, Array.prototype.Reduce

Reduce Object Array to Single Object [duplicate]

I've read this answer on SO to try and understand where I'm going wrong, but not quite getting there.
I have this function :
get() {
var result = {};
this.filters.forEach(filter => result[filter.name] = filter.value);
return result;
}
It turns this :
[
{ name: "Some", value: "20160608" }
]
To this :
{ Some: "20160608" }
And I thought, that is exactly what reduce is for, I have an array, and I want one single value at the end of it.
So I thought this :
this.filters.reduce((result, filter) => {
result[filter.name] = filter.value;
return result;
});
But that doesn't produce the correct result.
1) Can I use Reduce here?
2) Why does it not produce the correct result.
From my understanding, the first iteration the result would be an empty object of some description, but it is the array itself.
So how would you go about redefining that on the first iteration - these thoughts provoke the feeling that it isn't right in this situation!
Set initial value as object
this.filters = this.filters.reduce((result, filter) => {
result[filter.name] = filter.value;
return result;
},{});
//-^----------- here
var filters = [{
name: "Some",
value: "20160608"
}];
filters = filters.reduce((result, filter) => {
result[filter.name] = filter.value;
return result;
}, {});
console.log(filters);
var filters = [{
name: "Some",
value: "20160608"
}];
filters = filters.reduce((result, {name, value}= filter) => (result[name] = value, result), {});
console.log(filters);
Since 2019 (ES2019) you can go with Object.fromEntries() but you need to map to array first.
const filtersObject = Object.fromEntries(filters.map(({ name, value }) => [name, value])

How do I create a nested object structure from a path (array of keys)?

I am trying to make something that takes arrays of strings, and then builds chains of nested objects that basically store what strings come after what in the input arrays. Initially, these chains had depths of 2, but I need to be able to generate higher-depth chains.
Basically, I need to take an array like this:
["test1", "test2", "test3", "test4"]
and convert it into this:
{
"test1":
{
"test2":
{
"test3":
{
"test4": {}
}
}
}
}
This looks like a job for Array#reduce:
function objectFromPath (path) {
var result = {}
path.reduce(function (o, k) {
return (o[k] = {})
}, result)
return result
}
var path = ["test1", "test2", "test3", "test4"]
console.log(objectFromPath(path))
.as-console-wrapper { min-height: 100%; }
I wanted to solve a similar problem, but I wanted to set a value at the end of the path within the resulting object, and I wanted to provide an initial object. I started with gyre's function and added some extra magic to satisfy my use case.
// Creates a nested object from an array representing the path of keys
//
// Takes 2 optional arguments:
// - a value to set at the end of the path
// - an initial object
//
// Ex: let house = objectFromPath(['kitchen', 'fridge'], 'empty', {civic: 123})
// => house = {civic: 123, kitchen: {fridge: 'empty'}}
const objectFromPath = (path, value = {}, obj = {}) =>
{
path.reduce((result, key, i, source) =>
{
if(i === (source.length - 1))
{
return (result[key] = value)
}
else
{
return (result[key] = {})
}
},
obj
)
return obj;
}
// Demo: house = {civic: 123, kitchen: {fridge: 'empty'}}
console.log(
objectFromPath(
['kitchen', 'fridge'],
'empty',
{civic: 123}
)
)

Categories