im building an api which needs to find the longest path that matches a number in a very performant manner.
eg
// API Request
{
'number': '123456789'
}
// DATA
[
{
'prefix': '1',
'price': 30.5
},
{
'prefix': '123',
'price': 10.5
},
{
'prefix': '12345',
'price': 22.5
},
]
// API RESPONSE
{
'prefix': '12345',
'price': 22.5
},
As you can see from above the response should be the row with prefix of 12345 as it is the longest. please i need a bit of help in doing this. i have spent about 2 days now looking for a solution so i decided to come to stack overflow for answers. Thanks in advance!
You could do the following where you check each characters position in the prefix to figure out which data set is the best match.
const incoming = {
'number': '123456789'
}
const data = [{
'prefix': '1',
'price': 30.5
},
{
'prefix': '123',
'price': 10.5
},
{
'prefix': '12345',
'price': 22.5
}
];
let bestMatch = {
matchSuccess: 0,
data: data[0]
};
for (let i = 0; i < data.length; i++) {
let matchSuccess = 0;
for (var x = 0; x < data[i].prefix.length; x++) {
const c = data[i].prefix.charAt(x);
if (data[i].prefix.charAt(x) === incoming.number.charAt(x)) {
matchSuccess++;
}
}
if (matchSuccess > bestMatch.matchSuccess) {
bestMatch = {
matchSuccess,
data: data[i]
}
}
}
console.log(bestMatch);
From the above comment ...
"The OP is not looking for ... "the longest possible path from an array of objects" ... which hopefully not only to me means a result like ... 'data[2].prefix' ... for ... const incoming = { prefix: '123456789' }. The OP's provided incoming value even would not match anything due to the number: '123456789' key-value pair (btw. number being a string type) instead of prefix: '123456789' . I highly recommend to edit topic and description of the problem."
But what the OP actually might want is ... filter, from an array of objects, the very first object where any of the object entry's stringified values matches the stringified value of the API call in the longest possible way.
function collectItemOfBestCoveringEntryValue(collector, item) {
const { search = '', coverage = 0, result = null } = collector;
if (search !== '') {
const matchingValues = Object
// retrieve all of an item's values.
.values(item)
// filter any value which matches `search`.
.filter(value => {
value = String(value);
return ((
value !== ''
) && (
// it might even goe both ways ...
// ... `value` in `search` ...
search.includes(value) ||
// ... or `search` in `value`.
value.includes(search)
));
});
// retrieve the longest (stringified) value's length.
const bestCoverage = String(
matchingValues
.sort((a, b) => b.length - a.length)[0] ?? ''
).length;
if (bestCoverage > coverage) {
collector.coverage = bestCoverage;
collector.result = item;
}
}
return collector;
}
const serverSideData = [{
'prefix': '1',
'price': 30.5,
}, {
'prefix': '123',
'price': 10.5,
}, {
'prefix': '12345',
'price': 22.5,
}];
const apiRequest = {
value: '123456789',
// or even
// value: 123456789,
};
const apiResponse = serverSideData
.reduce(collectItemOfBestCoveringEntryValue, {
search: String(apiRequest.value),
result: null,
}).result;
console.log({ apiResponse });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Related
I want to get all the values that equal a certain number and count how many of each of the objects.
My code looks like this:
var countItems = {
"aa":"70",
"bb":"70",
"cc":"80",
"dd":"90",
"ee":"90",
"ff":"90"
}
Now what I want to do is count each on that is in the second half.
For example, there are two "70", one "80", and three 90. Then I can assign to variables:
var firstCounter = ?? // 2
var secondCounter = ?? // 1
var thirdCounter = ?? // 3
?? is I don't know what goes here.
If it was structed differently like the following, I could do it like this:
let firstCounter = 0;
for (let i = 0; i < countItems.length; i++) {
if (countItems[i].status === '70') firstCounter++;
}
let secondCounter = 0;
for (let i = 0; i < countItems.length; i++) {
if (countItems[i].status === '80') secondCounter++;
}
let thirdCounter = 0;
for (let i = 0; i < countItems.length; i++) {
if (countItems[i].status === '90') thirdCounter++;
}
But the thing is, my original code which is what I have is not structured like that, so I'm not sure how to adapt it.
How can I count the items in the original list (var countItems) so that I can find out how much each value is?
You could use Object.values(countItems) to get an array that looks like this: ["70","70","80","90","90","90"] then either use a for loop to conditionally increment whatever counters you want, or use something like Array.reduce or Array.filter to count the elements you need.
You could use reduce to create a counted hash map like so:
const countItems = [
{ data: 'aa', status: '70' },
{ data: 'bb', status: '70' },
{ data: 'cc', status: '80' },
{ data: 'dd', status: '90' },
{ data: 'ee', status: '90' },
{ data: 'ff', status: '90' },
];
const countedHash = countItems.reduce((acc, curr) => {
if (!acc[curr.status])
acc[curr.status] = 1
else
acc[curr.status] += 1
return acc
}, {})
/* print out the results */
console.log(countedHash)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
You can access object keys like this :
countItems["aa"] // it will return "70"
You can also loop on the object (if you want to do as you did in your example) :
for (const item in countItems) {
console.log(countItems[item])
if (countItems[item] == "70") firstCounter++;
}
Object.values() and reduce() are both the right ideas. Taken together...
var countItems = {
"aa":"70",
"bb":"70",
"cc":"80",
"dd":"90",
"ee":"90",
"ff":"90"
};
let counts = Object.values(countItems).reduce((acc, value) => {
if (!acc[value]) acc[value] = 0;
acc[value]++;
return acc;
}, {});
let [theFirstValue, theSecondValue, theThirdValue] = Object.values(counts)
console.log(theFirstValue, theSecondValue, theThirdValue);
const countItems = [
{ data: 'aa', status: '70' },
{ data: 'bb', status: '70' },
{ data: 'cc', status: '80' },
{ data: 'dd', status: '90' },
{ data: 'ee', status: '90' },
{ data: 'ff', status: '90' },
];
var countValues = Object.values(countItems);
let obj ={}
for(let val of countValues){
if(!obj[val.status]){
obj[val.status] = 1
}else{
obj[val.status] += 1
}
}
console.log(obj)
Suppose I have the following array of objects:
myArray = [ { id: 'first', date: '2020-11-30', percentage: 10 }, { id: 'second', date: '2020-10-30', percentage: 20 }, { id: 'first', date: '2020-09-30', percentage: 30 } ]
Basically my question is how to find all the id's that have the same values, then compare their dates to see which has a higher value(I am planning on converting the string with Date.parse) and finally check which has the greater percentage, and then assign a variable to the condition.
Not really sure how to go about it, but figures it looks something like the code below or not, thanks for the help in advance.
for (i = 0; i < myArray.length; i++) {
if (myArray.id[i] === myArray.id[i]) {
if (myArray.date[i] > myArray.date[i]) {
if (myArray.percentage[i] > myArray.percentage[i]) {
let stuff = stuff;
}
}
}
}
You need to remember objects with the id that you've seen earlier so you can compare them with the object you're looking at "now" in each loop iteration. A Map is a good way to do that in modern JavaScript, or an object created with Object.create(null) in ES5.
const lastSeen = new Map();
for (const entry of myArray) {
const {id, date, percentage} = entry;
const last = lastSeen.get(id);
if (last) {
if (date > last.date && percentage > last.percentage) {
// ...this entry is newer than the previous one with the matching ID
// Replace the previous one (and possibly do something with `stuff`?)
lastSeen.set(id, entry);
}
} else {
lastSeen.set(id, entry);
}
}
Live Example:
const myArray = [ { id: 'first', date: '2020-11-30', percentage: 10 }, { id: 'second', date: '2020-10-30', percentage: 20 }, { id: 'first', date: '2020-09-30', percentage: 30 } ];
const lastSeen = new Map()
for (const entry of myArray) {
const {id, date, percentage} = entry;
const last = lastSeen.get(id);
if (last) {
console.log(`Checking ${id} / ${date} / ${percentage}...`);
if (date > last.date && percentage > last.percentage) {
// ...this entry is newer than the previous one with the matching ID
// Replace the previous one (and possibly do something with `stuff`?)
console.log(`Replacing ${id}...`);
lastSeen.set(id, entry);
} else {
console.log(`Not replacing ${id}`);
}
} else {
console.log(`${id} is new, adding...`);
lastSeen.set(id, entry);
}
}
I haven't included setting stuff above because it's not clear what let stuff = stuff; in your original code was meant to do. You can find the latest ones per id in lastSeen or do something where indicated above to handle stuff.
In ES5-level code (but here in 2020 about to be 2021, I strongly recommend writing modern code and using a transpiler if you need to support obsolete environments):
var lastSeen = Object.create(null);
for (let i = 0; i < myArray.length; ++i) {
var entry = myArray[i];
var last = lastSeen[entry.id];
if (last) {
if (entry.date > last.date && entry.percentage > last.percentage) {
// ...this entry is newer than the previous one with the matching ID
// Replace the previous one (and possibly do something with `stuff`?)
lastSeen[entry.id] = entry;
}
} else {
lastSeen[entry.id] = entry;
}
}
Live Example:
const myArray = [ { id: 'first', date: '2020-11-30', percentage: 10 }, { id: 'second', date: '2020-10-30', percentage: 20 }, { id: 'first', date: '2020-09-30', percentage: 30 } ];
var lastSeen = Object.create(null);
for (let i = 0; i < myArray.length; ++i) {
var entry = myArray[i];
var last = lastSeen[entry.id];
if (last) {
console.log(`Checking ${entry.id} / ${entry.date} / ${entry.percentage}...`);
if (entry.date > last.date && entry.percentage > last.percentage) {
// ...this entry is newer than the previous one with the matching ID
// Replace the previous one (and possibly do something with `stuff`?)
console.log(`Replacing ${entry.id}...`);
lastSeen[entry.id] = entry;
} else {
console.log(`Not replacing ${entry.id}`);
}
} else {
console.log(`${entry.id} is new, adding...`);
lastSeen[entry.id] = entry;
}
}
You could reduce the array with an object and check if the key exist or if the wanted properties are greater.
const
data = [{ id: 'first', date: '2020-11-30', percentage: 10 }, { id: 'second', date: '2020-10-30', percentage: 20 }, { id: 'first', date: '2020-09-30', percentage: 30 }],
result = Object.values(data.reduce((r, o) => {
if (
!r[o.id] ||
r[o.id].date < o.date ||
r[o.id].date === o.date && r[o.id].percentage < o.percentage
) {
r[o.id] = o;
}
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
So I want to get the object by the id 1 in this object:
let users = {
'users': {
'user1': {
'id': '1',
'name': 'Brandon',
'DOB': '05/04/2000'
},
'user2': {
'id': '2',
'name': 'Jefferson',
'DOB': '05/19/2004'
}
}
}
and I want it to return the entire 'user1' array and log it, does anyone know how I could do this?
I looked all over stackoverflow, and docs, and couldn't find a way to do this. Could I get some help?
There are a few approaches, both of these should roughly achieve what you're looking for:
let users = {
'users': {
'user1': {
'id': '1',
'name': 'Brandon',
'DOB': '05/04/2000'
},
'user2': {
'id': '2',
'name': 'Jefferson',
'DOB': '05/19/2004'
}
}
}
const findUserById = (id) => {
const key = Object.keys(users.users).find(user => users.users[user].id === '1')
return users.users[key]
}
console.log(findUserById('1'))
let users = {
'users': {
'user1': {
'id': '1',
'name': 'Brandon',
'DOB': '05/04/2000'
},
'user2': {
'id': '2',
'name': 'Jefferson',
'DOB': '05/19/2004'
}
}
}
const findUserById = (id) => {
const [key, user] = Object.entries(users.users).find(([key, user]) => user.id === '1');
return user;
}
console.log(findUserById('1'))
While the answer by skovy is right and this is what you should be doing in an actual production setting, I would advise against applying it immediately in your situation.
Why? Your question shows that you first need to learn some basic principles any JavaScript programmer should have, that is:
How to iterate over contents of an object
The simplest method used to iterate over an object's keys is the for .. in loop. When iterating over an object's keys using the for .. in loop, the code inside the curly brackets will be executed once for every key of the object we are iterating.
let users = {
"user1": {
"id": 1
},
"user2": {
"id": 2
}
}
for (let key in users) {
console.log(key);
}
The above code will print:
user1
user2
Proceeding from that, it should be clear how to find the element we want:
let foundUser = null;
for (let key in users) {
if (users[key].id === 1) {
foundUser = users[key];
break;
}
}
// now found user is our user with id === 1 or null, if there was no such user
When not to do that
If you have a complex object which is a descendant of another object and don't want to iterate over inherited properties, you could instead get an array of current object's keys with Object.keys:
let users = {
"user1": {
"id": 1
},
"user2": {
"id": 2
}
}
const keys = Object.keys(users) // now this is an array containing just keys ['user1', 'user2'];
let foundUser = null;
// now you can iterate over the `keys` array using any method you like, e.g. normal for:
for (let i = 0; i < keys.length; i++) {
if (users[keys[i]].id === 1) {
foundUser = users[keys[i]];
break;
}
}
// or alternatively `for of`:
for (for key of keys) {
if (users[key].id === 1) {
foundUser = users[key];
break;
}
}
Other options
You could use Object.values to get an array containing all values of the object:
let users = {
"user1": {
"id": 1
},
"user2": {
"id": 2
}
}
const values = Object.values(users); // values: [{"id":1},{"id":2}]
You can now find the entry you want on your own:
let foundUser = null
for (let i = 0; i < values.length; i++) {
if (values[i].id === 1) {
foundUser = values[i];
break;
}
}
Or using the Array's find method:
let foundUser = values.find(user => user.id === 1);
// now foundUser contains the user with id === 1
Or, shorter and complete version:
let users = {
"user1": {
"id": 1
},
"user2": {
"id": 2
}
}
const foundUser = Object.values(users).find(user => user.id === 1);
// now foundUser is `{ id: 1 }`
Not a big fan of reinventing the wheel. We use object-scan for most of our data processing now. It's very handy when you can just use a tool for that kind of stuff. Just takes a moment to wrap your head around how to use it. Here is how it could answer your questions:
// const objectScan = require('object-scan');
const find = (id, data) => objectScan(['**.id'], {
abort: true,
rtn: 'parent',
filterFn: ({ value }) => value === id
})(data);
const users = { users: { user1: { id: '1', name: 'Brandon', DOB: '05/04/2000' }, user2: { id: '2', name: 'Jefferson', DOB: '05/19/2004' } } };
console.log(find('1', users));
// => { id: '1', name: 'Brandon', DOB: '05/04/2000' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
A simple for loop would do it:
let users = {
'users': {
'user1': {
'id': '1',
'name': 'Brandon',
'DOB': '05/04/2000'
},
'user2': {
'id': '2',
'name': 'Jefferson',
'DOB': '05/19/2004'
}
}
}
let desiredUser = {};
Object.keys(users.users).forEach((oneUser) => {
if(users.users[oneUser].id === "1")
desiredUser = users.users[oneUser];
});
console.log(desiredUser);
You can also use reduce for this... as well as if you wanted to return the "entire" object, you could do:
let USER_LIST = {
'users': {
'user1': {
'id': '1',
'name': 'Brandon',
'DOB': '05/04/2000'
},
'user2': {
'id': '2',
'name': 'Jefferson',
'DOB': '05/19/2004'
}
}
}
function findUserById(id){
return Object.entries(USER_LIST.users).reduce((a, [user, userData]) => {
userData.id == id ? a[user] = userData : '';
return a;
}, {});
}
console.log(findUserById(1));
I agree with the Answers. just a short and simple way is to use .find() method
//--data returned-----//
data = [{"Id":22,"Title":"Developer"},{"Id":45,"Title":"Admin"}]
fs.readFile('db.json','utf8', function(err,data){
var obj = JSON.parse(data);
console.log(obj);
var foundItem = obj.find(o=>o.Id==id);
console.log(foundItem);
});
This question already has answers here:
Merge property from an array of objects into another based on property value lodash
(5 answers)
Closed 4 years ago.
I have 2 array of objects
The first one called data:
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
]
and the second called subs:
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
]
In which I want to compare that if they have the same ID, the subs array will pass its name value to it and if it does not match that it puts a '-' in the data array, try this way:
data.forEach((d)=>{
subs.forEach((s)=>{
if(d.id === s.id){
d.subname = s.name;
}
else {
d.subname = '-';
}
});
});
But always assign the values with '-' as if it does not match any. What part am I doing wrong? Is there any other simpler way to do this? I would greatly appreciate your help.
The size of the subs array may vary.
It looks like you are not exiting the inner loop when a successful match is found.
In the first example where you are looking for a match for Piero, in your first iteration 1===1 and d.subname is correctly set to 'Temprano'. However, you then continue to compare the values- 1 !== 4 so Temprano is overwritten with '-', and 1 !== 7 so it is overwritten again.
An alternate approach:
data.forEach(d => {
const match = subs.find(s => s.id === d.id);
d.subname = match ? match.name : '-';});
I'd also recommend adding a case where you're not expecting to find a match, so you can see that it works in both cases!
https://codepen.io/anon/pen/MGGBLP?editors=0010
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
];
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
];
// by caching one of the arrays in an object, it reduces the run time to linear.
const obj = subs.reduce((acc, item) => {
acc[item.id] = item;
return acc;
})
data.forEach(d => {
if (d.id in obj) {
d.subname = obj[d.id].name;
} else {
d.subname = '-';
}
});
console.log(data);
You just need two lines for this:
var findIds = id => subs.find(findId => findId.id === id);
data.forEach(findId => Object.assign(findId, findIds(findId.id)));
Your data array object should now include the name property from it's respective id sharing object in subs array.
jsFiddle: https://jsfiddle.net/AndrewL64/9k1d3oj2/1/
I have this object data:
[ RowDataPacket {
id: 59,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 60,
steamid: '76561198220437096',
product_id: 24,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 250,
website: 'csgo500' },
RowDataPacket {
id: 61,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 62,
steamid: '76561198345348530',
product_id: 6,
status: 1,
date: 2017-12-18T20:05:55.000Z,
message: null,
name: 'wal gruche',
amount: 100,
website: 'csgoatse' }
Im trying to sort this data with steamid and website, i managed to sort this only by one value like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid] = [item];
}
});
My idea was to make two dimensional array but for some reason i cant do it like this:
var list = groupedOrders[item.steamid][item.website];
It throws me an error "Cant read property ... of undefined"
Now my code looks like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid][item.website];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid][item.website] = [item];
}
});
Do you have any ideas how to fix this errors?
The problem is that var list = groupedOrders[item.steamid][item.website] is actually saying:
var temp = groupedOrders[item.steamid];
var list = temp[item.website];
There is no entry at groupedOrders[item.steamid] and so line one sets temp to undefined. The second line tries to index into undefined which is an error.
You would have to split the code out and essentially do the whole one-key grouping twice:
var outerList = groupedOrders[item.steamid];
if (!outerList)
outerList = groupedOrders[item.steamid] = {};
var innerList = outerList[item.website];
if (innerList)
innerList.push(item);
else
outerList[item.website] = [item];
(I have not tested this code but it is the right shape.)
The following works by creating a recursive groupBy grouping function for each of the fields supplied as an argument.
These dynamically created groupBy functions are then invoked one by one, passing the result between, starting with the supplied data.
Each groupBy function instance creates an object and adds properties to it corresponding to the key values for the field being grouped.
By calling these groupBy functions successively, we create a progressively more nested tree of objects, with groups at each successive level marked as being groups using a symbol.
The final result is a nest (a tree!) of objects, with keys corresponding to the field used for indexing at that level.
Finally, we flatten the nest and the final order is visible.
const flatten = o => Object.values(o).reduce((acc, c) => (Array.isArray(c) ? [...acc, ...c] : typeof c === 'object' ? [...acc, ...flatten(c)] : [...acc, c]), []);
const flow = (...fns) => data => fns.reduce((acc, c) => c(acc), data);
const GROUP = Symbol('group');
const asGroup = (result = []) => ((result[GROUP] = true), result);
const isGroup = o => o[GROUP];
const groupBy = field => (data, key) =>
data.reduce((acc, c) =>
((key = c[field]), (acc[key] ?
(acc[key].push(c), acc) :
((acc[key] = asGroup([c])), acc))), {});
const recurse = (test) => (transform) => o =>
test(o)
? transform(o)
: Object.entries(o).reduce(
(acc, [k, v]) => (test(v) ?
((acc[k] = transform(v)), acc) :
((acc[k] = recurse(test)(transform)(v)), acc)), {});
const group = (...fields) => flow(...fields.map(flow(groupBy, recurse(isGroup))), flatten);
const rows = asGroup([
{
id: 0,
steamid: '2',
website: 'a'
},
{
id: 1,
steamid: '2',
website: 'b'
},
{
id: 2,
steamid: '2',
website: 'a'
},
{
id: 3,
steamid: '1',
website: 'b'
},
{
id: 4,
steamid: '0',
website: 'b'
}
]);
console.log(JSON.stringify(group('steamid', 'website')(rows), null, 2));