So, I have this function that works to get a JSON object but I want to make it simplier so I created a function to get the values of the JSON object. Why doesn't it work?
var itemData = {
weapon: function () {
return {
1: {
'name': 'Dagger',
'extra_skill': 'none',
'cost': 500,
'attack': 5
},
2: {
'name': 'Pickaxe',
'extra_skill': 'mining',
'cost': 25,
'attack': 5
}
}
},
getWeapon: function (value, x) {
var obj = JSON.parse(value);
return itemData.weapon()[x].obj
}
}
// outputs: Dagger
console.log(itemData.weapon()[1].name)
// Get the name of weapon 1
// however, it outputs: Uncaught SyntaxError: Unexpected token a
console.log('Getting weapon... ' + itemData.getWeapon('name', 1))
What am I doing wrong?
You actually don't need JSON parsing at all to get this working, because there is nowhere where you have a JSON string that needs to be parsed.
Here is a working example:
var itemData = {
weapon: function () {
return [
{
'name': 'Dagger',
'extra_skill': 'none',
'cost': 500,
'attack': 5
},
{
'name': 'Pickaxe',
'extra_skill': 'mining',
'cost': 25,
'attack': 5
}
];
},
getWeapon: function (value, x) {
return itemData.weapon()[x][value];
}
}
// outputs: Dagger
console.log(itemData.weapon()[0].name)
// outputs: Getting weapon... Pickaxe
console.log('Getting weapon... ' + itemData.getWeapon('name', 1))
Related
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; }
I am working with Lucene Syntax (a AND b, c OR d) as a search query and I need to translate this search query. In order to translate the Lucene Syntax to a JavaScript object, I use Lucene Parser npm module (https://www.npmjs.com/package/lucene).
for an AND query my translation needs to happen like this:
Query: Hyatt AND Westin
Translated Lucene object:
{
"left":{
"field":"<implicit>",
"fieldLocation":null,
"term":"Hyatt",
"quoted":false,
"regex":false,
"termLocation":{
"start":{
"offset":0,
"line":1,
"column":1
},
"end":{
"offset":6,
"line":1,
"column":7
}
},
"similarity":null,
"boost":null,
"prefix":null
},
"operator":"AND",
"right":{
"field":"<implicit>",
"fieldLocation":null,
"term":"Westin",
"quoted":false,
"regex":false,
"termLocation":{
"start":{
"offset":10,
"line":1,
"column":11
},
"end":{
"offset":16,
"line":1,
"column":17
}
},
"similarity":null,
"boost":null,
"prefix":null
}
}
Translated Search Query for AND:
For an AND query, I get the below translation:
[
[
{
"col":"*",
"test":"Contains",
"value":"Hyatt"
},
{
"col":"*",
"test":"Contains",
"value":"Westin"
}
]
]
Translated Search Query for OR:
For an OR query, I get the below translation:
[
[
{
"col":"*",
"test":"Contains",
"value":"Hyatt"
}
],
[
{
"col":"*",
"test":"Contains",
"value":"Westin"
}
]
]
For this translation I have written the function (readExpression) which recursively traverses through the input(Translated Lucene Object):
result = [];
isFieldImplicit(node: Node): boolean {
return node.field === '<implicit>';
}
isFreeTextSearch(searchText: string): boolean {
// user RegExp for pattern matching of field.f1:abc pattern... i.e. alpha-num.alpha-num:any-char
return !this.isContainingLuceneReservedCharacters(searchText);
}
isAndOperator(expression): boolean {
return expression && expression.operator && expression.operator === 'AND';
}
isOrOperator(expression): boolean {
return expression && expression.operator && expression.operator === 'OR';
}
isQueryContainingQuotes(searchText: string): boolean {
const matches = searchText.match(/"/g);
const firstLastCharacterQuoteMatch = new RegExp(
/((?<![\\])['"])((?:.(?!(?<![\\])\1))*.?)\1/
);
const isFirstAndLastCharacterQuote = firstLastCharacterQuoteMatch.test(
searchText
);
if (!matches) {
return false;
}
if (/""/.test(searchText) && isFirstAndLastCharacterQuote) {
return true;
}
return isFirstAndLastCharacterQuote;
}
isQueryEndingWithStar(searchText: string): boolean {
return searchText.endsWith('*');
}
doesQueryContainsStar(searchText: string): boolean {
return searchText.indexOf('*') !== -1;
}
getTestFromQuery(keyword: string): string {
if (this.isQueryContainingQuotes(keyword)) {
return WHERE_CLAUSE_TESTS.CONTAINS;
} else if (this.isQueryEndingWithStar(keyword)) {
return WHERE_CLAUSE_TESTS.CONTAINS_ANY;
} else {
return WHERE_CLAUSE_TESTS.CONTAINS_ANY;
}
}
generateFreeTextClause(node: Node): WhereClause[] {
const searchText = node.quoted ? `"${node.term}"` : node.term;
const clause = [
{
col: '*',
test: this.getTestFromQuery(searchText as string),
value: (node.term as string).replace('*', '')
} as WhereClause
];
return clause;
}
generateFieldSearchClause(node: Node): WhereClause[] {
const searchText = node.quoted ? `"${node.term}"` : node.term;
const clause = [
{
col: node.field.split('.')[1],
test: this.getTestFromQuery(searchText as string),
value: (node.term as string).replace('*', '')
} as WhereClause
];
return clause;
}
readExpression(expression): any {
let left, right;
if (expression && expression.field) {
return expression;
}
if (this.isOrOperator(expression)) {
left = this.readExpression(expression.left);
right = this.readExpression(expression.right);
if (left) {
if (this.isFieldImplicit(left)) {
this.results.push(this.generateFreeTextClause(left));
} else {
this.results.push(this.generateFieldSearchClause(left));
}
}
if (right) {
if (this.isFieldImplicit(right)) {
this.results.push(this.generateFreeTextClause(right));
} else {
this.results.push(this.generateFieldSearchClause(right));
}
}
console.log(this.results);
return this.results;
}
if (this.isAndOperator(expression)) {
left = this.readExpression(expression.left);
right = this.readExpression(expression.right);
if (left) {
if (this.isFieldImplicit(left)) {
this.results.push(this.generateFreeTextClause(left)[0]);
} else {
this.results.push(this.generateFieldSearchClause(left)[0]);
}
}
if (right) {
if (this.isFieldImplicit(right)) {
this.results.push(this.generateFreeTextClause(right)[0]);
} else {
this.results.push(this.generateFieldSearchClause(right)[0]);
}
}
console.log(this.results);
return this.results;
}
}
It works fine for 1 level AND and OR but as soon as I have 2 level it fails.
Query: (Hyatt AND Westin) OR Orchid
I am expecting below translation:
[
[
{
"col":"*",
"test":"Contains",
"value":"Hyatt"
},
{
"col":"*",
"test":"Contains",
"value":"Westin"
}
],
[
{
"col":"*",
"test":"Contains",
"value":"Orchid"
}
]
]
In the recursive call, the leaf node of OR is getting ignored and not injected inside the array of results. Any suggestions are welcome.
Without trying to dig deeply into your code (see my comments on the question), the core of this should involve a straightforward recursion.
Here I get your output from what I think would be the relevant input:
const convert = (query) =>
'operator' in query
? [convert (query .left), convert (query .right)]
: {
col: '*',
test: 'Contains',
value: query .term
}
const query = {left: {left: {field: "<implicit>", fieldLocation: null, term: "Hyatt", quoted: false, regex: false, termLocation: {start: {offset: 2, line: 1, column: 3}, end: {offset: 8, line: 1, column: 9}}, similarity: null, boost: null, prefix: null}, operator: "AND", right: {field: "<implicit>", fieldLocation: null, term: "Westin", quoted: false, regex: false, termLocation: {start: {offset: 12, line: 1, column: 13}, end: {offset: 18, line: 1, column: 19}}, similarity: null, boost: null, prefix: null}, parenthesized: true}, operator: "OR", right: {field: "<implicit>", fieldLocation: null, term: "Orchid", quoted: false, regex: false, termLocation: {start: {offset: 23, line: 1, column: 24}, end: {offset: 29, line: 1, column: 30}}, similarity: null, boost: null, prefix: null}}
console .log (convert (query))
.as-console-wrapper {max-height: 100% !important; top: 0}
Of course I hard-code col: '*' and test: 'Contains'; you'd have to fill them in appropriately. And my test to see if we're at a branch or leaf ('operator' in query) is likely much too naïve. And you probably will need other cases than what I have here (AND/OR pair and leaf node.) In the end, you might replace every bit of this function, but it could well serve as a skeleton on which to grow the other requirements.
I still think your output format is odd. This result won't distinguish between '(Hyatt AND Westin) OR Orchid', '(Hyatt AND Westin) AND Orchid', '(Hyatt OR Westin) OR Orchid', and '(Hyatt OR Westin) AND Orchid'.
I make the following axios call and get the response below it:
axios.get('/[my url]/', {
params: {
myParam: this.items
}
})
Response:
0 Object { index: 1, var1: 3, var2: 2, … }
index 1
var1 3
var2 2
var3 480
var4 5800000
1 Object { index: 1, var1: 4, var2: 4, … } ......
I then try to split this response up into an array of objects, with an object for each index value present, and assign that value the this.series array:
.then(response => {
let test = response.data;
let obj = [];
test.forEach(function(item) {
console.log('run: ' + item);
if(!obj[item.index]) {
obj[item.index] = {
name: item.index,
type: 'scatter',
data: []
};
}
obj[item.index].data.push(
[
parseFloat(item.var3),
parseFloat(item.var4)
]
);
delete item.index;
});
this.series = obj;
})
Here is the part I don't understand. The code above does not work and prints this to console:
run: [object Object]
so it only runs once.
When I replace the console.log with console.log('run: ' + item.var4);
it then runs the right amount of times, but the obj variable still does not populate properly.
Any insights as to why this is happening?
I'm new to javascript, so this question may sound very basic.
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
I am trying to write a function to return who's got max number of shirts. So I write two functions like this to first get the max values
var shirtValues = data.map(function(obj) {return obj.items[shirts]; });
var maxValue = Math.max.apply(null, shirtValues);
console.log(maxValue);
Now i need to find who is the person that got max number of shirts. How to achieve that?
I would start by changing your data structure. It's hard to see how the current format would be overly useful for iterating, aggregating, getting the user's name, getting the item's names, etc., without excessive iterating.
Here is one alternative data structure that is much easier to work with because it doesn't have nested arrays and doesn't require Object.keys to access data you need consistently (e.g. user name):
var data = [{
user: 'Amy',
items: {
shirt: 12,
trouser: 10
}
}, {
user: 'Bill',
items: {
shirt: 10,
trouser: 11
}
}, {
user: 'Chet',
items: {
shirt: 11,
trouser: 12
}
}];
With this format, you could easily sort by a particular item quantity:
let getTopItem = (data, itemName) => {
// 1. clone the array using concat
// 2. sort by value at supplied itemName
// 3. return the first item
let sorted = data.concat().sort((a, b) => {
return b.items[itemName] - a.items[itemName];
});
return sorted.shift();
}
let topShirts = getTopItem(data, 'shirt');
console.log(topShirts.user);
EDIT - I don't mean this negatively toward any of the answers as they all seem to be correct and useful approaches to getting the required data from the presented data structure - but look at how many iterations they all require to get this very basic data out of your object. Choosing the right structure for your data will save you a lot of headaches down the road.
Provided that, you cannot change your datastructure, reduce function can be very handy to serve your purpose. Actually, the logic becomes pretty straight-forward!. The code is provided below:
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
var new_data = data.reduce(function(max, obj) {
var obj_val;
var max_val;
max_val = get_shirt_val(max);
obj_val = get_shirt_val(obj);
return obj_val > max_val ? obj : max;
});
function get_shirt_val(obj) {
key = Object.keys(obj)[0]
return obj[key].items[0].shirt;
}
console.log(JSON.stringify(new_data));
console.log(Object.keys(new_data)[0])
Hope this helps!
If your JSON structure is this always. Then you can use this way to find maximum shirts count :
var max=0;
var maxShirtCount=null;
data.forEach(function(ele,ind){
currVal = ele[Object.keys(ele)[0]].items[0].shirt;
if(currVal>max){
max=currVal;
maxShirtCount=ele;
}
});
console.log(maxShirtCount);
for maximum trousers count :
var max=0;
var maxTrouserCount=null;
data.forEach(function(ele,ind){
currVal = ele[Object.keys(ele)[0]].items[1].trouser;
if(currVal>max){
max=currVal;
maxTrouserCount=ele;
}
});
console.log(maxTrouserCount);
var data = [
{ Amy: {items:[{shirt: 12},{trouser: 10}] } },
{ Bill: {items:[{shirt: 10},{trouser: 11}] } },
{ Chet: {items:[{shirt: 11},{trouser: 12}] } }
];
function doSort(a, b){
return a[Object.keys(a)].items[0].shirt < b[Object.keys(b)].items[0].shirt;
}
console.log(Object.keys(data.sort(doSort)[0])[0]);
You could get the key, as name, get for the wanted item the count and make a comparison with the count of the previous persons. If max is the same, then extend the result array, if greater, then return a new object with the max data.
function getMax(item) {
return data.reduce(function (r, a) {
var name = Object.keys(a)[0],
count = a[name].items.reduce(function (s, b) {
return s + (b[item] || 0);
}, 0);
if (count >= r.count) {
if (count > r.count) {
return { item: item, names: [name], count: count };
}
r.names.push(name);
}
return r;
}, { item: item, names: [], count: 0 });
}
var data = [{ Amy: { items: [{ shirt: 12 }, { trouser: 10 }] } }, { Bill: { items: [{ shirt: 10 }, { trouser: 11 }] } }, { Chet: { items: [{ shirt: 11 }, { trouser: 12 }] } }],
maxShirt = getMax('shirt');
console.log(maxShirt); // all names with max count
console.log(maxShirt.names); // just the names
Without sorting and with a single pass reduce you might do as follows;
var data = [{ Amy: {items:[{shirt: 12},{trouser: 10}] } }, { Bill: {items:[{shirt: 10},{trouser: 11}] } }, { Chet: {items:[{shirt: 11},{trouser: 12}] } }
],
result = data.reduce((p,c) => { var sc = c[Object.keys(c)[0]].items[0].shirt;
return sc > p[0] ? [sc,c] : p;
},[0,{}])[1];
console.log(result);
I have 2 arrays of JSON objects which I'm looking to merge/combine together and then sum up the quantities of any matching entries.
Both of the arrays contain the same structure, one represents a list of equipment that is required to be used...
var required = [
{ SerialisedEquipment: { SerialNo: "ser855212" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: { SerialNo: "ser288945" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm71770" }, Serialised: false, Quantity: 5 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm11025" }, Serialised: false, Quantity: 2 }];
...and the other represents a list of equipment that actually has been used.
var used = [
{ SerialisedEquipment: { SerialNo: "ser663033" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: { SerialNo: "ser288945" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm71770" }, Serialised: false, Quantity: 2 }];
I have access to underscore.js and have been trying to use the _.groupBy and _.reduce methods to try and get the result I'm after but with no success. The result I'm looking to achieve is:
var result = [
{ SerialisedEquipment: { SerialNo: "ser663033" }, Type: undefined, Used: 1, Expected: 0, Remaining: 0 },
{ SerialisedEquipment: { SerialNo: "ser288945" }, Type: undefined, Used: 1, Expected: 1, Remaining: 0 },
{ SerialisedEquipment: { SerialNo: "ser855212" }, Type: undefined, Used: 0, Expected: 1, Remaining: 1 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm71770" }, Used: 2, Expected: 5, Remaining: 3 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm11025" }, Used: 0, Expected: 2, Remaining: 2 }];
I've also been looking at some of the Array methods that underscore provides but I'm not sure how I would use these to specify the criteria to merge by. Would anyone have any suggestions on the best way to achieve this?
UPDATE
I've managed to get the merged list of both of the arrays, removing duplicates...
// Split based on the serialised flag - so I know to look at either the serialNo or Type property
var isSerialised = _.groupBy(required, function (equip) {
return equip.Serialised;
});
// Get all the required serialised equipment that is not already in the used list
var serialised = _.filter(isSerialised[true], function (value) {
return (!_.some(used, function (equip) {
return equip.SerialisedEquipment && equip.SerialisedEquipment.SerialNo == value.SerialisedEquipment.SerialNo;
}));
});
// Get all the required types that are not already in the used list
var types = _.filter(isSerialised[false], function (value) {
return (!_.some(used, function (equip) {
return equip.Type && equip.Type.ItemId == value.Type.ItemId;
}));
});
// Combine the equipment that is not in the list with the equipment that is in the list
var result = _.union(used, serialised, types);
I think it's now just a case now of looping through this results list with the required equipment list and summing up equipment that match based on serial number or type.
Sometime wanting to use a library to much makes you miss simpler algorithms:
var resultsById = {};
function getTemporaryId(value) {
return value.SerialisedEquipment ? value.SerialisedEquipment.SerialNo : value.Type.ItemId;
}
function getResultForValue(value) {
var id = getTemporaryId(value);
if(!resultsById[id]) {
resultsById[id] = {
SerialisedEquipment: value.SerialisedEquipment,
Type: value.Type,
Used: 0,
Expected: 0,
Remaining: 0
};
}
return resultsById[id];
}
_.each(required, function(value) {
var result = getResultForValue(value);
result.Expected += value.Quantity;
result.Remaining += value.Quantity;
});
_.each(used, function(value) {
var result = getResultForValue(value);
result.Used += value.Quantity;
result.Remaining = Math.max(result.Remaining - value.Quantity, 0);
});
var merged = _.values(resultsById);
If you really want to use lots of underscore, you can play with this solution:
var requiredValues = _.map(required, (function(value){
//maybe want to clone value? value = _.clone(value);
value.Used = 0;
value.Expected = value.Quantity;
return value;
}));
var usedValues = _.map(used, (function(value){
//maybe want to clone value? value = _.clone(value);
value.Used = value.Quantity;
value.Expected = 0;
return value;
}));
var mergedValues = _.chain(requiredValues.concat(usedValues))
.groupBy(function(value){
return value.SerialisedEquipment ? value.SerialisedEquipment.SerialNo : value.Type.ItemId;
})
.map(function(values) {
var memo = {
SerialisedEquipment: values[0].SerialisedEquipment,
Type: values[0].Type,
Used: 0,
Expected: 0,
Remaining: 0
};
return _.reduce(values, function(memo, value) {
memo.Used += value.Used;
memo.Expected += value.Expected;
memo.Remaining = Math.max(memo.Expected - memo.Used, 0);
return memo;
}, memo)
})
.values()
.value();
You can just use Array.concat() method:
var result = required.concat(used);
and it will make merge for you.
EDIT
Though if you want to use undescore method _.union(arr1,arr2) is for you!