Combine associative array/object in JS without erasing numerically indexed values $.extend() - javascript

When using $.extend(true, {}, obj1, obj2), the resulting object erases any numerically-indexed data up to the number of results in obj2.
A sample call to $.extend() would be like this:
Then take this sample data for instance:
var base_json_obj = {
'regions': [
'NC',
'-Greensboro',
'VA',
'-Richmond'
],
'vehicles': [
'Ford:Escape',
'Nissan:Altima'
],
'turnover': {
'min': '0d',
'max': '5d'
}
};
var new_json_obj = {
'regions': [
'FL',
'-Miami'
],
'vehicles': [
'Hyundai:Sonata'
],
'turnover': {
'min': '1d',
'max': '6d'
}
};
var resulting_object = $.extend(true, {}, base_json_obj, new_json_obj);
The resulting object is
{
'regions': [
'FL',
'-Miami',
'VA',
'-Richmond'
],
'vehicles': [
'Hyundai:Sonata',
'Nissan:Altima'
]
'turnover': {
'min': '1d',
'max': '6d'
},
}
And here is the expected output. Notice that regions has all 6 values and that vehicles has all 3 values.
{
'regions': [
'NC',
'-Greensboro',
'VA',
'-Richmond',
'FL',
'-Miami'
],
'vehicles': [
'Ford:Escape',
'Nissan:Altima',
'Hyundai:Sonata'
]
'turnover': {
'min': '1d',
'max': '6d'
},
}
Is there a way to modify the call to $.extend() or use $.merge() in some way to achieve this?

$.extend(true, ...) will treat the Arrays like an object, using value and index, so just like 'min', the key of 0 will be replaced.
You'll need to write something that handles this case. The $.extend() function is relatively complicated, but a good place to start:
Github - jQuery Core Source

You can use a loop :
var base_json_obj = {
'regions': [
'NC',
'-Greensboro',
'VA',
'-Richmond'
],
'vehicles': [
'Ford:Escape',
'Nissan:Altima'
],
'turnover': {
'min': '0d',
'max': '5d'
}
};
var new_json_obj = {
'regions': [
'FL',
'-Miami'
],
'vehicles': [
'Hyundai:Sonata'
],
'turnover': {
'min': '1d',
'max': '6d'
}
};
var resulting_object={};
for (key in base_json_obj) {
// the values from base and new json objects respectively
var val1 = base_json_obj[key], val2 = new_json_obj[key];
resulting_object[key] = Array.isArray(val1)? // if array
val1.concat(val2): // concatenate them, else (objects)
Object.assign({}, val1, val2); // assign the second object to the first one
}
console.log(resulting_object);

Related

Mapping values from two arrays

I am using a text analysis service (pos) which I can pass a string at it tells me whether than string contains verbs, nouns etc.
I have code:
var words = new pos.Lexer().lex(req.body.Text);
var tagger = new pos.Tagger();
var taggedWords = tagger.tag(words);
taggedWords is then passed to a handlebars template and looped through and printed.
If I console.log(taggedWords) I see a multidimensional array eg:
[
[ 'Interest-only', 'RB' ],
[ 'deals', 'NNS' ],
[ 'allow', 'VB' ],
[ 'only', 'RB' ],
[ 'ends', 'NNS' ],
...
...
]
I would like to maintain a separate array which maps the values in the above array to human-readable version:
[
['RB', 'adjective'],
['NNS', 'noun'],
['VB', 'verb'],
...
...
]
and then be able to rewrite so that the original array (taggedWords) looks like:
[
[ 'Interest-only', 'adjective' ],
[ 'deals', 'noun' ],
[ 'allow', 'verb' ]
]
and then pass this new array to my template. What is the most efficient way to do this?
var taggedWords = [
[ 'Interest-only', 'RB' ],
[ 'deals', 'NNS' ],
[ 'allow', 'VB' ],
[ 'only', 'RB' ],
[ 'ends', 'NNS' ]
];
var dico = {
'RB' : 'adjective',
'NNS' : 'noun',
'VB' : 'verb'
};
taggedWords.forEach( elt => { elt[1] = dico[elt[1]] });
console.log(taggedWords);
You can use map() to create a new array with modified elements from your original. This code changes the second item in each tagged word to what is listed in the dictionary for that tag.
let taggedWords = [
[ 'Interest-only', 'RB' ],
[ 'deals', 'NNS' ],
[ 'allow', 'VB' ],
[ 'only', 'RB' ],
[ 'ends', 'NNS' ]
];
let dict = [
['RB', 'adjective'],
['NNS', 'noun'],
['VB', 'verb']
];
let result = taggedWords.map(tag => {
tag[1] = dict.find(item => item[0] === tag[1])[1];
return tag;
});
console.log(result);
Brute force method would involve going through all the elements in the arrays and find a match for them in another array and push them into a third array. This will require that you don't have the same tag word in an array twice; ie: 'RB', or 'NNS'. Hope this solves your problem for now. The benefits to this method as apposed to the previous answer would be that the order of items in arrays don't matter since you're comparing each element to every other element in the other array.
let array1 = [
[ 'Interest-only', 'RB' ],
[ 'deals', 'NNS' ],
[ 'allow', 'VB' ],
[ 'only', 'RB' ],
[ 'ends', 'NNS' ]
];
let array2 = [
['RB', 'adjective'],
['NNS', 'noun'],
['VB', 'verb'],
];
let array3 = [];
array1.forEach(el =>
{
array2.forEach(par =>
{
if (el[1] === par[0])
{
array3.push([el[0], par[1]])
}
})
});
console.log(array3);

for of loop is only run once

I need to traverse a pretty deep object and I need to extract 12 values in different depths. My plan was to extract with the for of the values for each depth but I have a problem in the first depth.
I am a little confused about the for of behavior.
I thought this:
for (const key of Object.keys(jsonData)){
console.log(i+1);
if (isWantedValue(key))
{
artifactColl[key] = jsonData[key];
}
console.log(key, jsonData[key]);
}
for of loop would run a cycle for each key element that it finds inside the object but the loop is only running once. It prints out all necessary keys with values in the lower console.log function but it calls isWantedValue function only once.
Can please somebody explain that to me?
Object looks like this:
{ searchNGResponse:
{ totalCount: [ '420' ],
from: [ '-1' ],
count: [ '-1' ],
tooManyResults: [ 'false' ],
collapsed: [ 'false' ],
repoDetails: [ [Object] ],
data: [ [Object] ] } }
console output:
1
called with searchNGResponse
searchNGResponse { totalCount: [ '416' ],
from: [ '-1' ],
count: [ '-1' ],
tooManyResults: [ 'false' ],
collapsed: [ 'false' ],
repoDetails: [ { 'xxx': [Object] } ],
data: [ { artifact: [Object] } ] }
Edit: updated
Your jsonData object has only one key, as the console output shows: searchNGResponse.
What you want is the keys of that object, the searchNGResponse object, specifically jsonData.searchNGResponse like this
for (const key of Object.keys(jsonData.searchNGResponse)){
console.log(i+1);
if (isWantedValue(key))
{
artifactColl[key] = jsonData.searchNGResponse[key];
}
console.log(key, jsonData.searchNGResponse[key]);
}

How to handle Promise.all properly: Getting undefined

I'm trying to get an array filled with the info back from some requests made to different REST APIs.
I thought about using Promise.all to do that but for some reason, it yields an Array with a bunch of undefined inside.
[ undefined, undefined, undefined, undefined ]
Here's my code:
var _ = require("lodash");//Used exclusively to check if the result from the request is an object
var ccxt = require("ccxt");//External library used to make the requests
let pairs = ["ETH/EUR", "BTC/EUR", "LTC/EUR", "BCH/EUR"]; //Array on which the Promise.all is based
function test(p) {
for (var i = 0; i < ccxt.exchanges.length; i++) { //Looping through all the rest APIs
let exchange = new ccxt[ccxt.exchanges[i]](); //Defining each API to make the requests
if (exchange.hasFetchOrderBook) {
exchange //Beginning of the request
.fetchOrderBook(p)
.then(order => {
if (_.isObject(order) && order.bids[0][1]) {
let now = Math.floor(new Date());
order.mkt = exchange.name;
order.pair = p;
order.ping = now - order.timestamp;
return order; //Return the result of the request
}
})
.catch(e => {});
}
}
}
Promise.all(pairs.map(test)) //Making the requests based on the Pairs Array
.then(res => {
console.log(res); //Logging the results ==> [undefined, undefined, undefined, undefined] for some reason...
})
.catch(e => {
console.log(e);
});
I know that the requests are correctly being made since if I console.log the order within the loop, I get the correct results -- Example of the result when logging:
{ bids:
[ [ 12009.52, 0.0468 ],
[ 12008.5, 0.0227 ],
[ 12007.48, 30.9321 ],
[ 12006.46, 0.0537 ],
[ 12005.45, 0.0157 ],
[ 12004.43, 7.1659 ],
[ 12003.41, 0.0164 ],
[ 12002.39, 23.4159 ],
[ 12001.38, 0.0284 ],
[ 12000.36, 0.0132 ],
[ 11999.34, 0.0194 ],
[ 11998.33, 0.0034 ],
[ 11997.31, 7.526 ],
[ 2445.72, 34.075 ],
[ 2445.17, 25.4842 ],
[ 2444.96, 0.1118 ],
[ 2444.75, 23.288 ],
[ 2444, 0.0247 ],
[ 2443.8, 0.192 ],
[ 765.51, 0.0828 ] ],
asks:
[ [ 12048.74, 2.523 ],
[ 12049.77, 0.0159 ],
[ 12050.79, 0.029 ],
[ 12051.82, 0.0061 ],
[ 12052.84, 0.0181 ],
[ 12053.87, 0.0164 ],
[ 12054.89, 0.0355 ],
[ 12055.92, 0.0042 ],
[ 13419.62, 0.0063 ],
[ 13420.64, 0.0174 ],
[ 13421.78, 0.0143 ],
[ 13422.92, 0.026 ],
[ 13424.06, 0.0055 ],
[ 13425.2, 14.4552 ],
[ 13426.23, 0.0065 ],
[ 13427.25, 0.0057 ],
[ 13428.39, 0.0147 ],
[ 13429.53, 4.0375 ],
[ 13430.56, 23.9541 ],
[ 13431.58, 0.0137 ] ],
timestamp: 1512845715447,
datetime: '2017-12-09T18:55:15.447Z',
mkt: 'LakeBTC',
pair: 'BTC/EUR',
ping: 0 }
So I guess that the problems I'm dealing with has to do with the asynchronous character of the function... but I'm not sure how I can make it synchronous.
Again, just to try to clarify my question: The objective is to get an array with 4 different types of object (one per pair --> array) so that I can operate on each.
Just to make it clearer, here's an illustration of what I'm trying to achieve:
[
[
Object1,
Object2,
Object3,
etc...
],
[
Object1,
Object2,
Object3,
etc...
],
[
Object1,
Object2,
Object3,
etc...
],
[
Object1,
Object2,
Object3,
etc...
]
]
Why is Promise.all returning the array without waiting on the requests'results?
I hope that was clear enough! If not let mw know! :P
Thanks in advance for your help!
Your test function does return a undefined. You need to return a promise for the result:
function test(p) {
return Promise.all(ccxt.exchanges.map(api => { //Looping through all the rest APIs
//^^^^^^^^^^^^^^^^^^
let exchange = new ccxt[api](); //Defining each API to make the requests
if (exchange.hasFetchOrderBook) {
return exchange //Beginning of the request
.fetchOrderBook(p)
.then(order => {
if (_.isObject(order) && order.bids[0][1]) {
let now = Math.floor(new Date());
order.mkt = exchange.name;
order.pair = p;
order.ping = now - order.timestamp;
return order; //Return the result of the request
}
// else undefined
})
.catch(e => {}); // undefined
}
// else undefined
}));
}
Of course your promises still fulfill with undefined when the if conditions do not apply or an error happens.

Default values in json object based on contents of other objects in the same array

I'm pretty lost right now and have been working on this for about 6 days now so forgive me if this is a bit confusing. I'm using NVD3 to display some graphs based on data that comes in from BigQuery. All the data coming in is correct and so is the graph implementation, the issue is the actual JSON data. The multi bar chart requires that each "set" of data have the same dates and same number of values under the initial array. Based off my data, sometimes there will be missing values if a user didn't log an event or something that day.
The general idea of what I'm trying to do here is loop through the initial json and append on the "missing" values. For example this would be the initial data that I get out of BigQuery and my API:
[
"t1":{
"target": "t1",
"datapoints": [
[
16.0,
1483747200.0
],
[
10.0,
1484352000.0
]
]
},
"t2":{
"target": "t2",
"datapoints": [
[
10.0,
1483660800.0
],
[
19.0,
1484006400.0
],
[
10.0,
1484956800.0
]
]
}
]
You can see here that the first object has a datapoints array with 2 values, object two has a datapoints array with 3 values. The 1 index of the datapoints array contains a UNIX date, every datapoints array within the entire object must have an array with the date and then 0 for a default value. So the formatted data would look something like this:
[
"t1":{
"target": "t1",
"datapoints": [
[
16.0,
1483747200.0
],
[
10.0,
1484352000.0
],
[
0.0,
1483660800.0
],
[
0.0,
1484006400.0
],
[
0.0,
1484956800.0
]
]
},
"t2":{
"target": "t2",
"datapoints": [
[
10.0,
1483660800.0
],
[
19.0,
1484006400.0
],
[
10.0,
1484956800.0
],
[
0.0,
1483747200.0
],
[
0.0,
1484352000.0
]
]
}
]
I really have no idea where to go from here and any help whatsoever would be extremely helpful. I've been working on this for days and at this point am just grinding my gears. Thanks
Basically, each value that's found in one array but not in others should take the timestamp but set the first value/index to 0.
I should also mention that the query is only querying for 30 days back so at most each one of the datapoints arrays would have 30 arrays.
You will have to do a bit of data processing first to get all of the dates, then it's just a matter of filling in the actual data for each date.
const json = [
{
"target": "t1",
"datapoints": [
[
16.0,
1483747200.0
],
[
10.0,
1484352000.0
]
]
},
{
"target": "t2",
"datapoints": [
[
10.0,
1483660800.0
],
[
19.0,
1484006400.0
],
[
10.0,
1484956800.0
]
]
}
]
// using es6 set
const dates = new Set()
json.forEach( x => x.datapoints.map( dp => dates.add(dp[1]) ) )
// all dates are there
dates.forEach( d => console.log(d) )
const fillDp = dp => {
return Array.from(dates).sort().map( d => dp.find( x => x[1] === d ) || [0,d] )
}
const result = json.map( x => Object.assign(x, {datapoints: fillDp(x.datapoints)}) )
console.log(JSON.stringify(result[0].datapoints))
Below is a quick and dirty solution. Note that I added an additional entry to the sample you provided so that both data objects contain a data point with a common date.
var orig= [
{
"target": "t1",
"datapoints": [
[
16.0,
1483747200.0
],
[
10.0,
1484352000.0
],
[
10.0,
1483660800.0
]
]
},
{
"target": "t2",
"datapoints": [
[
10.0,
1483660800.0
],
[
19.0,
1484006400.0
],
[
10.0,
1484956800.0
]
]
}
];
console.log('Original Data', JSON.stringify(orig));
// Get a list of all the datapoint dates
var dates = [];
orig.forEach(function(item) {
item.datapoints.forEach(function(dp) {
var date = dp[1];
dates.push(date);
})
});
console.log('All Dates', JSON.stringify(dates));
// Remove duplicates from array
dates = dates.filter(function (el, i, arr) {
return arr.indexOf(el) === i;
});
console.log('Unique Dates', JSON.stringify(dates));
// Check each item in the original array for records for each date and add a
// 0 value entry if the date entry is missing
dates.forEach(function(dt) {
orig.forEach(function(item, itemIndex) {
var hasEntry = false;
item.datapoints.forEach(function(dp) {
if (dp[1] === dt) hasEntry = true;
});
if (!hasEntry) {
item.datapoints.push([0, dt]);
}
});
});
console.log('Updated Data', JSON.stringify(orig));
And here is the corresponding plunker so you can see it in action: https://plnkr.co/edit/K1NK2Xx8RNrqyp7yZ3n2?p=preview

UnderscoreJs: _.groupBy together with _.sortBy

I'm trying to use underscores _.groupBy() and _.sortBy in pair, and a problem is that the last one changes object with keys returned from groupBy to array with indexes. Is it possible to preserve original indexes (keys) from object?
Here is example:
My code:
var sorted = _.chain(cars).groupBy('Make').sortBy(function(car) {
return car.length * -1;
});
Result from groupBy:
{
"Volvo" : [ "S60", "V40" ],
"Volkswagen" : [ "Polo", "Golf", "Passat" ]
}
Result from sortBy:
[
0 : [ "Polo", "Golf", "Passat" ],
1 : [ "S60", "V40" ]
]
Expected result:
[
"Volkswagen" : [ "Polo", "Golf", "Passat" ],
"Volvo" : [ "S60", "V40" ]
]
Objects are unordered in JavaScript. If you need something like an object but ordered, you can use _.pairs to convert it, then sort the list of pairs.
_.pairs({
"Volvo" : [ "S60", "V40" ],
"Volkswagen" : [ "Polo", "Golf", "Passat" ]
})
Gives:
[
["Volvo", [ "S60", "V40" ]],
["Volkswagen", [ "Polo", "Golf", "Passat" ]]
]
...which you can then sort using _.sortBy. If you assign the above to cars, then:
_.sortBy(cars, function(x) { return -x[0].length; });
gives:
[
[ 'Volkswagen', ['Polo', 'Golf', 'Passat' ]],
[ 'Volvo', ['S60', 'V40']]
]

Categories