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)
Related
Here's a snippet of my JSON object (EDITED) -
[{
"traditional": "%",
"simplified": "%",
"referencedTraditional": [],
"referencedSimplified": [],
"pinyinNumeric": "pa1",
"pinyinDiacritic": "pā",
"definitions": [
"percent (Tw)"
],
"definitionsDiacritic": [
"percent (Tw)"
]
},
{
"traditional":"龠","simplified":"龠","referencedTraditional":[{"cp":"9fa0","c":"龠"}],"referencedSimplified":[{"cp":"9fa0","c":"龠"}],"pinyinNumeric":"yue4","pinyinDiacritic":"yuè","definitions":["ancient unit of volume (half a 合[ge3], equivalent to 50ml)","ancient flute"],"definitionsDiacritic":["ancient unit of volume (half a 合[gě], equivalent to 50ml)","ancient flute"]},
{"traditional":"龡","simplified":"龡","referencedTraditional":[{"cp":"9fa1","c":"龡"}],"referencedSimplified":[{"cp":"9fa1","c":"龡"}],"pinyinNumeric":"chui4","pinyinDiacritic":"chuì","definitions":["to blow (a flute)","archaic version of 吹"],"definitionsDiacritic":["to blow (a flute)","archaic version of 吹"]},
]
I want to convert every key called definitions from an array into a string.
E.g. From ["percent (Tw)"] to "percent (Tw)". Basically, I don't want the array brackets.
So far, I've tried looping through the file and converting every "definitions" key with JSON.stringify() or toString() -
translations.forEach(key => JSON.stringify((key.definitions)))
However, nothing changes in the outputted file.
Issue with the code is the one below
translations.forEach(key => JSON.stringify((key.definitions)))
Simply running this statement will not update the value for the key inside that object. You have to update the object for this.
Logic.
Loop through the keys in the object translations. I used Object.entries(translations).forEach for that.
Check each key, whether it includes "definitions" in that key.
If the key has "definitions" in that, run your stringification logic. I prefer Array.join() compared to JSON.stringify.
Update the key of that object.
Working Fiddle
const translations = {
"traditional": "%",
"simplified": "%",
"referencedTraditional": [],
"referencedSimplified": [],
"pinyinNumeric": "pa1",
"pinyinDiacritic": "pā",
"definitions": [
"percent (Tw)"
],
"definitionsDiacritic": [
"percent (Tw)"
]
}
Object.entries(translations).forEach(([key, value]) => key.includes('definitions') ? translations[key] = translations[key].join("") : null);
console.log(translations);
const obj = {
traditional: '%',
simplified: '%',
referencedTraditional: [],
referencedSimplified: [],
pinyinNumeric: 'pa1',
pinyinDiacritic: 'pā',
definitions: ['percent (Tw)'],
definitionsDiacritic: ['percent (Tw)'],
};
const returnStringDefinitions = (obj) => {
const arr = [];
for (const [key, value] of Object.entries(obj)) {
if (key === 'definitions') arr.push(value.toString());
}
return arr;
};
console.log(returnStringDefinitions(obj));
I think what may be happening for you is that you aren't saving the resulting array anywhere. The approach could be to create a new object.
const obj = {
traditional: '%',
simplified: '%',
referencedTraditional: [],
referencedSimplified: [],
pinyinNumeric: 'pa1',
pinyinDiacritic: 'pā',
definitions: ['percent (Tw)'],
definitionsDiacritic: ['percent (Tw)'],
};
const output = Object.keys(obj).reduce((accumulator, currentKey) => {
const currentValue = obj[currentKey];
if (currentKey === 'definitions' && Array.isArray(currentValue)) {
accumulator[currentKey] = currentValue.join('');
} else {
accumulator[currentKey] = currentValue;
}
return accumulator;
}, {})
console.log(output);
This solution checks if it is an array, and also just joins everything in the array just in case there are multiple things in the array, but how this is handled is really up to you :)
So I am pretty new when it comes to Javascript and it is as simple as read a json list with a value of:
{
"URL": [{
"https://testing.com/en/p/-12332423/": "999"
}, {
"https://testing.com/en/p/-123456/": "123"
},
{
"https://testing.com/en/p/-456436346/": "422"
}
]
}
What I would like to do is to have both the URL and the amount of numbers etc
"https://testing.com/en/p/-12332423/" and "999"
and I would like to for loop so it runs each "site" one by one so the first loop should be
"https://testing.com/en/p/-12332423/" and "999"
second loop should be:
"https://testing.com/en/p/-123456/" and "123"
and so on depending on whats inside the json basically.
So my question is how am I able to loop it so I can use those values for each loop?
As Adam Orlov pointed out in the coment, Object.entries() can be very useful here.
const URLobj = {
"URL": [{
"https://testing.com/en/p/-12332423/": "999"
}, {
"https://testing.com/en/p/-123456/": "123"
},
{
"https://testing.com/en/p/-456436346/": "422"
}
]
};
URLobj.URL.forEach(ob => {
console.log('ob', ob);
const entries = Object.entries(ob)[0]; // 0 just means the first key-value pair, but because each object has only one we can just use the first one
const url = entries[0];
const number = entries[1];
console.log('url', url);
console.log('number', number);
})
You mean something like this using Object.entries
const data = {
"URL": [
{"https://testing.com/en/p/-12332423/": "999"},
{"https://testing.com/en/p/-123456/": "123"},
{"https://testing.com/en/p/-456436346/": "422"}
]
}
data.URL.forEach(obj => { // loop
const [url, num] = Object.entries(obj)[0]; // grab the key and value from each entry - note the [0]
console.log("Url",url,"Number", num); // do something with them
})
let's call your object o1 for simplicity. So you can really go to town with this link - https://zellwk.com/blog/looping-through-js-objects/
or you can just use this code :
for(var i = 0; i < o1.URL.length; i++) {
//each entry
var site = Object.keys(URL[i]) [0];
var value = Object.values(URL[i]) [0];
// ... do whatever
}
don't forget each member of the array is an object (key : value) in its own right
You can extract the keys and their values into another object array using map
Then use the for loop on the newly created array. You can use this method on any object to separate their keys and values into another object array.
const data = {
"URL": [{
"https://testing.com/en/p/-12332423/": "999"
}, {
"https://testing.com/en/p/-123456/": "123"
},
{
"https://testing.com/en/p/-456436346/": "422"
}
]
}
var extracted = data.URL.map(e => ({
url: Object.keys(e)[0],
number: Object.values(e)[0]
}))
extracted.forEach((e) => console.log(e))
Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.
Take for example this extremely simplified Array of Objects:
[
{
createID: '1'
// Many other properties...
},
{
createID: '1'
// Many other properties...
},
{
createID: '1'
// Many other properties...
},
{
createID: '37'
// Many other properties...
},
{
createID: '37'
// Many other properties...
},
{
createID: '2'
// Many other properties...
},
{
createID: '2'
// Many other properties...
},
{
createID: '14'
// Many other properties...
},
];
Given this Array I then use the objects createID property to create an Array of Arrays containing Objects [[{..},{..}], [{..}], ..n]. This final format is required by the current front end framework I am using (Angular v6).
To accomplish this task I use the following code, where tempArr is an array like the example array provided above.
let currentGroup: string = tempArr[0].createID;
let tempGrouped: any[] = [];
let childGroup: any[] = [];
tempArr.forEach(item => {
if (item.createID !== currentGroup) {
tempGrouped.push(childGroup);
childGroup = [];
currentGroup = item.createID;
}
childGroup.push(item);
});
tempGrouped.push(childGroup);
This code works fine. However, I can't help but believe there must be a more efficient and elegant way given the data to convert an Array of objects into an Array of Arrays containing objects.
UpdateIt is important to note that the createID's are only id's that signify which objects should be grouped together. Therefore, they do not need to be numerically ordered by createID. In addition, the objects do come from the server "grouped" with their sibling objects (same createID) as you can see in the given example array provided.
Your example has all identical IDs adjacent to each other. If that is guaranteed to always be the case, looping though and pushing to a new array is all you need. However if this isn't the case, your solution will fail to group items properly. In that case using a hash table will allow you to still group by ID with same asymptotic complexity.
You can group your objects into a hash table object with keys created from createdID. This will let you group everything efficiently. Then just take the objects from the hash table:
let arr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];
let o = arr.reduce((a, c) => {
(a[c.createID] || (a[c.createID] = [])).push(c)
return a
}, {} )
// o is a an object with createID keys pointing to arrays of grouped objects
// just take the values
console.log(Object.values(o))
Edit based on question edit
Since the objects will already be grouped, there's not a better way than looping through. If you want an option that doesn't add the temp arrays, you can still use reduce(), which is essentially the same as your current solution, but maybe a little more self contained:
let tempArr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];
let r = tempArr.reduce((a, c, i, self) => {
if (i === 0 || self[i-1].createID !== c.createID)
a.push([])
a[a.length - 1].push(c)
return a
}, [])
console.log(r)
Assuming that your array of data is stored into a variable called data:
const result = data.reduce((acc, current) => {
if (!acc.dictionary[current.createID]) {
const createIdArray = [];
acc.dictionary[current.createID] = createIdArray;
acc.array.push(createIdArray);
}
acc.dictionary[current.createID].push(current);
return acc;
}, {array: [], dictionary: {}}).array;
This way, you'll loop only once on data, and it's efficient as we don't use filter or find (which would go through the whole array again and again).
Here's the output:
[
[
{
createID: '1',
},
{
createID: '1',
},
{
createID: '1',
},
],
[
{
createID: '37',
},
{
createID: '37',
},
],
[
{
createID: '2',
},
{
createID: '2',
},
],
[
{
createID: '14',
},
],
];
Here's a running demo: https://stackblitz.com/edit/typescript-phbzug
Summary:
The dictionary is self contained within the reduce function which means that as soon as the reduce is done, it'll be garbage collected
Not relying on any external variables, easier to reason about and IMO a better practice
This solution is more robust (the array doesn't need to be sorted) for ~ the same number of lines as OP's answer
Clean: With the dictionary you know directly what you're accessing and it's really fast
you want to group by createID?
let grouped=tempArray
//first get uniq values
.filter((s,index)=>tempArray.findIndex(f=>f.createID==s.createID)==index)
//then, with the uniq values make a map
.map(seg=>{ //with each uniq value, create an object with two properties
return {
createID:seg.createID, //the key
items:tempArray.filter(s=>s.createID==seg.createID) //An array with the values
}
})
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>