I have an array like this:
0: "SuSPENSE"
1: "Subcontractor Expense"
2: "Data Entry"
3: "Design"
4: "Programming"
5: "Subcontractor Expense - Other"
6: "Total Subcontractor Expense"
7: "Technology-Communication"
8: "Licenses"
9: "Domain Hosting Fee"
10: "Internet"
11: "Internet Servers"
12: "Internet - Other"
13: "Total Internet"
14: "Telephone"
15: "Call Center"
16: "Cellular Phones"
17: "Fax"
18: "Telephone - Other"
19: "Total Telephone"
20: "Hardware/Software"
21: "Computer Software"
22: "Hardware/Software - Other"
23: "Total Hardware/Software"
24: "Technology-Communication - Other"
25: "Total Technology-Communication"
This is a list of categories and subcategories. For example, "Subcontractor Expense" is a sub-category and ends with "Total Subcontractor Expense". Same for "Internet" and "Total Internet". The template is always the same, category starts with "(name)" and ends with "Total (name)". But each category can have many levels of sub-categories, like a tree. I'm trying to parse this array into a JSON-like array or a multidimensional array using recursive function, but I never know what the maximum depth is. I tried to do the following using js:
var parsedArray = [];
var items = getLineItems("Expense", "Total Expense"); //This is a function to return the mentioned array
var newArray = parseArray(items, "Expense");
function parseArray(items, category){
var lineItems = [];
for(var i = 0; i < items.length; i++){
var inArray = $.inArray("Total " + items[i], items);
if(inArray !== -1){
parseArray(getLineItems(items[i], "Total " + items[i]), items[i]);
}
else {
lineItems.push(items[i]);
}
}
parsedArray[category] = lineItems;
}
But this recursive function never goes deeper then 2 levels. Is is possible to generate something like this?
"SuSPENSE"
"Subcontractor Expense"
"Data Entry"
"Design"
"Programming"
"Subcontractor Expense - Other"
"Technology-Communication"
"Licenses"
"Domain Hosting Fee"
"Internet"
"Internet Servers"
"Internet - Other"
"Telephone"
"Call Center"
"Cellular Phones"
"Fax"
"Telephone - Other"
"Hardware/Software"
"Computer Software"
"Hardware/Software - Other"
"Technology-Communication - Other"
I'm still guessing about your output format. Here is a recursive solution that gives the format I asked about in the comments:
[
{name: "SuSPENSE"},
{name: "Subcontractor Expense", children: [
{name: "Data Entry"},
{name: "Design"},
{name: "Programming"},
{name: "Subcontractor Expense - Other"}
]},
{name: "Technology-Communication", children: [
//...
]}
]
const restructure = (
[s = undefined, ...ss],
index = s == undefined ? -1 : ss .indexOf ('Total ' + s)
) =>
s == undefined
? []
: index > -1
? [
{name: s, children: restructure (ss .slice (0, index))},
... restructure (ss .slice (index + 1))
]
: [{name: s}, ... restructure (ss)]
const data = ["SuSPENSE", "Subcontractor Expense", "Data Entry", "Design", "Programming", "Subcontractor Expense - Other", "Total Subcontractor Expense", "Technology-Communication", "Licenses", "Domain Hosting Fee", "Internet", "Internet Servers", "Internet - Other", "Total Internet", "Telephone", "Call Center", "Cellular Phones", "Fax", "Telephone - Other", "Total Telephone", "Hardware/Software", "Computer Software", "Hardware/Software - Other", "Total Hardware/Software", "Technology-Communication - Other", "Total Technology-Communication"]
console .log (
restructure (data)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
Note that in one of the recursive cases, we call our main function twice. Once for the nested data and once for the remainder of the array.
Another possible structure, one which I like less, but which might meet your needs, looks like this:
[
"SuSPENSE",
{"Subcontractor Expense": [
"Data Entry", "Design", "Programming", "Subcontractor Expense - Other"
]},
{ "Technology-Communication": [
// ...
]}
]
This could be achieved with only a minor modification:
const restructure = (
[s = undefined, ...ss],
index = s == undefined ? -1 : ss .indexOf ('Total ' + s)
) =>
s == undefined
? []
: index > -1
? [
{[s]: restructure (ss .slice (0, index))},
... restructure (ss .slice (index + 1))
]
: [s, ... restructure (ss)]
Update
This variant does the same as the first, but may look more familiar to people not used to my expression-heavy style:
const restructure = ([s = undefined, ...ss]) => {
if (s == undefined) {return []}
const index = ss .indexOf ('Total ' + s)
return index < 0
? [{name: s}, ... restructure (ss)]
: [
{name: s, children: restructure (ss .slice (0, index))},
... restructure (ss .slice (index + 1))
]
}
You could do this by checking if the current element has the corresponding element that starts with the word Total and then continues with the text of the current element and if so you increment the level. When the current element starts with word total then you decrement the level.
const data = {"0":"SuSPENSE","1":"Subcontractor Expense","2":"Data Entry","3":"Design","4":"Programming","5":"Subcontractor Expense - Other","6":"Total Subcontractor Expense","7":"Technology-Communication","8":"Licenses","9":"Domain Hosting Fee","10":"Internet","11":"Internet Servers","12":"Internet - Other","13":"Total Internet","14":"Telephone","15":"Call Center","16":"Cellular Phones","17":"Fax","18":"Telephone - Other","19":"Total Telephone","20":"Hardware/Software","21":"Computer Software","22":"Hardware/Software - Other","23":"Total Hardware/Software","24":"Technology-Communication - Other","25":"Total Technology-Communication"}
function toNested(data) {
const result = [];
const levels = [result]
let level = 0;
const checkChildren = (string, data) => {
return data.some(e => e === `Total ${string}`)
}
data.forEach((e, i) => {
const object = { name: e, children: []}
levels[level + 1] = object.children;
if (e.startsWith('Total')) level--;
else levels[level].push(object);
if (checkChildren(e, data.slice(i))) level++;
})
return result;
}
const result = toNested(Object.values(data));
console.log(result)
Related
I have an array of objects, data, that I'm attempting to retrieve the total of both shoe sizes.
I'm having issues counting "size 10" apart from "size 5". I've attempted to use the .reduce() method with a conditional to only add the total count if the names match:
const data = [
{ "shoeSize": "size 10", "total": 9 },
{ "shoeSize": "size 10", "total": 3 },
{ "shoeSize": "size 5", "total": 2 }
];
const grandTotal = data.reduce((prev, curr) => {
return {
shoeSize10Total:
prev.shoeSize === "size 10" ||
curr.shoeSize === "size 10" &&
prev.total + curr.total,
shoeSize5Total:
prev.shoeSize === "size 5" ||
curr.shoeSize === "size 5" &&
prev.total + curr.total
};
});
console.log(grandTotal);
However, the result of this method returns NaN and false - instead of the total amount of numbers for each.
I've tried looking up a solution and found something similar on this post, but they are filtering to return the value of 1; I would like to return both in an object.
This is the intended result of my .reduce():
const grandTotal = {
shoeSize10Total: 12
shoeSize5Total: 2
};
I think using .reduce here makes things too convoluted. Consider a plain loop where you increment the appropriate property an an object declared outside.
const data = [
{ shoeSize: "size 10", total: 9},
{ shoeSize: "size 10", total: 3},
{ shoeSize: "size 5", total: 2}
];
const grandTotal = {};
for (const { shoeSize, total } of data) {
const digits = shoeSize.match(/\d+/)[0];
if (!digits) continue;
const key = `shoeSize${digits}Total`;
grandTotal[key] = (grandTotal[key] || 0) + total;
}
console.log(grandTotal);
We could implement basic approaches using EcmaScript Methods such as filter. Take a look at this code snippet, pal. I certainly hope this helps, buddy!
const data = [
{ shoeSize: "size 10", total: 9 },
{ shoeSize: "size 10", total: 3 },
{ shoeSize: "size 5", total: 2 },
];
let shoeSize10Total = 0;
let shoeSize05Total = 0;
const grandTotal = (data) => {
data.filter((el) => {
if (el.shoeSize === "size 10") {
shoeSize10Total += el.total;
} else {
shoeSize05Total += el.total;
}
});
return { shoeSize10Total, shoeSize05Total };
};
console.log(grandTotal(data));
One of issues is that you are not using the initialValue of reduce method.
Moreover, the approach to build the grandTotal is far from the best as the values are hardcoded into the reduce handler, for multiple size values that code will be a mess.
A cleaner approach is to create a mapping of input values to output keys, then in the reduce handler just to use that mapping for lookups, like this:
const data = [
{ shoeSize: "size 10", total: 9},
{ shoeSize: "size 10", total: 3},
{ shoeSize: "size 5", total: 2}
];
const map = {
'size 5': 'shoeSize5Total',
'size 10': 'shoeSize10Total'
};
const grandTotal = data.reduce((o, {shoeSize: s, total}) =>
(o[map[s]] = (o[map[s]] ?? 0) + total, o), {});
console.log(grandTotal);
little info about below code
firstly use {} object as initial value to reduce function
now extract shoeSize from data array and use it as a key for new object
now write a condition if that key exist in new object then increment the value otherwise create a new property to object
boom at the end you will have a sorted object as a result
const data = [
{ shoeSize: "size 10", total: 9 },
{ shoeSize: "size 10", total: 3 },
{ shoeSize: "size 5", total: 2 }
]
let res = data.reduce((main,each)=>{
let num = each.shoeSize.match(/\d+/)[0],
newKey = `shoeSize${num}Total`,
exist = typeof main[newKey] !== "undefined"
if(exist){main[newKey] += each.total}
else{main[newKey] = each.total}
return main
},{})
console.log(res)
A simple forEach will do:
const data = [
{ shoeSize: "size 10", total: 9},
{ shoeSize: "size 10", total: 3},
{ shoeSize: "size 5", total: 2}
]
const n = {}
data.forEach(d => {
const key = "Shoe" + d.shoeSize.charAt(0).toUpperCase() + d.shoeSize.slice(1).replace(" ","") + 'Total';
const res = Object.keys(n).filter(_i => _i == key)
if (res.length == 0) {
n[key] = 0;
}
n[key] += d.total;
})
console.log(n)
I don't think .reduce is the right think to use in this problem because it makes things a little bit more complex in this situation. So I prefer always using functions to do the work for me.
function collectShoesTogether(arr) {
var obj = {};
arr.forEach((item) => {
let key = `shoeSize${item.shoeSize.split(" ").join("")}Total`;
if (obj[key] === undefined) {
obj[key] = item.total;
} else {
obj[key] += item.total;
}
});
return obj;
}
I have two datasets and have been trying to combine them, but have no idea where to start. I am making two API calls and below is a small example of a response:
{
early_game_items: {
57: 16
59: 60
106: 1
180: 54
214: 28
232: 6
},
late_game_items: {
108: 1
116: 1
},
mid_game_items: {
1: 52
102: 3
108: 4
116: 1
193: 1
194: 1
223: 1
232: 73
}
}
The other data set is numbered from 1 - 300ish and is an object made of other objects. Below is an snippet:
const items = [{
"id": 57,
"name": "void_stone",
"cost": 825,
"secret_shop": 1,
"side_shop": 0,
"recipe": 0,
"localized_name": "Void Stone"
},
{
"id": 58,
"name": "mystic_staff",
"cost": 2700,
"secret_shop": 1,
"side_shop": 0,
"recipe": 0,
"localized_name": "Mystic Staff"
},
{
"id": 59,
"name": "energy_booster",
"cost": 900,
"secret_shop": 1,
"side_shop": 0,
"recipe": 0,
"localized_name": "Energy Booster"
}...]
I need to put the data from the second data set into the first by matching the key in the first data set with the id in the second. For example:
{
early_game_items: {
57: {amount: 16, name: 'void_stone', cost: 825}
59: {amount: 60...
106: {amount: 1...
180: {amount: 54...
214: {amount: 28...
232: {amount: 6...
}...
Thank you so much for looking this over! I am new to js and am really trying to learn.
Such as another answer naming the datasets dataset1 & dataset2, and assuming dataset2 is an array of objects. If dataset2 is big array, this answer has better performance:
let result = {};
// key = 'early_game_items', 'mid_game_items', 'late_game_items'
for(let key in dataset1) {
result[key] = {};
for(let id in dataset1[key]) {
result[key][id] = { amount: dataset1[key][id] };
}
}
for(let id in dataset2) {
for(let key in dataset1) {
let _item;
if(_item=result[key][id]){
const { name, cost } = dataset2[id];
_item.name = name;
_item.cost = cost;
}
}
}
console.log(result);
Naming the datasets dataset1 & dataset2, and assuming dataset2 is an array of objects:
let result = {};
// key = 'early_game_items', 'mid_game_items', 'late_game_items'
for(let key in dataset1) {
const itemGroup = dataset1[key];
let _itemGroup = {};
for(let id in itemGroup) {
let _item = { amount: itemGroup[id] };
// find item by id, in second dataset
const item = dataset2.find(i => i.id == id) || {};
// get name & cost via destructuring
const { name, cost } = item;
_item.name = name;
_item.cost = cost;
// store in new itemGroup
_itemGroup[id] = _item;
}
// store in result
result[key] = _itemGroup
}
console.log(result);
If dataset2 is an object with numbers as keys, you'll need to modify the "find by id" function:
// find item by id, in second dataset
const item = Object.values(dataset2).find(i => i.id === id) || {};
I have two array like this:
var actval = [
0: "3"
1: "22-Nov-2018 15:32:36 IST"
2: "22-Nov-2018 15:32:40 IST"
3: "3"
4: "22-Nov-2018 15:32:36 IST"
5: "22-Nov-2018 15:32:40 IST"
6: "3"
7: "22-Nov-2018 15:32:36 IST"
8: "22-Nov-2018 15:32:40 IST"
]
var id = [
0: "STATUS"
1: "STARTTIME"
2: "ENDTIME"
3: "STATUS"
4: "STARTTIME"
5: "ENDTIME"
6: "STATUS"
7: "STARTTIME"
8: "ENDTIME"
]
What I want to do is make a final array which would have keys as STATUS, STARTTIME, ENDTIME and each of the keys can have multiple values like this:
finalarray = [
STATUS: ["3", "3", "3"]
STARTTIME: ["22-Nov-2018 15:32:36 IST", "22-Nov-2018 15:32:36 IST", "22-Nov-2018 15:32:36 IST"]
ENDTIME: ["22-Nov-2018 15:32:40 IST", "22-Nov-2018 15:32:40 IST", "22-Nov-2018 15:32:40 IST"]
]
For this I have tried this approach :
for (i = 0; i < id.length; i++) {
currentKey = id[i];
currentVal = actval[i];
result[currentKey] = currentVal;
}
but it only gives me one value for each key not every value:
How can i get the each value in that array linked to that key?
Solved Just adding one check in your code
try this:
var result = [];
for (i = 0; i < id.length; i++) {
let currentKey = id[i];
let currentVal = actval[i];
if(result[currentKey])
result[currentKey] = [...result[currentKey],currentVal];
else
result[currentKey] = [currentVal];
}
If actval always look like that and you want to extract the top 3 values you could just do a shift() something like this.
var final = {STATUS: [], STARTTIME:[], ENDTIME:[]};
for (let i=0; actval.length > i; i++) {
final.STATUS.push( actval.shift() );
final.STARTTIME.push( actval.shift() );
final.ENDTIME.push( actval.shift() );
}
What you are doing is just writing over the same array value with =.
You are always doing result['key'] = 'value';
Try this assuming actval is array not object:
finalobject = {
STATUS: actval.filter(e=>e.length===1),
STARTTIME: actval.filter((e, i)=>i%3).filter((e, i)=>i%2===0),
ENDTIME: actval.filter((e, i)=>i%3).filter((e, i)=>i%2!==0),
};
I will update question, when you will clarify some things and change this "arrays" to objects.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I'd like to change the structure of my result.There are objects "itemGroup" and I'd like to delete them and keep keys "fruit" and "vegetable".
[{"id": 1, "shop": "shop1", "itemGroup": {"fruit": 2, "vegetable": 2},"total":4},
{"id": 2, "shop": "shop2", "itemGroup": {"fruit": 0, "vegetable": 1},"total":1}]
I'd like this result
[
{ "id": 1, "shop": "shop1", "fruit": 2, "vegetable": 2, "total": 4 },
{ "id": 2, "shop": "shop2" "fruit": 0, "vegetable": 1, "total": 1 }
]
my code
var myArray = [
{shop: "shop1",item1: "my apple 1",item2: "my carrot 1"},
{shop: "shop1",item1: "my apple 1",item2: "my carrot 1"},
{shop: "shop2",item1: "my apple 0",item2: "my carrot 0"},
{shop: "shop2",item1: "my apple 0",item2: "my carrot 1"}
];
var MyArrayDefinition = [
{item: "my apple 0",color: "red", group: "fruit",score: 0},
{item: "my carrot 1",color: "orange",group: "vegetable",score: 1},
{item: "my apple 1",color: "red", group: "fruit",score: 1},
{item: "my carrot 0",color: "orange",group: "vegetable",score: 0}
];
var k = Object.keys,
items = MyArrayDefinition.reduce((o, v) => (o[v.item] = v, o), {});
var shops = myArray.reduce((o, v, i, s) => (
s = v[k(v).find(k => k)],
s = o[s] || (o[s] = {
fruit: 0,
vegetable: 0,
}),
k(v).forEach(k => k.includes('item') &&
(s[(i = items[v[k]]).group] += i.score)), o), {});
var result = k(shops).map((k, i) => ({
id: i + 1,
shop: k,
itemGroup: shops[k],
total:Object.values(shops[k]).reduce((a, b) => a + b),
}));
Pretty much like in most of your questions from the last couple of days. :-)
You can map over the data, use Object.assign and delete the itemGroup.
let x = f.map(e => {
e = Object.assign(e, e.itemGroup);
delete e.itemGroup;
return e;
})
console.log(x);
<script>
let f = [{
"id": 1,
"shop": "shop1",
"itemGroup": {
"fruit": 2,
"vegetable": 2
},
"total": 4
},
{
"id": 2,
"shop": "shop2",
"itemGroup": {
"fruit": 0,
"vegetable": 1
},
"total": 1
}
]
</script>
Expounding on your "original question" set the percentages of each item in the store, and answering your "modified question" change the structure of my array, this gives you both by modifying your original code.
let myArray = [{"shop":"shop1","item1":"my apple 1","item2":"my carrot 1"},{"shop":"shop1","item1":"my apple 1","item2":"my carrot 1"},{"shop":"shop2","item1":"my apple 0","item2":"my carrot 0"},{"shop":"shop2","item1":"my apple 0","item2":"my carrot 1"}]
let MyArrayDefinition = [{"item":"my apple 0","color":"red","group":"fruit","score":0},{"item":"my carrot 1","color":"orange","group":"vegetable","score": null},{"item":"my apple 1","color":"red","group":"fruit","score":1},{"item":"my carrot 0","color":"orange","group":"vegetable","score":0}]
let k = Object.keys
let items = MyArrayDefinition.reduce((o, v) => (o[v.item] = v, o), {})
let shops = myArray.reduce(function (o, v, i, s) {
return s = v[k(v).find(function (k) {
return k;
})], s = o[s] || (o[s] = {
fruit: 0,
vegetable: 0
}), k(v).forEach(function (k) {
return k.includes('item') && (s[(i = items[v[k]]).group] += i.score);
}), o;
}, {});
// Helper function that calculates percentage
function percentage (amount, total) {
if (total === 0) { // added check for 0 divisor
return `0%`
}
return `${(amount / total) * 100}%`
}
let result = k(shops).map((k, i) => {
let total = Object.values(shops[k]).reduce((a, b) => a + b) | 0 // added check if number else 0
let fruit = shops[k].fruit | 0 // added check if number else 0
let veg = shops[k].vegetable | 0 // added check if number else 0
return {
id: i + 1,
shop: k,
fruit: fruit,
vegetable: veg,
total: total,
fruitPercentage: percentage(fruit, total),
vegetablePercentage: percentage(veg, total)
}
})
console.log(JSON.stringify(result, null, 2))
/** result from console.log()
*
[
{
"id": 1,
"shop": "shop1",
"fruit": 2,
"vegetable": 2,
"total": 4,
"fruitPercentage": "50%",
"vegetablePercentage": "50%"
},
{
"id": 2,
"shop": "shop2",
"fruit": 2,
"vegetable": 0,
"total": 2,
"fruitPercentage": "100%",
"vegetablePercentage": "0%"
}
]
* */
Using map is the way to transpose the data from one array onto another and run calculations if needed.
// Create a function that takes in your result array
function setPercentage (array) {
// Helper function that calculates percentage
function percentage (amount, total) {
return (amount / total) * 100
}
// Map the results of the input array onto a new array,
// and return the result
return array.map((obj) => {
return {
id: obj.id,
shop: obj.shop,
fruit: percentage(obj.itemGroup.fruit, obj.total),
vegetable: percentage(obj.itemGroup.vegetable, obj.total),
total: obj.total
}
})
}
// pass in the result array from your code...
const shops_by_percentage = setPercentage(result)
console.log(shops_by_percentage)
/** result in the console.log()
*
[
{
'id': 1,
'shop': 'shop1',
'fruit': 50,
'vegetable': 50,
'total': 4
},
{
'id': 2,
'shop': 'shop2',
'fruit': 0,
'vegetable': 100,
'total': 1
}
]
*
* */
Below you can find general solution to your problem.
Using this approach you can create unlimited number of items in your items array as well as unlimited number of groups in your definitions and your code will still work as expected. Lastly your score value acts as weight (when you give some item for example score 2 each occurrence will count as two items).
// Your items
const items = [
{
shop: "shop1",
item1: "my apple 1",
item2: "my carrot 1",
},
{
shop: "shop1",
item1: "my apple 1",
item2: "my carrot 1"
},
{
shop: "shop2",
item1: "my apple 0",
item2: "my carrot 0"
},
{
shop: "shop2",
item1: "my apple 0",
item2: "my carrot 1"
},
];
// Your definitions
const definitions = [
{
item: "my apple 0",
color: "red",
group: "fruit",
score: 0
},
{
item: "my carrot 1",
color: "orange",
group: "vegetable",
score: 1
},
{
item: "my apple 1",
color: "red",
group: "fruit",
score: 1
},
{
item: "my carrot 0",
color: "orange",
group: "vegetable",
score: 0
}
];
function groupShops(items) {
return items.reduce((acc, cur) => {
// Find shop with id of current item in accumulator
const currentShop = acc.find(shop => shop.id === cur.shop);
// Get all shop items
const shopItems = Object.keys(cur)
// Filter shop key as it is shop's ID
.filter(key => key !== 'shop')
// Map keys into values
.map(key => cur[key]);
// If shop already exists in accumulator
if (!!currentShop) {
// Return updated accumulator
return acc
// Remove current shop
.filter(shop => shop !== currentShop)
// And add new copy of current shop with new items to the accumulator
.concat({
id: currentShop.id,
items: currentShop.items.concat(shopItems),
});
}
// If shop doesn't exist in accumulator add it there and return updated accumulator
return acc.concat({
id: cur.shop,
items: shopItems,
});
}, []);
};
function extendItems(shops) {
// Filter items which have score 0 or less
const filterItems = items => items.filter(item => item.score > 0);
// Map though shops
return shops.map(shop => {
// Return updated shop
return {
// Keep shop id
id: shop.id,
// Extend itemIds by the properties stored in the definition and filter them
items: filterItems(shop.items.map(item => definitions.find(definition => definition.item === item))),
}
});
}
function calculateResult(shop, index) {
// Get all available groups
const availableGroups = definitions.reduce((acc, cur) => acc.indexOf(cur.group) > -1 ? acc : acc.concat(cur.group), []);
// Calculate total possible score
const getTotalScore = () => shop.items.reduce((acc, cur) => cur.score + acc, 0);
// Get score of a passed group
const getGroupScore = group => shop.items.reduce((acc, cur) => cur.group === group ? acc + cur.score : acc, 0);
// Loop though each available group and get its score
const resultData = availableGroups.reduce((acc, cur) => {
return {
// Copy data from accumulator
...acc,
// Add new property to the accumulator with a property key {group name} and value {percantage}
[cur]: getGroupScore(cur, shop.items) / getTotalScore(shop.items) * 100,
}
}, {});
// Return result object
return {
// Destruct items of the result object
...resultData,
// Store total items count
total: shop.items.length,
// Store shop id
shop: shop.id,
// Store index
id: index,
}
}
// Groups shops
const groupedShops = groupShops(items);
// Groups shops with extended items
const extendedShops = extendItems(groupedShops);
// You result object
const result = extendedShops.map((shop, index) => calculateResult(shop, ++index));
console.log(result);
I'm trying to get some array data into a particular format so I can use google linechart. But I can't quite get it right.
Right now I have the format
//format [date, id, count
var data = [
["2014-04-01", "1", "100"],
["2014-04-02", "1", "200"],
["2014-04-03", "1", "150"],
["2014-04-04", "1", "5"],
["2014-04-01", "2", "200"],
["2014-04-02", "2", "600"],
["2014-04-03", "2", "15"],
["2014-04-04", "2", "25"],
["2014-04-01", "3", "99"],
["2014-04-02", "3", "85"],
["2014-04-03", "3", "555"],
["2014-04-04", "3", "0"]
];
I need to get it into the format:
var reformatted = [
['Date', '1', '2', '3'],
['2014-04-01', 100, 200, 99],
['2014-04-02', 200, 600, 85],
['2014-04-03', 150, 15, 555],
['2014-04-04', 5, 25, 0]
]);
var graph = [["date"]];
//first element of array to be populated with array of ID's
//always second element of inner arrays
//these will be the lines of the graph
for (var i = 0; i < data.length; i++){
if (graph[0].indexOf(data[i][1]) < 0){
graph[0].push(data[i][1]);
}
}
This puts me in a pretty good place. I get:
Array[1]]
0: Array[4]
0: "date"
1: "1"
2: "2"
3: "3"
But I'm stumped on how to get the rest of the data in the appropriate format. Any ideas?
Tried this. No good result.
for (i = 0; i < data.length; i++){
graph[i + 1] = graph[i + 1] || [];
graph[i + 1].push(data[i][2]);
}
Logic:
First generate this by iterating through the initial array finding unique dates.
[
['Date'],
['2014-04-01'],
['2014-04-02'],
['2014-04-03'],
['2014-04-04']
]
Then convert the generated array as follows again iterating through the initial array finding unique numbers. Also generate list of unique numbers.
[
['Date','1','2','3'],
['2014-04-01'],
['2014-04-02'],
['2014-04-03'],
['2014-04-04']
]
Now iterate through above array, and for each item iterate through the number list and find matches from the initial array where date and number matches. place the matches in the above array. Place a null if not found. You should get the following. I have done this in php but not in javascript.
[
['Date','1','2','3'],
['2014-04-01', null, 100, 200],
['2014-04-02', 100, 400, 500],
['2014-04-03', 200, null, 100],
['2014-04-04', 100, 300, 100]
]
Good Luck!
addition
In php:
$originalData = array(
array("2014-04-01", '1', '200'),
array("2014-04-02", '1', '300'),
array("2014-04-03", '1', '400'),
array("2014-04-04", '1', '200'),
array("2014-04-01", '2', '400'),
array("2014-04-02", '2', '100'),
array("2014-04-03", '2', '200'),
array("2014-04-04", '2', '100'),
array("2014-04-01", '3', '200'),
array("2014-04-02", '3', '600'),
array("2014-04-03", '3', '300'),
array("2014-04-04", '3', '900'),
);
result from second step would be:
$graphData = array(
array('Date','1','2','3'),
array('2014-04-01'),
array('2014-04-02'),
array('2014-04-03'),
array('2014-04-04'),
);
list of numbers would be:
$numbers = array('1','2','3');
I would then do the third step as follows:
$i = 0;
foreach($graphData as $graphDataItem) {
if ($graphDataItem[0]!='Date') { // ignore the first index
$j = 1; // 0 is date column
foreach($numbers as $number) {
foreach($originalData as $originalDataItem) {
// set the value to null until found
if (!isset($graphData[$i][$j]))
$graphData[$i][$j] = null;
if ($originalDataItem[0] == $graphDataItem[0] && // check date match
$originalDataItem[1] == $number) { // check number match
$graphData[$i][$j] = $originalDataItem[2];
break;
}
}
$j++;
}
}
$i++;
}
The resulting $graphData would be:
array
(
0 => array
(
0 => 'Date'
1 => '1'
2 => '2'
3 => '3'
)
1 => array
(
0 => '2014-04-01'
1 => '200'
2 => '400'
3 => '200'
)
2 => array
(
0 => '2014-04-02'
1 => '300'
2 => '100'
3 => '600'
)
3 => array
(
0 => '2014-04-03'
1 => '400'
2 => '200'
3 => '300'
)
4 => array
(
0 => '2014-04-04'
1 => '200'
2 => '100'
3 => '900'
)
)
The above would get you the results in $graphData. However, this would be heavy on processor for larger sets.
Here's an option:
First group all the data by dates, then convert it back into the representation you need.
If you want to automate it a bit more, you could also keep track of the IDs and add the list for the legend automatically.
var data = [
["2014-04-01", "1", "100"],
["2014-04-02", "1", "200"],
["2014-04-03", "1", "150"],
["2014-04-04", "1", "5"],
["2014-04-01", "2", "200"],
["2014-04-02", "2", "600"],
["2014-04-03", "2", "15"],
["2014-04-04", "2", "25"],
["2014-04-01", "3", "99"],
["2014-04-02", "3", "85"],
["2014-04-03", "3", "555"],
["2014-04-04", "3", "0"]
];
var groupByDate = function (data) {
var dataByDate = {};
data.forEach(function (entry) {
var date = entry[0];
var count = entry[2];
if (!(date in dataByDate)) {
dataByDate[date] = [];
}
dataByDate[date].push(count);
});
return dataByDate;
}
var toChartData = function (dataByDate) {
var chartData = [];
for (var date in dataByDate) {
chartData.push([date].concat(dataByDate[date]));
};
return chartData;
};
var byDate = groupByDate(data);
var chartData = toChartData(byDate);
chartData.unshift(['Date', 1, 2, 3]);
console.log(chartData);
Arrays can be rough for things like this, to make it easier it can be best to first convert it to an object to make it easier to work with.
var obj = {};
for(var i=0;i<data.length;i++){
if(!obj[data[i][0]]){ //check if index already exists
obj[data[i][0]] = {}; //create sub object from the index
}
for(var ii=0;ii<data[i].length;ii++){
if(ii!=0){ //since the 0 index is the parent here, ignore it
obj[data[i][0]][ii] = data[i][ii];
}
}
}
This should translate to an object like so:
var data = {
'2014-04-01':{
1:100,
2:200,
3:99
} //continue for the rest
}
So like that you can probably see that converting it into pretty much any other structure will be far easier.
var graph = [['date','1','2','3']];
for(var index in data){
if(data.hasOwnProperty(index)){
var arr = [index];
for(var index2 in data[index]){
if(data[index].hasOwnProperty(index2)){
arr.push(data[index][index2]);
}
}
graph.push(arr);
}
}
Untested and written on the spot, but the concept is there.