I have been struggling a lot with a piece of javascript code recently. The code looks like this:
var bigData = {
"teams" : [
["Team 1", "Team 2" ],
["Team 3", "Team 4" ],
["Team 5", "Team 6" ],
["Team 7", "Team 8" ],
["Team 9", "Team 10" ],
["Team 11", "Team 12" ],
["Team 13", "Team 14" ],
["Team 15", "Team 16" ],
],
results : [[ /* WINNER BRACKET */
[[1,0], [1,0], [0,3], [2,3], [1,5], [5,3], [7,2], [1,2]],
[[1,2], [3,4], [5,6], [7,8]],
[[9,1], [8,2]],
[[1,3]]
]
}
As you might have guessed, it's a jquery plugin for tournaments. The problem is that I don't want to write the teams manually, I want them to be written automatically I have done this, and the code doesn't work, because the while loop is inside the variable (so far I know) :
var count = 1;
var bigData = {
"teams" : [
while (count <= 8) {
["Team ".count, "Team ".count ],
count++;
}
],
results : [[ /* WINNER BRACKET */
[[1,0], [1,0], [0,3], [2,3], [1,5], [5,3], [7,2], [1,2]],
[[1,2], [3,4], [5,6], [7,8]],
[[9,1], [8,2]],
[[1,3]]
]
}
It won't work, and I really don't know what to do.
var bigData = {
"teams" : [],
results : [[ /* WINNER BRACKET */
[[1,0], [1,0], [0,3], [2,3], [1,5], [5,3], [7,2], [1,2]],
[[1,2], [3,4], [5,6], [7,8]],
[[9,1], [8,2]],
[[1,3]]
]] };
for( var i=1 ; i<16 ; i+=2 )
bigData.teams.push(['Team '+i,'Team '+(i+1)]);
console.log(JSON.stringify(bigData));
In console:
{"teams":[["Team 1","Team 2"],["Team 3","Team 4"],["Team 5","Team 6"],["Team 7","Team 8"],["Team 9","Team 10"],["Team 11","Team 12"],["Team 13","Team 14"],["Team 15","Team 16"]],"results":[[[[1,0],[1,0],[0,3],[2,3],[1,5],[5,3],[7,2],[1,2]],[[1,2],[3,4],[5,6],[7,8]],[[9,1],[8,2]],[[1,3]]]]}
Try this:
var count = 1;
var data = [];
while (count <= 16) {
data.push(["Team " + (count++).toString(), "Team " + count.toString() ]);
count++;
}
var bigData = {
"teams" : data
}
It builds the array by pushing the teams into it. Note the concatenation of count as a string to the team name, plus the ++ on the first count to iterate it to the next team (thus producing Team 1 and Team 2, not Team 1 and Team 1).
and the code doesnt work, because the while loop is inside the variable
Yes, you cannot put control flow statements in the midst of an object literal. Instead, you will need to start with an empty array, and then fill that with values via assignments (or push):
var teams = [];
for (var i=1; i<=16; )
teams.push(['Team '+i++,'Team '+i++]);
var bigData = {
teams: teams,
results: …
};
The other answers provided here build a list of teams using a set number (8, 16, whatever).
You can get a little cheeky and generate the list of teams off the teams in the actual results instead, which will future-proof your script against your league size expanding. Code spends most of its lifetime in maintenance, if you can spend just a few extra minutes to make your stuff more data-driven, you'll save plenty of time over the long haul.
Since you're using jquery, I'll make some use of the functions in that library here:
var bigData = {
results : [
[ /* WINNER BRACKET */
[[1,0], [1,0], [0,3], [2,3], [1,5], [5,3], [7,2], [1,2]],
[[1,2], [3,4], [5,6], [7,8]],
[[9,1], [8,2]],
[[1,3]]
]
]
};
function flatten(n) {
return $.map(n, function(x){
return $.isArray(x) ? flatten(x) : x;
});
}
bigData.teams = $.map(
$.unique(flatten(bigData.results)).sort(),
function(num) { return "Team "+num; }
);
Console output:
> JSON.stringify(bigData)
"{
"results":[[[[1,0],[1,0],[0,3],[2,3],[1,5],[5,3],[7,2],[1,2]],[[1,2],[3,4],[5,6],[7,8]],[[9,1],[8,2]],[[1,3]]]],
"teams":["Team 0","Team 1","Team 2","Team 2","Team 2","Team 3","Team 4","Team 5","Team 6","Team 7","Team 8","Team 8","Team 9"]
}"
Related
I'm using an ajax request to grab some XML data which I then need to push into a chart in fusioncharts.
The XML data is formatted as [time taken], [work done], [which team done for], [who did it] (see below).
I'm iterating over the XML and then building the array using the code below:
//Time Recorded
if (columnidchecker == 7781) {
timearray.push($j(this).find('displayData').text());
temp1 = $j(this).find('displayData').text();
}
//Type of Activity
if (columnidchecker == 7782) {
activityarray.push($j(this).find('displayData').text());
temp2 = $j(this).find('displayData').text();
}
//Team Done For
if (columnidchecker == 7783) {
subjectarray.push($j(this).find('displayData').text());
temp3 = $j(this).find('displayData').text();
}
//Name
if (columnidchecker == 7777) {
internalclientarray.push($j(this).find('displayData').text());
temp4 = $j(this).find('userDisplayName').text();
}
});
//PUSH INTO A NEW ARRAY WHICH CAN THEN BE SORTED AND DE-DUPED WITH TIME COMBINED AGAINST ACTIVITY / TEAM.
objectarray.push([temp1, temp2, temp3, temp4]);
This builds an array of entries from the XML which basically outputs to something which looks like this:
0: (4) ["1.50", "Ad-hoc queries or calls", "Team 1", "James"]
1: (4) ["2.50", "Ad-hoc queries or calls", "Team 1", "James"]
2: (4) ["1.00", "Advice", "Team 2", "James"]
3: (4) ["3.50", "Meeting (External 3rd Party)", "Team 1", "James"]
4: (4) ["1.20", "Administration", Team 2", "James"]
5: (4) ["5.50", "Advice", "Team 1", "John"]
I'm trying to build a chart in fusioncharts which needs the format as shown below (ignore foot stuffs - it's taken straight from the fusioncharts help pages!).
{
"chart": {
"theme": "fusion",
"caption": "Revenue split by product category",
"subCaption": "For current year",
"xAxisname": "Quarter",
"yAxisName": "Revenues (In USD)",
"showSum": "1",
"numberPrefix": "$"
},
"categories": [
{
"category": [
{
"label": "Q1"
},
{
"label": "Q2"
},
{
"label": "Q3"
},
{
"label": "Q4"
}
]
}
],
"dataset": [
{
"seriesname": "Food Products",
"data": [
{
"value": "11000"
},
{
"value": "15000"
},
{
"value": "13500"
},
{
"value": "15000"
}
]
},
{
"seriesname": "Non-Food Products",
"data": [
{
"value": "11400"
},
{
"value": "14800"
},
{
"value": "8300"
},
{
"value": "11800"
}
]
}
]
}
The problem i'm having is that I cannot work out how to take the array of data with times, activity, team, name and push them into categories.
I think the first step is to create a new array of names which can be pushed into the "Category" data field in fusioncharts.
I then need a way in which to take the times being recorded against each activity and for each team and make sure it's assigned to the right person within the stacked bar chart and combine the amount of time spent. (i.e. "James" spent a total of 4 hours doing "Ad Hoc Queries and Calls" for Team 1 but this is split across two time entries so I need a way in which to combine them into one.)
Any help on this would be massively appreciated.
I can de-dupe the names to create a new array by using the following code:
namesarray.push(temp4);
uniq = [...new Set(namesarray)];
but after that it starts getting pretty complicated.
Maybe this can help you along the way. It's probably not exactly in the form you want it, but it demonstrates how you could break the problem down into smaller parts.
Pseudo-code:
get the unique names.
get the unique "task" names (for lack of a
better word)
for each unique person name:
3.1. get the data rows for that person
3.2 for each of all unique tasks names:
find the person data rows matching the task name
sum the duration of those data rows
const testData = [
[
"1.50",
"Ad-hoc queries or calls",
"Team 1",
"James"
],
[
"2.50",
"Ad-hoc queries or calls",
"Team 1",
"James"
],
[
"1.00",
"Advice",
"Team 2",
"James"
],
[
"3.50",
"Meeting (External 3rd Party)",
"Team 1",
"James"
],
[
"1.20",
"Administration",
"Team 2",
"James"
],
[
"5.50",
"Advice",
"Team 1",
"John"
]
];
const columnIndexByName = {
TASK_DURATION: 0,
TASK_NAME: 1,
FOR_WHICH_TEAM: 2,
PERSON_DOING_TASK: 3
};
const sum = (acc, next) => acc + next;
const uniqueNames = [...new Set(testData.map(row => row[columnIndexByName.PERSON_DOING_TASK])) ];
const uniqueTaskNames = [...new Set(testData.map(row => row[columnIndexByName.TASK_NAME])) ];
let result = {};
uniqueNames.forEach(personName => {
const personDataRows = testData.filter(row => row[columnIndexByName.PERSON_DOING_TASK] === personName);
let taskDurationsByTaskName = {};
uniqueTaskNames.forEach(taskName => {
const taskRows = personDataRows.filter(row => row[columnIndexByName.TASK_NAME] === taskName);
const taskDurations = taskRows.map(row => Number.parseFloat( row[columnIndexByName.TASK_DURATION] ));
const taskTotalDuration = taskDurations.reduce(sum, 0);
taskDurationsByTaskName[taskName] = taskTotalDuration;
})
result[personName] = taskDurationsByTaskName;
})
const renderData = data => document.querySelector("#output").innerHTML = JSON.stringify(data, null, 2);
renderData(result);
<pre id="output"></pre>
Given a JavaScript object which represents a JSON like so -
[
{
"Id": "8868dfdd-9b4e-4bad-a4ce-ecae6a3cc828",
"Name": "Company 1",
"Locations": [
{
"Id": "bd017b9c-b62e-43aa-9f00-c164a855eed1",
"Name": "Location 1",
"Departments": [
{
"Id": "c9e4afe3-bbdb-474f-9062-2935025bfa2e",
"Name": "Department 1",
"Employees": [
{
"Id": "92c3a085-5712-422d-8b0f-922b57889c4f",
"Name": "Employee 1",
"Title": "FrontEnd Engineer",
"Location": "New York",
"Photo": ""
}
]
}
]
}
]
}
]
I want to filter this data structure by employee name, given that there might be multiple company, location, department. Here is my attempt at it but clearly it is not working due to my understanding of how Array.filter or Array.reduce works.
filterContacts = search => {
if (search.trim() === "") {
this.setState({ filteredContacts: null, search: search });
} else {
let filteredArray = this.state.contacts.reduce((f, c) => {
let clone = [];
for (let i = 0; i < c.Locations.length; i++) {
const l = c.Locations[i];
for (let j = 0; j < l.Departments.length; j++) {
const d = l.Departments[j];
for (let k = 0; k < d.Employees.length; k++) {
const e = d.Employees[k];
if (e.Name.search(new RegExp(search, "i") > -1)) {
clone.push(l);
}
}
}
}
return clone;
}, []);
this.setState({ filteredContacts: filteredArray, search: search });
}
};
Any help would be much appreciated. Thanks.
When you use:
let clone = [];
at the top of the reduce() callback, you throw away the accumulator — the array that keeps getting passed in the loop which is being passed as f in your code. You should use the same reduce accumulator each time and push into it. At the end you'll have an array of all the values:
let arr = [{"Id": "8868dfdd-9b4e-4bad-a4ce-ecae6a3cc828","Name": "Company 1","Locations": [{"Id": "bd017b9c-b62e-43aa-9f00-c164a855eed1","Name": "Location 1","Departments": [{"Id": "c9e4afe3-bbdb-474f-9062-2935025bfa2e","Name": "Department 1","Employees": [{"Id": "92c3a085-5712-422d-8b0f-922b57889c4f","Name": "Employee 1","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}]}]}]}]
let emp = arr.reduce((f, obj) => {
obj.Locations.forEach(location =>
location.Departments.forEach(department =>
f.push(...department.Employees.filter(emp => emp.Name == "Employee 1"))
)
)
return f
}, []) // <-- this array will get passed to every loop as `f`
console.log(emp)
EDIT based on comment
If you want to persevere the structure you can filter the arrays based on the length of the filtered array below them. Here's an example with some extra data see the filtering work, The first one is completely filtered the third has two employees with the same name. Basically it will preserve any item the has location that has a department that has a matching employee:
let arr = [
{"Id": "someother","Name": "Company 2","Locations": [{"Id": "loc2Id","Name": "Location 2","Departments": [{"Id": "d2","Name": "Department 2","Employees": [{"Id": "emp","Name": "Employee 2","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}]}]}]},
{"Id": "8868dfdd-9b4e-4bad-a4ce-ecae6a3cc828","Name": "Company 1","Locations": [{"Id": "bd017b9c-b62e-43aa-9f00-c164a855eed1","Name": "Location 1","Departments": [{"Id": "c9e4afe3-bbdb-474f-9062-2935025bfa2e","Name": "Department 1","Employees": [{"Id": "92c3a085-5712-422d-8b0f-922b57889c4f","Name": "Employee 1","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}]}]}]},
{"Id": "someother","Name": "Company 2","Locations": [{"Id": "loc2Id","Name": "Location 2","Departments": [{"Id": "d2","Name": "Department 2","Employees": [{"Id": "emp","Name": "Employee 1","Title": "FrontEnd Engineer","Location": "New York","Photo": ""}, {"Id": "emp","Name": "Employee 1","Title": "FrontEnd Engineer 2","Location": "New York","Photo": ""}]}]}]},
]
let f = []
let emp = arr.filter(arr =>
arr.Locations.filter(location =>
location.Departments.filter(department => {
let emp = department.Employees.filter(emp => emp.Name == "Employee 1")
return emp.length ? emp: false
}
).length
).length
) // <-- this array will get passed to every loop as `f`
console.log(emp)
Here is another short version using map:
var rx=new RegExp(search,'i'),emp=[];
obj.map(c=>
c.Locations.map(l=>
l.Departments.map(d=>
d.Employees.map(e=>
{if(e.Name.match(rx)) emp.push(e)}
))));
search contains the case-insensitive search pattern. The result is emp, an array of employee objects.
As mentioned above, map is not really necessary and could be replaced by forEach, but in my opinion it is easier to write and does not not really cause significantly more overhead.
Edit, this time using reduce():
It’s Christmas and with too much time on my hands I’ve been playing around further. The following solution will filter out the sought employees without showing their non-matching colleagues and leaving the original array intact:
const rd=(prop,fun)=>
(a,el)=>{
var arr=el[prop].reduce(fun,[]);
if(arr.length){
var r=Object.assign({},el);
// alternatively: use spread operator
// var r={...el};
r[prop]=arr;a.push(r);}
return a;}
var rx=new RegExp('employee 1','i');
var f=ma.reduce(
rd('Locations',
rd('Departments',
rd('Employees',(a,e)=>{
if(e.Name.match(rx))
a.push(e);
return a;}
,[]),[]),[]),[]);
f will contain an array containing only those locations, departments and employees where the employees will match the regular expression rx.
rd() is a generator function returning the actual filter functions that are being used at three different reduce-levels.
The static Object.assign() function is an easy way of generating a shallow object copy (similar to the slice() method for arrays).
I have the array range with 3 columns and 10 rows.
How can I concatenate the contents of column 1 with column 2 and push them to a new range dataEnome?
I'm using the following loop, but it isn't very efficient:
var dataEnome =[];
for (i=0; i<range.length; i++){
dataEnome.push(range[i][0])+(range[i][1]);
};
The range looks like this:
For data mapping you can consider using the array.map API.
Example:
var range =
[
[ 'col1.a', 'col2.1', 'c' ],
[ 'col1.b', 'col2.2', '3' ],
[ 'col1.c', 'col2.3', '6' ],
[ 'col1.d', 'col2.4', '9' ],
[ 'col1-e', 'col2.5', '1c' ],
[ 'col1-f', 'col2.6', '6c' ],
[ 'col1-g', 'col2.7', '7c' ],
[ 'col1-h', 'col2.8', '8c' ],
[ 'col1-i', 'col2.9', '9c' ],
[ 'col1-j', 'col2.10', '0c' ],
];
var dataEnome =range.map(row => { return row[0] + row[1]});
console.log(dataEnome);
For more example usages for map;
See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
If you want to concatenate each record from both column
You may do something like this:
var dataEnome =[];
for (i=0; i<range.getValues().length; i++){
dataEnome.push(range.getValues()[i][0]+range.getValues()[i][1]);
};
Hope this will help you.
Thanks.
... besides dataEnome.push(range[i][0])+(range[i][1]); does most probably feature a broken syntax ...
shouldn't it be dataEnome.push(range[i][0] + range[i][1]); or dataEnome.push(range[i][0].concat(range[i][1]));
... I do not consider the OP's solution to be not that efficient.
One only could transform it into a reusable and more functional style ...
function collectConcatenatedFirstTwoRowColumns(collector, row) {
collector.push(row[0].concat(" / ", row[1]));
return collector;
}
var
range = [
["0, 0", "0, 1", "0, 2"],
["1, 0", "1, 1", "1, 2"],
["2, 0", "2, 1", "2, 2"],
["3, 0", "3, 1", "3, 2"],
["4, 0", "4, 1", "4, 2"],
["5, 0", "5, 1", "5, 2"]
],
dataEnome = range.reduce(collectConcatenatedFirstTwoRowColumns, []);
console.log(dataEnome);
I'm creating an index file in JSON, which I'm using as a sort-of-database index for a javascript application I'm working on.
My index will look like this:
{
"_id": "acomplex_indices.json",
"indexAB": {
"title": {
"Shawshank Redemption": [
"0"
],
"Godfather": [
"1"
],
"Godfather 2": [
"2"
],
"Pulp Fiction": [
"3"
],
"The Good, The Bad and The Ugly": [
"4"
],
"12 Angry Men": [
"5"
],
"The Dark Knight": [
"6"
],
"Schindlers List": [
"7"
],
"Lord of the Rings - Return of the King": [
"8"
],
"Fight Club": [
"9"
],
"Star Wars Episode V": [
"10"
],
"Lord Of the Rings - Fellowship of the Ring": [
"11"
],
"One flew over the Cuckoo's Nest": [
"12"
],
"Inception": [
"13"
],
"Godfellas": [
"14"
]
},
"year": {
"1994": [
"0",
"3"
],
"1972": [
"1"
],
"1974": [
"2"
],
"1966": [
"4"
],
"1957": [
"5"
],
"2008": [
"6"
],
"1993": [
"7"
],
"2003": [
"8"
],
"1999": [
"9"
],
"1980": [
"10"
],
"2001": [
"11"
],
"1975": [
"12"
],
"2010": [
"13"
],
"1990": [
"14"
]
}
}
}
So for every keyword (like Pulp Fiction), I'm storing the matching document-id(s).
My problem is with integers/numbers/non-string data, like the release year in the above example. This is stored as a string, while I had hoped it would be stored as a number.
I'm creating the index entries like this:
// indices = the current index file
// doc = the document to update the index with
// priv.indices = all indices defined for this application instance
// priv.indices.fields = index fields e.g. "year", "director", "title"
// priv.indices.name = name of this index
priv.updateIndices = function (indices, doc) {
var i, j, index, value, label, key, l = priv.indices.length;
// loop all indices to add document
for (i = 0; i < l; i += 1) {
index = {};
index.reference = priv.indices[i];
index.reference_size = index.reference.fields.length;
index.current = indices[index.reference.name];
for (j = 0; j < index.reference_size; j += 1) {
label = index.reference.fields[j]; // like "year"
value = doc[label]; // like 1985
// if document has a label field (e.g. doc.year = 1985)
if (value !== undefined) {
// check if the index file already contains an entry for 1985
index.current_size = priv.getObjectSize(index.current[label]);
if (index.current_size > 0) {
// check if the document id is already in the index
// in case the data is updated (e.g. change 1982 to 1985)
key = priv.searchIndexByValue(
index.current[label],
doc._id,
"key"
);
if (!!key) {
delete index.current[label][key];
}
}
// create a new array if 1985 is not in the index yet
if (index.current[label][value] === undefined) {
index.current[label][value] = [];
}
// add the document id to an existing entry
index.current[label][value].push(doc._id);
}
}
}
return indices;
};
This works fine, except that fields I want to store as non-strings (integers, numbers or datetime), like the year in the above example end up as strings in my index.
Question:
Is it at all possible to store "non-string" types in a JSON document? If so, can I also store the key of a key/value pair as a "non-string" element.
If not, would I have to add a parameter to my index definitions declaring the type of each key in order to modify the key-string when I run into it or is there a better way to do it?
Thanks!
Is it at all possible to store "non-string" types in a JSON document?
Yes. The value of a property can be a string, number, boolean, object, array or null (undefined is a notable exception - it's a native JavaScript type but it's not a valid JSON value).
Can I also store the key of a key/value pair as a "non-string" element?
No. The key name must always be a string. However, that doesn't mean you can't parse that string into some other JavaScript type. For example, if you have a string but need a number, you can use the parseInt function, or the unary + operator.
See the JSON grammar for more detail.
no you can't, in JSON keys are strings.
the best you can do is storing string representations of those keys, wether integer or objects(more complicated, you have to build a serialization function).
If you want to use only consecutive integers keys starting from 0, then you can use arrays.
According to the json spec, you can have a number anywhere you could have a value. So the key of an object must be a string, but the value can be a number. Also any of the values in an array can be a number.
The spec is beside the point though; I believe the issue is this line:
index.current[label][value].push(doc._id);
When you read doc._id, that is a string. If you want to store it in the JSON as a number, you need to cast it:
index.current[label][value].push(parseInt(doc._id, 10));
Also note that having just numbers as IDs is not valid HTML.
I have got an array that contains data in hierarchical form such as:
Level 2
chapter 1
chapter 2
Level 4
chapter 1
chapter 2
Level 1
chapter 1
chapter 2
Level 3
chapter 1
chapter 2
If I just call array.sort(), the hierarchy gets disturbed. So, I have to develop my own way of sorting items. The thing I can't understand is, how would I compare two levels such that I would know that level 1 is less than level 2 and it should be at the top index of the array?
You really shouldn't be using a flat array. You lose all the hierarchical information. Something like this would be better:
//I've deliberately made these unsorted to show you that sorting works
levels = ["Level 4", "Level 3", "Level 1", "Level 2"];
data = {
"Level 3" : ["chapter 1", "chapter 2"],
"Level 1" : ["chapter 2", "chapter 1"],
"Level 2" : ["chapter 2", "chapter 1"],
"Level 4" : ["chapter 1", "chapter 2"]
};
levels.sort();
for(var i = 0 i < levels.length; i++) {
console.log(levels[i]);
var chapters = data[levels[i]];
chapters.sort();
for(var j = 0; j < chapters.length; j++) {
console.log(chapters[j]);
}
}
EDIT
Rob suggested using levels.sort(function(x,y){return x.localeCompare(y)}) instead of the regular .sort(). The former will sort ["abc", "Abcd", "Ab"] to ["Ab", "abc", "Abcd"] instead of ["Ab", "Abcd", "abc"].
This should reformat the flat PHP array to the nicer JS object:
var fromPHP = ['Level 2','chapter 1','chapter 2','Level 4','chapter 1','chapter 2','Level 1','chapter 1','chapter 2','Level 3','chapter 1','chapter 2'];
var levels = [],
betterArray = [fromPHP[0]],
currentLevel=betterArray[0];
for (var i=1;i<fromPHP.length;i++) {
if (fromPHP[i].substr(0,5) == 'Level') {
currentLevel = [];
levels.push(fromPHP[i]);
betterArray[fromPHP[i]] = currentLevel;
} else {
currentLevel.push(fromPHP[i]);
}
}
Should give the following levels and betterArray:
// levels:
['Level 4','Level 3','Level 1','Level 2']
// betterArray:
{
'Level 2': ['chapter 1','chapter 2'],
'Level 4': ['chapter 1','chapter 2'],
'Level 1': ['chapter 1','chapter 2'],
'Level 3': ['chapter 1','chapter 2']
}
Now you can run whatever sorting you want on the subarrays and get what you wanted.