Related
My usage will contain 6 different object types (some which contain double nested arrays), and any possibility of number of entries, on the condition that an given entry is unique.
These objects do not have a consistent unique identifier (a unique identifier is applied in backend on submission).
here is an example of what the array may look like (only 2 object types):
arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{timeline: "250", chars[{a},{b},{c}]},
{timeline: "220", chars[{d},{e},{f}]},
]
obj = {name:"aaa",time:22}
My intention is to gain a true or false based on if obj is inside arr
I have tried methods:
I was suggested this method & it errors: #<Object> is not a function
console.log(arr.find(obj))
I also found this suggestion but it will always return false even with the element present
console.log(arr.includes(object))
I tried this method myself, though it will always fail.
console.log(arr.filter((element, index) => element === obj)
With attempt 4, If I was to compare name, this would be insufficient as unique time would be ignored missing valid entries.
If I was to pass every field, this would also not work as each object may or may not have the field and cause error.
Its not really possible to manually pre-filter filter into distinct categories, as every time a new type is added it will need manually adding to the filter.
If there is a library which could do this that you know of, please let me know as that would be perfect. Otherwise any other suggestions (excluding separating arrays) Would be greatly appreciated.
Use arr.some() to check if the required object is present in the array.
To compare the objects, a simpler way is to Stringify both the Objects and compare them.
const arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{name: "aaa", chars: ["a", "b", "c"]},
{name: "bbb", chars: ["d", "e", "f"]},
]
const obj1 = {name:"aaa", time: 15}
const obj2 = {name:"aaa",chars: ["a", "b", "c"]}
console.log(arr.some((element) => JSON.stringify(element) === JSON.stringify(obj1))) // true
console.log(arr.some((element) => JSON.stringify(element) === JSON.stringify(obj2))) // true
Didn't give much thought on performance.
I didn't put much thought on performace here but this might help:
function checkObjectInArray(arr, obj) {
const res = arr.some((el) => deepEqual(el, obj));
console.log(res);
}
function deepEqual(obj1, obj2) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) return false;
for (let prop in obj1) {
if (!obj2.hasOwnProperty(prop) || obj2[prop] !== obj1[prop]) {
return false;
}
}
return true;
}
in your case you can use it like:
arr = [
{ name: "aaa", time: 15 },
{ name: "aaa", time: 22 },
{ timeline: "250", data: ["2", "3", "4"] },
{ timeline: "251", data: ["2", "3", "4"] }, // what is chars[{d},{e},{f}] ?!
];
obj = { name: "aaa", time: 22 };
checkObjectInArray(arr, obj);
Observation : arr is not a valid array. Nested chars is not containing a valid value.
Solution : You can simply achieve the requirement by Just converting the JSON object into a JSON string and by comparing.
This solution works fine as you are just trying to find a single object in the passed arr.
Live Demo :
const arr = [
{name:"aaa",time:15},
{name:"aaa",time:22},
{timeline: "250", chars: [{a: 1},{b: 2},{c: 3}]},
{timeline: "220", chars: [{d: 4},{e: 5},{f: 6}]},
];
const obj = {name:"aaa",time:22};
const res = JSON.stringify(arr).indexOf(JSON.stringify(obj)) !== -1 ? true : false;
console.log(res);
Given an array of objects named allItems which is pre-sorted, but cannot be sorted again from the information it contains - what is an alternative implementation to the reduce function below that will retain the sorted order of allItems?
The logic below will output:
[{ id: 'd' }, { id: 'a' }, { id: 'b' }]
The desired output is:
[{ id: 'a' }, { id: 'b' }, { id: 'd' }]
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const includedIds = ['d', 'a', 'b'];
// QUESTION: How to create the same output, but in the order they appear in allItems
const unsortedIncludedItems = includedIds.reduce((accumulator, id) => {
const found = allItems.find(n => n.id === id);
if (found) accumulator.push(found);
return accumulator;
}, [])
As mentioned in response to #Ben, simply reversing the logic is a deal breaker for performance reasons.
Instead of iterating over the includedIds (in the wrong order) and seeing whether you can find them in allItems, just iterate over allItems (which is the right order) and see whether you can find their ids in includedIds:
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const includedIds = ['d', 'a', 'b'];
const includedItems = allItems.filter(item => includedIds.includes(item.id));
The issue you have here is that your code reverses the list. You can simply push to the front of the list instead, and the original order will be maintained.
Unfortunately, pushing to the front of a list is slower, it's O(n) rather than O(1). It looks like Array.prototype.unshift is supposed to be faster, but it's still O(n) according to this blog. Assuming that the number of found elements is small you won't notice any performance issues. In that case, replace push with unshift like so:
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const includedIds = ['d', 'a', 'b'];
// QUESTION: How to create the same output, but in the order they appear in allItems
const unsortedIncludedItems = includedIds.reduce((accumulator, id) => {
const found = allItems.find(n => n.id === id);
if (found) accumulator.unshift(found);
return accumulator;
}, [])
Otherwise, these are your options:
Create a wrapper around this object that reverses the indexes rather than the array. This can be done with a function like this:
const getFromEnd = (arr, i) => arr[arr.length - 1 - i]
Note that this can be replaced with arr.at(-i) in new browser versions (last few months). This could be encapsulated within a class if you're feeling OOP inclined.
Remember to manually invert the indices wherever you use this array (this will be bug-prone, as you may forget to invert them)
Reverse the array. As shown in this fiddle, even with 10,000 elements, the performance is not bad. Assuming this isn't a hotpath or user-interactive code, I think that even 100,000 is probably fine.
Update
Example B will use the index of the input array to sort the filtered array.
Try .filter() and .include() to get the desired objects and then .sort() by each object's string value. See Example A.
Another way is to use .flatMap() and .include() to get an array of arrays.
// each index is from the original array
[ [15, {id: 'x'}], [0, {id: 'z'}], [8, {id: 'y'}] ]
Then use .sort() on each sub-array index.
[ [0, {id: 'z'}], [8, {id: 'y'}], [15, {id: 'x'}] ]
Finally, use .flatMap() once more to extract the objects and flatten the array of arrays into an array of objects.
[ {id: 'z'}, {id: 'y'}, {id: 'x'} ]
See Example B
Example A (sort by value)
const all = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const values = ['d', 'a', 'b'];
const sortByStringValue = (array, vArray, key) => array.filter(obj => vArray.includes(obj[key])).sort((a, b) => a[key].localeCompare(b[key]));
console.log(JSON.stringify(sortByStringValue(all, values, 'id')));
Example B (sort by index)
const all = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}, {id:'f'}];
const values = ['d', 'a', 'b'];
const alt = [{name:'Matt'}, {name:'Joe'}, {name:'Jane'}, {name:'Lynda'}, {name:'Shelly'}, {name:'Alice'}];
const filter = ['Shelly', 'Matt', 'Lynda'];
const sortByIndex = (array, vArray, key) => array.flatMap((obj, idx) => vArray.includes(obj[key]) ? [[idx, obj]] : []).sort((a, b) => a[0] - b[0]).flatMap(sub => [sub[1]]);
console.log(JSON.stringify(sortByIndex(all, values, 'id')));
console.log(JSON.stringify(sortByIndex(alt, filter, 'name')));
Why not just reverse the logic, Filter out the ids which not suppose to be includes.
// NOTE: allItems is pre-sorted, but lacks the information to re-sort it
const allItems = [
{ id: "a" },
{ id: "b" },
{ id: "c" },
{ id: "d" },
{ id: "e" },
{ id: "f" },
];
const includedIds = ["d", "a", "b"];
const findElms = (items, includedIds) => items.filter((n) => includedIds.includes(n.id))
console.log(findElms(allItems, includedIds));
I have multiple json5 files that I need to join in one object.
Example the main object that other files will join to:
/equations/mass-energy-equivalence.json5
{
name: 'Mass-energy equivalence',
expression: 'E=mc^{2}',
expressionIntern: '\\mag{E}=\\mag{m}\\const{c}^{2}',
description: '...',
categories: ['physics'],
units: [
'joule'
],
constants: [
'speed-of-light'
],
magnitudes: [
'energy', 'mass'
],
values: [
{ value: 1000, units: ['joule'] }
]
}
/magnitudes/energy.json5
{ name: 'Energy', symbol: 'E', slug: 'energy', units: ['joule'], description: '', ... }
So Magnitudes have units, i have to join units/joule with magnitudes/energy and finally to the first object.
/units/joule.json5
{ name: 'Joule', symbol: 'J', slug: 'joule', description: '', ... }
And so on.
I need to join: categories, units, constants and mangitudes. Just like MySQL join. Also magnitudes has units so they have to be joined too.
So I'm trying to do a function that gets an array of nested properties like this:
This is the input to the function:
const nestedProperties = [
'categories.slug',
'constants.slug.units.slug',
'magnitudes.slug',
'units.slug',
'variables.slug.units.slug',
'values.units.slug'
];
Needs to do this for all the nestedProperties. The final object will be the output:
https://i.stack.imgur.com/yat2V.jpg
I have a function that gets the data await getData() so I need this recursive function that will set data.categories = await getData('categories', slug: 'physics')
My idea is something like this... yet not finished.
getAllData(object, nestedProperties) {
nestedProperties.forEach(async (item) => {
const parts = item.split('.');
const size = parts.length;
// We need at least two parts to get the data.
if(size === 0) console.error('Invalid Path');
// Size is even so is a multiple of 2
// ex. categories.slug
if(size % 2 === 0) {
for(let i = 0; i < size - 2; i += 2) {
// path.property -> categories.slug
if(i == 0) {
let path = parts[i];
let property = parts[i + 1];
// If the path is in the data and is an array with items
// ex. data[categories]
if(Array.isArray(data[path]) && data[path].length > 0) {
// Iterate
for(let i = 0; i < data[path].length; i++) {
// path => 'categories',
// data[path] => 'physics'
data[path] = getData(path, data[path]);
// Recursive data[path] = getAllData(path, data[path])
}
} else {
data[path] = getAllData(path, data[path])
}
}
}
} else {
// Is odd so we need to do it a bit different
// ex. 'values.units.slug'
}
}
}
Been trying a lot but not success to get to the 2 or 3 nested property :[
Thanks a lot.
I'm afraid I don't have time right now to write up a more complete explanation of this, so I'll be brief. If I find time tomorrow, I'll add more explanation. It's not complete, and doesn't handle your 'constants.slug.units.slug', so it might really be way off. (I actually simply remove the slug nodes, as I don't make sense of them.)
getData is just a dummy, meant to allow us to run something like your getData above. (Is this the equivalent of $content from your code?).
last is a trivial helper getting the last element of an array.
getPath takes a path such as ['foo', 1' 'bar'] and an object such as {foo: [{bar: 1, baz: 2}, {bar: 3, baz: 4}], qux: 5} and returns 3, the value of the bar property of the element at index 1 of the foo property of our object.
setPath simply reverses this:
setPath (['foo', 1, 'bar']) (42) ({foo: [{bar: 1, baz: 2}, {bar: 3, baz: 4}], qux: 5})
//=> {foo: [{bar: 1, baz: 2}, {bar: 42, baz: 4}], qux: 5}
fullPaths is more complex. It deals with the fact that you have fields that might be arrays or might be scalar values. It takes a path such as ['magnitudes'] and your initial data and finds the paths in the format required by getPath and setPath. Thus
fullPaths (['magnitudes']) (rawData) //=> [["magnitudes", 0], ["magnitudes", 1]]
which in turn point to 'energy' and 'mass', respectively.
With these helpers in place, we can write getAllData.
That uses fullPaths after taking your nestedProperties and turning them to arrays, removing the 'slug' substrings. With these results, we can dig into, say values.units.slug to get ['values', 0, 'units', 0], which maps to 'joules', and using 'units' and 'joules' we call getData.
After the Promises return resolve, we can fold over the results, calling such things as setPath (['values', 0, 'units', 0], promiseResult, accumulator). We return the result of that fold.
I don't know if I'll have much time to come back to this, but in case I do, I'd love to hear how close this is to your requirements. It's not clear to me for instance if you need to run those same getAllData over each result returned from getData, and if you do, whether the same nestedProperties are to be used for them.
I also don't know how to deal with the constants.slug.units.slug, as our constants are string values and don't have units.
const last = (xs) =>
xs [xs .length - 1]
const getPath = ([p, ...ps]) => (o) =>
p == undefined ? o : getPath (ps) (o && o[p])
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const fullPaths = ([p, ...ps]) => (o) =>
p == undefined
? [[]]
: Array .isArray (o)
? o .flatMap ((x, i) => fullPaths (ps) (x [p]) .map (ns => [p, i, ...ns]))
: Object (o) === o
? p in o
? Array .isArray (o [p])
? o [p] .map ((x, i) => fullPaths (ps) (x) .flatMap ((x) => [p, i, ...x]))
: fullPaths (ps) (o [p]) .map (x => [p, ...x])
: []
: [[p]]
const getAllData = (
rawData,
nestedProperties,
paths = nestedProperties .map (s => s.split ('.'))
.map (a => a.filter (s => s !== 'slug'))
.flatMap (p => fullPaths (p) (rawData))
) =>
Promise .all (
paths .map (
p => getData (
last (p .filter (s => String (s) === s)),
getPath (p) (rawData)
)
)
) .then (res => res .reduce (
(a, r, i) => setPath (paths[i]) (r) (a),
rawData
))
const rawData = {name: 'Mass-energy equivalence', expression: 'E=mc^{2}', expressionIntern: '\\mag{E}=\\mag{m}\\const{c}^{2}', description: '...', categories: ['physics'], units: ['joule'], constants: ['speed-of-light'], magnitudes: ['energy', 'mass'], values: [{value: 1000, units: ['joule']}]}
const nestedProperties = ['categories.slug', /*'constants.slug.units.slug',*/ 'magnitudes.slug', 'units.slug', 'variables.slug.units.slug', 'values.units.slug']
getAllData (rawData, nestedProperties )
.then ((r) => console .log(JSON.stringify(r, null, 4)))
.catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script> <!-- Dummy version of getData -->
const getData = ((data) => async (group, value) => group in data && value in data [group] ? Promise .resolve (data [group] [value]) : Promise .reject (`Cannot find ${group}/${value}`))({categories: {physics: {id: 2, name: "Physics", description: "Physics (from Ancient Greek: φυσική (ἐπιστήμη), romanized: physikḗ (epistḗmē), lit. 'knowledge of nature', from φύσις phýsis 'nature') is the natural science that studies matter, its motion and behavior through space and time, and the related entities of energy and force. Physics is one of the most fundamental scientific disciplines, and its main goal is to understand how the universe behaves."}, chemistry: {id: 3, name: "Chemistry", description: "Chemistry is the scientific discipline involved with elements and compounds composed of atoms, molecules and ions: their composition, structure, properties, behavior and the changes they undergo during a reaction with other substances."}}, units: {joule: {name: "Joule", symbol: {text: "J", html: "J", tex: "J"}, type: "si", categories: ["physics"], units: ["joule-per-kelvin", "joule-second"], description: "The joule (/dʒaʊl, dʒuːl/ jowl, jool) is a derived unit of energy in the International System of Units. It is equal to the energy transferred to (or work done on) an object when a force of one newton acts on that object in the direction of the force's motion through a distance of one metre (1 newton metre or $N⋅m$). It is also the energy dissipated as heat when an electric current of one ampere passes through a resistance of one ohm for one second. It is named after the English physicist James Prescott Joule (1818–1889)."}}, magnitudes: {energy: {name: 'Energy', symbol: {text: 'E', html: 'E', tex: 'E',}, categories: ['physics'], description: 'In physics, energy is the quantitative property that must be transferred to an object in order to perform work on, or to heat, the object. Energy is a conserved quantity; the law of conservation of energy states that energy can be converted in form, but not created or destroyed. The SI unit of energy is the joule, which is the energy transferred to an object by the work of moving it a distance of 1 metre against a force of 1 newton.', baseUnit: 'joule', units: ['joule']}, mass: {name: "Mass", symbol: {text: "m", html: "m", tex: "m"}, categories: ["physics"], description: "Property of matter to resist changes of the state of motion and to attract other bodies", baseUnit: "kilogram", units: ["tonne", "kilogram", "gram", "milligram", "microgram", "long-ton", "short-ton", "stone", "pound", "ounce"]}}, constants: {'speed-of-light': {name: "Speed of light in vacuum", symbol: {text: "c", html: "c", tex: "c"}, description: "The speed of light in vacuum, commonly denoted $c$, is a universal physical constant important in many areas of physics. Its exact value is defined as $299, 792, 458$ $m/s$ (approximately $300, 000$ $km/s$, or $18, 6000$ $mi/s$). It is exact because, by international agreement, a metre is defined as the length of the path travelled by light in vacuum during a time interval of $\\frac{1}{299, 792, 458}$ second. According to special relativity, $c$ is the upper limit for the speed at which conventional matter, energy or any information can travel through coordinate space.", categories: ["universal", "physics"], units: ["metre-per-second"], values: [{value: 299792458, units: "metre-per-second", exact: false, base: false}, {value: 3e8, units: "metre-per-second", exact: false}]}}})
</script>
I have this code in JS:
const Projects = [{fkTeamId: 'a'}, {fkTeamId: '8'}, {fkTeamId: 'c'}, {fkTeamId: 'a'}];
const Teams = [{TeamId: 'a'}, {TeamId: 'c'}, {TeamId: '8'}];
const output = Projects.filter(item1 => Teams.some(item2 => item2.TeamId === item1.fkTeamId))
console.log(output)
This is for return to people the number of projects that actually haves each Team,so if in this case, "TeamID: a" have 2 projects, so I need to know that these Team have 2 projects, I was thinking put it into an array an after use some like .length, but I'm not sure how can I do that.
At the moment the code returns:
Array [Object { fkTeamId: "a" }, Object { fkTeamId: "8" }, Object { fkTeamId: "c" }, Object { fkTeamId: "a" }]
So how can I make an count with .length for each Id and knows who length corresponds to each ID?
In other words I'm trying to get something like these:
const count = [[{fkTeamId: "a"},{fkTeamId: "a"}], [{fkTeamId: "8"}], [{fkTeamId: "c"}]];
console.log(count.forEach(Team => Team.length))
The intention of the previous code is to returns an array or something with:
count = [2, 1, 1]
well I began counting to be able to filter how I did at the beginning, returning the Object that counted is what you wanted.. OK :D
const Projects = [{fkTeamId: 'a'}, {fkTeamId: '8'}, {fkTeamId: 'c'}, {fkTeamId: 'a'}];
const Teams = [{TeamId: 'a'}, {TeamId: 'c'}, {TeamId: '8'}];
const output = Projects.filter(item1 => Teams.some(item2 => item2.TeamId === item1.fkTeamId))
var tempObj={}
var newOutput=
output.filter(a=>{
if(!tempObj[a.fkTeamId]){tempObj[a.fkTeamId]=1}
else{tempObj[a.fkTeamId]++}
return tempObj[a.fkTeamId]==1
})
//console.log(newOutput) //the new output without 2 teams of same name in the array
console.log(tempObj) //object that has the team names and how many times they occured
var tempArr=Object.keys(tempObj).map(a=>tempObj[a])
console.log(tempArr) //literally what you asked for in terms of expected result
var commentQuestionAnswer=
Object.keys(tempObj).map((a,i)=>{return {teamId:a,quantity:tempArr[i]} })
console.log(commentQuestionAnswer) //for the question u sent in a comment below
I am trying to pass a function that removes duplicates from an array. It should handle strings, object, integers as well. In my code so far I am showing that it will handle strings but nothing else. How can Imake this function universalto handle numbers,handle arrays,handle objects, and mixed types?
let unique = (a) => a.filter((el, i ,self) => self.indexOf(el) ===i);
In this function I hav unique() filtering to make a new array which checks the element and index in the array to check if duplicate. Any help would be appreciated.
i think the first you should do is to sort the array ( input to the function ). Sorting it makes all the array element to be ordered properly. for example if you have in an array [ 1, 3, 4, 'a', 'c', 'a'], sorting this will result to [ 1 , 3 , 4, 'a', 'a' , 'c' ], the next thing is to filter the returned array.
const unique = a => {
if ( ! Array.isArray(a) )
throw new Error(`${a} is not an array`);
let val = a.sort().filter( (value, idx, array) =>
array[++idx] != value
)
return val;
}
let array = [ 1 , 5, 3, 2, "d", "q", "b" , "d" ];
unique(array); // [1, 2, 3, 5, "b", "d", "q"]
let obj = { foo: "bar" };
let arraySize = array.length;
array[arraySize] = obj;
array[arraySize++] = "foo";
array[arraySize++] = "baz";
array[arraySize++] = obj;
unique(array); // [1, 2, 3, 5, {…}, "b", "baz", "d", "foo", "hi", "q"]
it also works for all types, but if you pass in an array literal with arrays or objects as one of its element this code will fail
unique( [ "a", 1 , 3 , "a", 3 , 3, { foo: "baz" }, { foo: "baz" } ] ); // it will not remove the duplicate of { foo: "baz" } , because they both have a different memory address
and you should also note that this code does not return the array in the same order it was passed in , this is as a result of the sort array method
Try using sets without generics. You can write a function as
Set returnUnique(Object array[]) {
Set set=new HashSet();
for (Object obj:array) {
set.add(obj);
}
return set;
}