Since no one cares.
Depth - how deep record chain goes in one branch,
Record count - how many records are in total
Time is in milliseconds.
There's obviously a lot of noise in these results.
Example: Depth 5 record count 500 means 100 branches of 5 records each
[Element] --> [Child] --> [Child] --> [Child] --> [Child] --> [Child]
[Element] --> [Child] --> [Child] --> [Child] --> [Child] --> [Child]
...
[97 more]
...
[Element] --> [Child] --> [Child] --> [Child] --> [Child] --> [Child]
Performance:
+-------+--------------+------------------------------------------+------------------------------------------------+---------------------------------------+----------------------------+----------------------------+-----------------------+
| Depth | Record count | Chrome (Parent-child-orphan) shuffled ms | Chrome (Bulk read grep with clean) shuffled ms | Chrome (Bulk read grep with clean) ms | Chrome (Bulk read grep) ms | Chrome (Layer by layer) ms | Chrome (Bulk read) ms |
+-------+--------------+------------------------------------------+------------------------------------------------+---------------------------------------+----------------------------+----------------------------+-----------------------+
| 1 | 25 | 650 | 659 | 464 | 0 | 0 | 0 |
| 1 | 50 | 614 | 707 | 417 | 0 | 0 | 0 |
| 1 | 75 | 577 | 683 | 448 | 0 | 0 | 0 |
| 1 | 100 | 659 | 679 | 442 | 0 | 0 | 0 |
| 1 | 200 | 604 | 819 | 514 | 0 | 0 | 0 |
| 1 | 500 | 759 | 1217 | 766 | 0 | 0 | 0 |
| 1 | 1000 | 1696 | 2310 | 1421 | 0 | 0 | 0 |
| 5 | 25 | 373 | 669 | 502 | 651 | 806 | 865 |
| 5 | 50 | 464 | 661 | 534 | 893 | 932 | 1097 |
| 5 | 75 | 462 | 676 | 588 | 641 | 939 | 1348 |
| 5 | 100 | 581 | 679 | 599 | 677 | 1025 | 1764 |
| 5 | 200 | 562 | 772 | 629 | 761 | 1234 | 2942 |
| 5 | 500 | 712 | 1359 | 1007 | 1339 | 1908 | 13299 |
| 5 | 1000 | 1274 | 2792 | 2072 | 2900 | 3733 | 32602 |
| 10 | 20 | 428 | 647 | 429 | 601 | 948 | 790 |
| 10 | 50 | 386 | 670 | 435 | 576 | 975 | 930 |
| 10 | 70 | 597 | 710 | 440 | 631 | 1023 | 1284 |
| 10 | 100 | 432 | 695 | 463 | 653 | 1001 | 1321 |
| 10 | 200 | 654 | 809 | 501 | 684 | 1357 | 3130 |
| 10 | 500 | 758 | 1356 | 826 | 1208 | 2116 | 11246 |
| 10 | 1000 | 1243 | 2765 | 1661 | 2524 | 3687 | 33714 |
| 20 | 20 | 531 | 634 | 435 | 612 | 1198 | 848 |
| 20 | 40 | 574 | 668 | 425 | 687 | 1176 | 1003 |
| 20 | 60 | 545 | 690 | 478 | 620 | 1245 | 1093 |
| 20 | 100 | 552 | 735 | 448 | 671 | 1343 | 1693 |
| 20 | 200 | 711 | 837 | 486 | 785 | 1584 | 3134 |
| 20 | 500 | 965 | 1417 | 914 | 1125 | 2478 | 12825 |
| 20 | 1000 | 1510 | 2985 | 1763 | 2621 | 3956 | 35619 |
| 50 | 50 | 513 | 679 | 411 | 571 | 1854 | 1129 |
| 50 | 100 | 529 | 747 | 435 | 684 | 1984 | 1523 |
| 50 | 200 | 677 | 860 | 458 | 704 | 1928 | 3107 |
| 50 | 500 | 1065 | 1254 | 781 | 1428 | 3081 | 12592 |
| 50 | 1000 | 1868 | 2768 | 1595 | 2765 | 4908 | 35515 |
| 100 | 100 | 468 | 676 | 467 | 729 | 3071 | 1835 |
| 100 | 200 | 477 | 812 | 556 | 776 | 3232 | 3894 |
| 100 | 500 | 848 | 1379 | 855 | 1330 | 4215 | 13687 |
| 100 | 1000 | 2054 | 2670 | 1998 | 2577 | 6415 | 37376 |
| 200 | 200 | 0 | 0 | 0 | 823 | 5336 | 3771 |
| 200 | 400 | 0 | 0 | 0 | 1197 | 6243 | 10169 |
| 200 | 1000 | 0 | 0 | 0 | 3080 | 9392 | 36198 |
+-------+--------------+------------------------------------------+------------------------------------------------+---------------------------------------+----------------------------+----------------------------+-----------------------+
'Parent-child-orphan'™ by myself wins
Could someone please beat this algorithm.
I am shuffling data before processing.
Here's the code.
function ComposeRecordsToTreeStructure(results, tableArray, columnArray, options)
{
if (results.rows.length > 0) {
if (options.parentLayerIdList == undefined){
options.parentLayerIdList = options.masterIdList;
}
if (options.orphans == undefined){
options.orphans = [];
}
var childRecordIdArray = [];
if (options.runningOnOrphans)
{
if (options.orphans.length > 0)
{
for (var j = 0; j < options.orphans.length; j++)
{
var rowRecord = options.orphans[j];
var parentPosition = $.inArray(rowRecord.Fields[options.parentIdColumn], options.parentLayerIdList);
if (parentPosition != -1)
{
AttachPassedChildRecordsToParents(rowRecord, options.parentLayerIdList[parentPosition], options.knockoutContextName);
childRecordIdArray.push(rowRecord.Fields['Id']);
childRecordIdArray = AddChildRecordsToNextParentList(rowRecord, childRecordIdArray);
}
else
{
var recentParentPosition = $.inArray(rowRecord.Fields[options.parentIdColumn], childRecordIdArray);
if (recentParentPosition != -1)
{
AttachPassedChildRecordsToParents(rowRecord, childRecordIdArray[recentParentPosition], options.knockoutContextName);
childRecordIdArray.push(rowRecord.Fields['Id']);
childRecordIdArray = AddChildRecordsToNextParentList(rowRecord, childRecordIdArray);
}
else
{
var matchingOrphans = $.grep(options.orphans, function(item){ return item.Fields['Id'] == rowRecord.Fields[options.parentIdColumn]; });
if (matchingOrphans.length > 0)
{
AttachToOrphans(rowRecord, matchingOrphans);
}
}
}
}
options.orphans = $.grep(options.orphans, function(item){
return $.inArray(item.Fields['Id'], childRecordIdArray) == -1;
});
}
}
else
{
for(var i = 0; i < results.rows.length; i++)
{
var rowRecord = results.rows.item(i);
if (rowRecord[options.parentIdColumn] == '' || rowRecord[options.masterIdColumn] == '' || rowRecord[options.masterIdColumn] == rowRecord['Id'])
{
rowRecord.isInvalid = true;
}
if (rowRecord.isInvalid == true)
{
var parentPosition = $.inArray(rowRecord[options.masterIdColumn], options.parentLayerIdList);
if (parentPosition != -1)
{
var childRecord = AttachChildRecordsToParents(rowRecord, options.parentLayerIdList[parentPosition], options.knockoutContextName);
childRecordIdArray.push(childRecord.Fields['Id']);
}
}
else
{
var parentPosition = $.inArray(rowRecord[options.parentIdColumn], options.parentLayerIdList);
if (parentPosition != -1)
{
var childRecord = AttachChildRecordsToParents(rowRecord, options.parentLayerIdList[parentPosition], options.knockoutContextName);
childRecordIdArray.push(childRecord.Fields['Id']);
}
else
{
var recentParentPosition = $.inArray(rowRecord[options.parentIdColumn], childRecordIdArray);
if (recentParentPosition != -1)
{
var childRecord = AttachChildRecordsToParents(rowRecord, childRecordIdArray[recentParentPosition], options.knockoutContextName);
childRecordIdArray.push(childRecord.Fields['Id']);
}
else
{
var matchingOrphans = $.grep(options.orphans,function(item){ return item.Fields['Id'] == rowRecord[options.parentIdColumn]; });
var composedObject = ComposeChildObject(rowRecord);
if (matchingOrphans.length > 0)
{
AttachToOrphans(composedObject, matchingOrphans);
}
else
{
options.orphans.push(composedObject);
options.runningOnOrphans = true;
}
}
}
}
}
}
if (options.orphans.length > 0)
{
options.parentLayerIdList = childRecordIdArray;
ComposeRecordsToTreeStructure(results, tableArray, columnArray, options);
}
}
}
function AddChildRecordsToNextParentList(childRecord, childRecordIdArray)
{
if (childRecord.Children != undefined)
{
for(var i = 0; i < childRecord.Children().length; i++)
{
childRecordIdArray.push(childRecord.Children()[i].Fields['Id']);
if (childRecord.Children()[i].Children != undefined)
{
AddChildRecordsToNextParentList(childRecord.Children()[i], childRecordIdArray);
}
}
}
return childRecordIdArray;
}
function RowsToListDataStructure(results)
{
var array = [];
for(var i = 0; i < results.rows.length; i++)
{
array.push(results.rows.item(i));
}
return array;
}
function AttachLayerOfChildRecords(results, tableArray, columnArray, options)
{
var childRecordArray = [];
if (results.rows.length > 0) {
for(i = 0; i < results.rows.length; i++){
var childRecord = AttachChildRecordsToParents(results.rows.item(i), results.rows.item(i)[options.parentIdColumn], options.knockoutContextName);
childRecordArray.push(childRecord);
}
}
return childRecordArray;
}
function AttachChildRecordsToParents(recordRow, id, knockoutContextName)
{
var childTreeOptions = {id : id, knockoutContextName : knockoutContextName, results: []};
findObjectsInChildTreeById(childTreeOptions);
if (childTreeOptions.results.length > 0) {
var childRecord = AttachChildRecord(recordRow, childTreeOptions.results);
}
return childRecord;
}
function ComposeChildObject(recordRow)
{
var recordObject = { Fields: {}, SetFields: [], Insert: false };
for (var field in recordRow) {
recordObject.Fields[field] = field === "Id" && recordRow.PrimaryRowId ? recordRow.PrimaryRowId : recordRow[field];
}
return recordObject;
}
function AttachChildRecord(recordRow, pageObjParentResults)
{
var recordObject = ComposeChildObject(recordRow);
for(var i = 0; i < pageObjParentResults.length; i++){
if(pageObjParentResults[i].Children == undefined)
{
pageObjParentResults[i].Children = ko.observableArray([]);
}
if ($.grep(pageObjParentResults[i].Children, function(children){ return children['Id'] == recordObject['Id'];}).length == 0)
pageObjParentResults[i].Children.push(recordObject);
}
return recordObject;
}
function AttachPassedChildRecordsToParents(recordObject, id, knockoutContextName)
{
var childTreeOptions = {id : id, knockoutContextName : knockoutContextName, results: []};
findObjectsInChildTreeById(childTreeOptions);
if (childTreeOptions.results.length > 0) {
var childRecord = AttachPassedChildRecord(recordObject, childTreeOptions.results);
}
return childRecord;
}
function AttachPassedChildRecord(recordObject, pageObjParentResults)
{
for(var i = 0; i < pageObjParentResults.length; i++){
if(pageObjParentResults[i].Children == undefined)
{
pageObjParentResults[i].Children = ko.observableArray([]);
}
if ($.grep(pageObjParentResults[i].Children, function(children){ return children['Id'] == recordObject['Id'];}).length == 0)
pageObjParentResults[i].Children.push(recordObject);
}
return recordObject;
}
function AttachToOrphans(recordObject, orphanParents)
{
for(var i = 0; i < orphanParents.length; i++){
if (orphanParents[i].Children == undefined)
{
orphanParents[i].Children = ko.observableArray([]);
orphanParents[i].Children.push(recordObject);
}
}
}
function findObjectsInChildTreeById(options)
{
if (options.item == undefined)
{
for(var item in PageObj[options.knockoutContextName]())
{
findObjectsInChildTreeById({item : PageObj[options.knockoutContextName]()[item], id : options.id, results: options.results});
}
}
else
{
if (typeof options.item.Fields['Id'] == 'function')
{
if (options.item.Fields['Id']() == options.id)
options.results.push(options.item);
}
else
{
if (options.item.Fields['Id'] == options.id)
options.results.push(options.item);
}
if (options.item.Children!=undefined)
{
for(var item in options.item.Children())
{
findObjectsInChildTreeById({item : options.item.Children()[item], id : options.id, results: options.results});
}
}
}
}
The next best thing is changing that rows.item(i) structure to list, that then can be JQuery 'grepped' and then processing layer by layer:
Bulk read grep with clean:
function ComposeRecordsToTreeStructure(results, tableArray, columnArray, options)
{
if (results.rows.length > 0) {
if (options.parentLayerIdList == undefined){
options.parentLayerIdList = options.masterIdList;
}
var childRecordIdArray = [];
var isThereNextLayer = false;
if (options.listResult == undefined){
options.listResult = RowsToListDataStructure(results);
}
for(var i = 0; i < options.parentLayerIdList.length; i++)
{
var children = $.grep(options.listResult, function(item){ return item[options.parentIdColumn] == options.parentLayerIdList[i]});
for(var ii = 0; ii < children.length; ii++)
{
AttachChildRecordsToParents(children[ii], options.parentLayerIdList[i], options.knockoutContextName)
childRecordIdArray.push(children[ii]['Id']);
if (isThereNextLayer == false){
isThereNextLayer = ($.grep(options.listResult, function(item){ return children[ii]['Id'] == item[options.parentIdColumn];}).length > 0)
}
}
}
if (isThereNextLayer)
{
options.parentLayerIdList = childRecordIdArray;
ComposeRecordsToTreeStructure(results, tableArray, columnArray, options);
}
}
}
function RowsToListDataStructure(results)
{
var array = [];
for(var i = 0; i < results.rows.length; i++)
{
array.push(results.rows.item(i));
}
return array;
}
function AttachLayerOfChildRecords(results, tableArray, columnArray, options)
{
var childRecordArray = [];
if (results.rows.length > 0) {
for(i = 0; i < results.rows.length; i++){
var childRecord = AttachChildRecordsToParents(results.rows.item(i), results.rows.item(i)[options.parentIdColumn], options.knockoutContextName);
childRecordArray.push(childRecord);
}
}
return childRecordArray;
}
function AttachChildRecordsToParents(recordRow, id, knockoutContextName)
{
var childTreeOptions = {id : id, knockoutContextName : knockoutContextName, results: []};
findObjectsInChildTreeById(childTreeOptions);
if (childTreeOptions.results.length > 0) {
var childRecord = AttachChildRecord(recordRow, childTreeOptions.results);
}
return childRecord;
}
function ComposeChildObject(recordRow)
{
var recordObject = { Fields: {}, SetFields: [], Insert: false };
for (var field in recordRow) {
recordObject.Fields[field] = field === "Id" && recordRow.PrimaryRowId ? recordRow.PrimaryRowId : recordRow[field];
}
return recordObject;
}
function AttachChildRecord(recordRow, pageObjParentResults)
{
var recordObject = ComposeChildObject(recordRow);
for(var i = 0; i < pageObjParentResults.length; i++){
if(pageObjParentResults[i].Children == undefined)
{
pageObjParentResults[i].Children = ko.observableArray([]);
}
if ($.grep(pageObjParentResults[i].Children, function(children){ return children['Id'] == recordObject['Id'];}).length == 0)
pageObjParentResults[i].Children.push(recordObject);
}
return recordObject;
}
function findObjectsInChildTreeById(options)
{
if (options.item == undefined)
{
for(var item in PageObj[options.knockoutContextName]())
{
findObjectsInChildTreeById({item : PageObj[options.knockoutContextName]()[item], id : options.id, results: options.results});
}
}
else
{
if (typeof options.item.Fields['Id'] == 'function')
{
if (options.item.Fields['Id']() == options.id)
options.results.push(options.item);
}
else
{
if (options.item.Fields['Id'] == options.id)
options.results.push(options.item);
}
if (options.item.Children!=undefined)
{
for(var item in options.item.Children())
{
findObjectsInChildTreeById({item : options.item.Children()[item], id : options.id, results: options.results});
}
}
}
}