Given the following JSON data containing 4 articles by 4 different authors:
const data = {
"DataSet": {
"Article": [
{
"Title": 'A',
"AuthorList": {
"Author": [{
"FirstName": "Al",
"LastName": "Ab",
"Initials": "A"
}, {
"FirstName": "Bi",
"LastName": "By",
"Initials": "B"
}] // Author
} // AuthorList
}, // Article
{
"Title": 'B',
"AuthorList": {
"Author": [{
"FirstName": "Bi",
"LastName": "By",
"Initials": "B"
}, {
"FirstName": "Ch",
"LastName": "Ch",
"Initials": "C"
}] // Author
} // AuthorList
}, // Article
{
"Title": 'C',
"AuthorList": {
"Author": [{
"FirstName": "Ch",
"LastName": "Ch",
"Initials": "C"
}, {
"FirstName": "Al",
"LastName": "Ab",
"Initials": "A"
}] // Author
} // AuthorList
}, // Article
{
"Title": 'D',
"AuthorList": {
"Author": [{
"FirstName": "Da",
"LastName": "Do",
"Initials": "D"
}, {
"FirstName": "Bi",
"LastName": "By",
"Initials": "B"
}] // Author
} // AuthorList
} // Article
] // Articles
} // DataSet
};
I need to answer the following query:
Generate a matrix such that each cell (i, j)
contains say N
the number of articles authored by the author in column and co-authored by author in row. A paper may have more than 2 authors. It may be assumed that JSON is present as a file.
For above example the output would simply look like:
[2, 1, 1, 0]
[1, 3, 1, 1]
[1, 1, 2, 0]
[0, 1, 0, 1]
I have devised the solution below without thinking too much about design and performance:
const fullName = (author) => author["FirstName"] + ' ' + author["LastName"];
const authorMap = {};
const getArticles = (data) =>
data["DataSet"]["Article"];
const getAuthors = (data) =>
data["DataSet"]["Article"]
.reduce((accum, article) => {
return [...accum, ...article["AuthorList"]["Author"].map((author) => {
return fullName(author);
})];
}, []);
const matrix = [];
const initializeMatrix = () => {
const N = Object.keys(authorMap).length;
for (let i = 0; i < N; i++) {
matrix[i] = Array(N).fill(0);
}
};
console.clear();
let authors = getAuthors(data);
authors = Array.from(new Set(authors));
const articles = getArticles(data);
authors.forEach((author, idx) => {
authorMap[author] = idx;
});
initializeMatrix();
articles.forEach((a) => {
const authors = a["AuthorList"]["Author"];
const authorIds = [];
authors.forEach((author) => {
authorIds.push(authorMap[fullName(author)]);
});
for (let i of authorIds) {
for (let j of authorIds) {
matrix[i][j] = matrix[i][j] + 1;
}
}
});
const printMatrix = (matrix) => {
console.log('///////////////');
for (let i = 0; i < matrix[0].length; i++) {
console.log(matrix[i]);
}
};
printMatrix(matrix);
Note:
There can be more than two authors per article.
Questions:
- How to test the code?
- What are the edge cases?
- What if there are millions of articles? how to optimize the code?
Update 1:
The author name is FirstName + LastName
Update 2:
Adding unit tests.
'use strict';
/* eslint-env mocha */
const assert = require('assert');
const m = require('./');
it('should check for invalid data format', () => {
assert.throws(() => m('fixtures/invalid.json'), /Invalid data/);
});
it('should return matrix of size 1 for single author', () => {
const expected = {
'A A': {
'A A': 1,
}
};
assert.deepEqual(m('fixtures/single-author.json'), expected);
});
it('should return matrix of size 2 for two authors', () => {
const expected = {
'A A': {
'A A': 1,
'B B': 1,
},
'B B': {
'A A': 1,
'B B': 1,
}
};
assert.deepEqual(m('fixtures/two-authors.json'), expected);
});
initials
field? Is it first name + last name as you seem to be using? \$\endgroup\$ – Mike Brant Apr 13 '17 at 16:08