Split 2D Array into multiple connected pieces by given rows and columns - javascript

I'm trying to find the most efficient way of breaking a two-dimensional array into different two-dimensional pieces, all stored into a new array which will be returned as the result.
I have a 2D array consisting of a string "N M" in the cell at row N and column M. For example, the value in row 2, column 3 would be "2 3".
Now given some rows and column indexes, I want to remove those rows and columns, and group each "piece" into its own 2D array. A "piece" is a set of orthogonally connected cells, without any intervening cells from the rows and columns removed above.
e.g. if I have an array with 8 rows and 7 columns, and I try to remove the rows [2, 3, 5], and columns [2, 6], the resulting array of "pieces" should be
[
[["0, 0", "0, 1"], ["1, 0", "1, 1"]],
[["0, 3", "0, 4", "0, 5"], ["1, 3", "1, 4", "1, 5"]],
[["4, 0", "4, 1"]],
[["4, 3", "4, 4", "4, 5"]],
[["6, 0", "6, 1"], ["7, 0", "7, 1"]],
[["6, 3", "6, 4", "6, 5"], ["7, 3", "7, 4", "7, 5"]]
]
In case the description is unclear, I went ahead and made a visual example of the process in mind here to detail the steps, problems and goals involved.
This is what I was able to come up with, but it's producing arrays that include exception columns. I know why, but I don't know how to get around it short of adding extra complexity which is not conducive to efficiency.
const SourceArray = [ ['R0·C0', 'R0·C1', 'R0·C2', 'R0·C3', 'R0·C4', 'R0·C5', 'R0·C6'],
['R1·C0', 'R1·C1', 'R1·C2', 'R1·C3', 'R1·C4', 'R1·C5', 'R1·C6'],
['R2·C0', 'R2·C1', 'R2·C2', 'R2·C3', 'R2·C4', 'R2·C5', 'R2·C6'],
['R3·C0', 'R3·C1', 'R3·C2', 'R3·C3', 'R3·C4', 'R3·C5', 'R3·C6'],
['R4·C0', 'R4·C1', 'R4·C2', 'R4·C3', 'R4·C4', 'R4·C5', 'R4·C6'],
['R5·C0', 'R5·C1', 'R5·C2', 'R5·C3', 'R5·C4', 'R5·C5', 'R5·C6'],
['R6·C0', 'R6·C1', 'R6·C2', 'R6·C3', 'R6·C4', 'R6·C5', 'R6·C6'],
['R7·C0', 'R7·C1', 'R7·C2', 'R7·C3', 'R7·C4', 'R7·C5', 'R7·C6'] ];
function split2DArray_test() {
let arrObjs = {
oldArr: {
obj: SourceArray,
bounds: { row: 8, col: 7, },
setBounds: function() {
this.bounds.row = this.obj.length;
this.bounds.col = this.obj[0].length;
},
},
newArr: { collection: [], component: [], design: { rInit: 0, rEnd: 0, cInit: 0, cEnd: 0 } },
splits: { atRows: [2, 3, 5], atCols: [2, 5] }
};
arrObjs.oldArr.setBounds();
let i = { lv1_R: 0, lv2_C: 0 , lv3_R: 0, lv4_C: 0, slicer: 0 }; let escape = false;
/*
1. Iterate through rows. (1st level)
2. If row excepted, continue iterating through rows. If not excepted, iterate through columns. (2nd level)
3. If column not excepted, RECORD ADDRESS and continue iterating through column.
4. When excepted column found, RECORD COLUMN - 1, and regin iterating through rows again (3rd level). When excepted row found, RECORD ROW - 1.
5. Record data (SliceArr): [RECORD ADDRESS[R] = RowA, RECORD ADDRESS[C] = ColA, RECORD ROW - 1 = RowB, RECORD COLUMN - 1 = ColB]
6. Create NewArr[] and SliceArrObj[]
7. Iterate through rows (r) from new SliceArr (4th level), and do SliceArrObj.push(OldArr[r].slice(ColA, ColB - ColA))
8. NewArr.push(SliceArrObj)
9. Set 2nd-level iterator to RECORD COLUMN + 1
10. Loop should work theoretically. Untested.
*/
Logger.log(`Excepting rows: ${arrObjs.splits.atRows.join(', ')}`);
Logger.log(`Excepting columns: ${arrObjs.splits.atCols.join(', ')}`);
console.time('slicer');
for (i.lv1_R = 0; i.lv1_R < arrObjs.oldArr.bounds.row; i.lv1_R++) {
if (arrObjs.splits.atRows.includes(i.lv1_R)) { continue; } else {
// We've arrived at a non-excepted row.
for (i.lv2_C = 0; i.lv2_C < arrObjs.oldArr.bounds.col; i.lv2_C++) {
if (arrObjs.splits.atCols.includes(i.lv2_C)) { continue; } else {
arrObjs.newArr.design.rInit = i.lv1_R; arrObjs.newArr.design.cInit = i.lv2_C;
Logger.log(`Found origin cell '${arrObjs.oldArr.obj[i.lv1_R][i.lv2_C]}'`);
for (i.lv3_R = arrObjs.newArr.design.rInit; i.lv3_R < arrObjs.oldArr.bounds.row; i.lv3_R++) {
if (arrObjs.splits.atRows.includes(i.lv3_R)) {
arrObjs.newArr.design.rEnd = i.lv3_R - 1;
for (i.lv4_C = arrObjs.newArr.design.cInit; i.lv4_C < arrObjs.oldArr.bounds.col; i.lv4_C++) {
if (arrObjs.splits.atCols.includes(i.lv4_C)) {
arrObjs.newArr.design.cEnd = i.lv4_C - 1;
for (i.slicer = arrObjs.newArr.design.rInit; i.slicer < arrObjs.newArr.design.rEnd + 1; i.slicer++) {
arrObjs.newArr.component.push([arrObjs.oldArr.obj[i.slicer].slice(arrObjs.newArr.design.cInit, arrObjs.newArr.design.cEnd + 1)]);
};
arrObjs.newArr.collection.push('Split'); // For the sake of output logging.
arrObjs.newArr.collection.push(arrObjs.newArr.component);
arrObjs.newArr.component = [];
i.lv2_C += 1 + arrObjs.newArr.design.cEnd - arrObjs.newArr.design.cInit;
arrObjs.newArr.design.rInit = 0; arrObjs.newArr.design.rEnd = 0; arrObjs.newArr.design.cInit = 0; arrObjs.newArr.design.cEnd = 0;
escape = true; break;
};
};
};
if (escape) { escape = false; break; };
};
};
};
i.lv2_R += 1 + arrObjs.newArr.design.rEnd - arrObjs.newArr.design.rInit;
};
};
console.timeEnd('slicer');
Logger.log(`Splitting 2D Array into:\n\n${(arrObjs.newArr.collection.join('\n\n'))}`); // The output logging.
/* Idea Space:
===== PROPOSED METHOD, INCOMPLETE =====
1. Save the origin cell of the first valid slice bounds.
2. Iterate (FOR) through columns, starting at the Bound Minimum, and going to the Bound Maximum.
3. If an exception row is found, save slice col bound max as (i.c - 1).
4. Now, iterate through rows from slice origin to next exception or bound. Once this is met, save slice row bound max as (i.r - 1).
5. Iterate through slice row bounds, pushing slices with slice column bounds as Array.slice() arguments.
6. Push new 2D array to new array collection.
=== CONCEPTS WORTH TESTING ===
I. Iterative Cell Search
Consider moving forward by setting exception or bound limits. Ex: Column iteration runs into a bound limit: slices.resume.c = false. If it's an exception, slices.resume.c = true. This way,
any time a full 2D slice is pushed to the collection of arrays, it can take the existing current slice data and either iterate across to the next horizontal array (Resume = true), or
iterate down to the next vertical array (Resume = false). If both Column and Row resume are false, then we are done adding sliced 2D arrays to the array collection.
If at least one Resume is true, then another resume can be made true. Once both are true, then a new slice origin point can be made.
II. Iterative Bounds & Exception Search.
1. Table Bounds defined and adjacent exceptions trimmed (Done).
> The current exceptions are now the top-left-side borders of the desired slices.
2. Iterate through exception indices column first (On first loop, operate from top-left bound), then row. On each, set slice start to (i.r + 1, i.c + 1), where i.? = the exception column or
row. Set the end bounds to the index represented by the next element in the list (column or row).
*/
};

In the approach I take below, I split up the problem in two main steps:
const ranges = (splits, length) => [...splits, length].reduce(
(r, e, i, a) => (s = i ? a[i - 1] + 1 : 0, e - s ? [...r, {s, e}] : r),
[]
);
const blocks = (array, rowRanges, colRanges) => rowRanges.map(
({s, e}) => array.slice(s, e)
).map(block => colRanges.map(
({s, e}) => block.map(row => row.slice(s,e))
)).flat();
The ranges() function takes an input array containing "splits" information (i.e. rowSplits or colSplits), and then uses that information to create an array of ranges for the row blocks or column blocks. Each range is defined as an object of type {s: number, e: number}, where s is the start of the range (inclusive) and e is the end (exclusive). The reduce operation used in this function makes it so that empty ranges are filtered out.
The blocks() function takes the 2D array and the row and column ranges as input. It first uses the row ranges to split the 2D array into row blocks, and then uses the column ranges to split those blocks down to the regions defined in the problem statement. The splitting operations are all implemented by mapping the ranges to their corresponding array slices. Finally, the entire result is flattened to arrive at the required output.
Complete snippet:
const rows = 8;
const cols = 7;
const arrOld = Array.from(
{ length: rows}, (_, y) => Array.from(
{ length: cols }, (_, x) => `${y}, ${x}`
)
);
const rowSplits = [2, 3, 5];
const colSplits = [2, 6];
const ranges = (splits, length) => [...splits, length].reduce(
(r, e, i, a) => (s = i ? a[i - 1] + 1 : 0, e - s ? [...r, {s, e}] : r),
[]
);
const blocks = (array, rowRanges, colRanges) => rowRanges.map(
({s, e}) => array.slice(s, e)
).map(block => colRanges.map(
({s, e}) => block.map(row => row.slice(s,e))
)).flat();
const arrNew = blocks(
arrOld,
ranges(rowSplits, rows),
ranges(colSplits, cols)
);
console.log(arrNew);

Related

Apps Script for Google Sheets: setValues parameters

I've been writing a script to copy some data from an input sheet to a database to keep track of some data. I've managed to successfully write the code for linear arrays (only one row) but when I try to copy an entire 15x15 cells range I get an error stating the parameters are not correct (suggesting the dimension of the arrays are not correct) but I can't seem to understand why.
I tried both copying directly the entire 15x15 range and creating a for loop to copy row by row 15 times but I can't mangage to make it work.
Here is the main structure:
// active spreadsheet and source + target sheets
const activeSheet = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = activeSheet.getSheetByName('Data Entry');
const dstTOTSheet = activeSheet.getSheetByName('DataBaseTOT');
var firstEmptyRowTOT = dstTOTSheet.getLastRow()+1;
For loop test:
for (var i=0; i=14;i=i+1) {
// source cells
var RoundInfo = srcSheet.getRange(10+i, 2, 1, 15); // 15x15 B10:P24
// target cells
var dstTOTRoundInfo = dstTOTSheet.getRange(firstEmptyRowTOT + i, 21, 1, 15); // I am starting from column 21 because of some other data
// set value
dstTOTRoundInfo.setValues(RoundInfo);
}
Direct 15x15 test:
// source cells
var RoundInfo = srcSheet.getRange("B10:P24"); // 15x15 B10:P24
// target cells
var dstTOTRoundInfo = dstTOTSheet.getRange(firstEmptyRowTOT, 21, 15, 15);
// set value
dstTOTRoundInfo.setValues(RoundInfo);
It is actually not that difficult to understand the 2D array concept for spreadsheet, it works like this:
// inner arrary is always one row of values:
// the 1st value (array index: 0) of the inner-array is always the cell value of the 1st column of the given row.
const row_1 = ['row_value_1-1','row_value_1-2','row_value_1-3','row_value_1-4'];
const row_2 = ['row_value_2-1','row_value_2-2','row_value_2-3','row_value_2-4'];
const row_3 = ['row_value_3-1','row_value_3-2','row_value_3-3','row_value_3-4'];
// the outter-array is always the container of every rows you have in range,
// the leangth of rows (which is the leangth of each inner-array) must be the same for this structure to work in spreadsheet.
const data = [
row_1, // the 1st value (array index: 0) of the outter-array is always the 1st row,
row_2, // the 2nd value (array index: 1) of the outter-array is always the 2nd row, etc.
row_3
];
// when you loop over such kind of 2D array, it is easier to understand with a "for... of" loop than the classic "for" loop:
for (const row of data) { // << this means "for each row of the given data"
for (const col of row) { // << this means "for each column of the given row"
// this structure will go through each cell of row 1, than each cell of row 2, etc., until all rows of the given data are iterated.
console.log(col);
}
}
/**output:
row_value_1-1
row_value_1-2
row_value_1-3
row_value_1-4
row_value_2-1
row_value_2-2
row_value_2-3
row_value_2-4
row_value_3-1
row_value_3-2
row_value_3-3
row_value_3-4
*/
// and if you need to works with the indexes of some cell, you can get the index by this:
for (const [rIndex,row] of data.entries()) {
for (const [cIndex,col] of row.entries()) {
console.log(rIndex,cIndex,col);
}
}
/** output:
0 0 row_value_1-1
0 1 row_value_1-2
0 2 row_value_1-3
0 3 row_value_1-4
1 0 row_value_2-1
1 1 row_value_2-2
1 2 row_value_2-3
1 3 row_value_2-4
2 0 row_value_3-1
2 1 row_value_3-2
2 2 row_value_3-3
2 3 row_value_3-4
*/

Weird comparison performance on Javascript object key lookup

Presentation :
I am working on a piece of code that can compare two javascript Object by looping into the first one (called A) to perform a key lookup in the second one called B (I put value as key and occurence as value).
But when I am measuring the performance of the subkey key lookup of Object A (10 times per amount of data, with data as changing parameters for each 10 times the program runs (100 per row, 200...) I get high timing for the smallest amount of data (so potentially less key in the dict B)
Objects layout :
Object A looks like below:
{
SQL_TABLE_1:
{
column_1:
[
'2jmj7l5rSfeb/vlWAYkK/YBwIDk=',
'3MaRDFGBKvsLLhrLUdplz3wUiOI=',
'PEvUFHDR4HbOYXcj7danOvyRJqs='
'XHvERAKZ4AqU+iWlx2scZXdem80=',
'nSG0lvwlkIe5YxZGTo5binr3pAw=',
'Swuc/7YCU9Ptfrff+KHaJJ1+b7U=',
'N28qqdfezfZbPmK7CaGmj7m7IOQ=',
'ffc7skeffegT1ZytAqjco3EpwUE=',
'2XldayBefzBxsUuu6tMFYHVZEEY=',
'5rC2y6IzadQ1aEy7CvNyr30JJ2k='
]
},
SQL_TABLE_2:
{
column_1:[......]
}
}
Object B field have various size but this size never change in our tests
And Object B looks like:
[
field_1:
{
'2jmj7l5rSfeb/vlWAYkK/YBwIDk=': 1,
'3MaRDFGBKvsLLhrLUdplz3wUiOI=': 1,
'PEvUFHDR4HbOYXcj7danOvyRJqs=': 1,
'XHvERAKZ4AqU+iWlx2scZXdem80=': 4,
'nSG0lvwlkIe5YxZGTo5binr3pAw=': 1,
'Swuc/7YCU9Ptfrff+KHaJJ1+b7U=': 1,
'N28qqdfezfZbPmK7CaGmj7m7IOQ=': 27,
'ffc7skeffegT1ZytAqjco3EpwUE=': 1,
'2XldayBefzBxsUuu6tMFYHVZEEY=': 18,
'5rC2y6IzadQ1aEy7CvNyr30JJ2k=': 1 },
field_2:{......}
]
Timing measurement in the code is structured like this:
sql_field_1:
{
mongo_field_1: 0.003269665241241455, mongo_field_2: 0.0015446391105651855, mongo_field_3: 0.0009834918975830079, mongo_field_4: 0.0004488091468811035,
},
sql_field_2:
{
....
}
Goal
The goal is to perform for each sub-subkey of Object A a key lookup on the Object B subkeys.
Code
Here's the code that cause this behavior:
Object A is called sql_dict_array
Object B is called hash_mongo_dict
for(var field_name in hash_mongo_dict)
{
performance_by_mongo_field[field_name] = {};
result_by_mongo_field[field_name] = {};
// LOOP ON OBJECT A KEYS
for(var table_name in sql_dict_array)
{
// Start of time measurement
var start_time = performance.now();
// there is only one column in our test data
for(var column_name in sql_dict_array[table_name])
{
found_count = 0;
for(var value of sql_dict_array[table_name][column_name])
{
// **SUBKEY LOOPKUP HERE WITH VALUE**
var results = hash_mongo_dict[field_name][value];
// IF NOT UNDEFINED THEN IT HAS BEEN FOUND
// THIS CHECK IS NOT THE BOTTLENECK
if(results != undefined)
{
found_count+=results;
}
}
if(found_count > limit_parameter)
{
console.log("error: too many match between hashes")
process.exit(0)
}
// PERFORMANCE CALCULATION
performance_by_mongo_field[field_name][table_name] = (performance.now() - start_time)/1000;
result_by_mongo_field[field_name][table_name+'.'+column_name] = (found_count/verif_hash_count*100);
}
}
return some results...
}
Testing:
With this code, I expect to have almost constant time whatever the size of the Object B (amount of subkey) but in my code I have higher time when I have only 10 subkeys in the nested object A, and it become stable when reaching 100 keys or more (tested with 6000 keys)
Here's 10 runs for the key lookup code of one key of Object A containing 10 subkeys with 300.000+ data from Object B:
0.2824700818061829 0.2532380700111389 0.2455208191871643 0.2610406551361084 0.2840422649383545 0.2344329071044922 0.2375670108795166 0.23545906591415405 0.23111085414886476 0.2363566837310791
Here's the same comparison but with 4000+ subkeys:
0.0027927708625793456 0.0018292622566223144 0.015235211849212647 0.0036304402351379395 0.002919149875640869 0.004972007751464844 0.014655702114105225 0.003572652339935303 0.0032280778884887697 0.003232938766479492
I will appreciate every advice you can provide me,

Split the data in to 4 different categories

For example - I have the following data
This below values are in an array it is not key and value.
tier2_rightend: 10766
tier1_rightend: 10766
tier1_leftend: 2719
tier1_leftstart: 1
tier2_maxjunctions: 2
tier2_leftend: 2719
tier2_leftstart: 1
tier2_minjunctions: 1
tier2_rightstart: 10275
tier1_minjunctions: 2
tier1_maxjunctions: 2
tier1_rightstart: 10275
I need the result in following format
tier1 = tier1_leftstart(value) - tier1_leftend(value) ,tier1_rightstart(value) -tier1_rightend(value)
So I need to have 4 arrays as tier1 left start tier1 left end
Tier1 right start and tier1 right end
Also I need similar thing to be done for tier 2
Can can body help?
You could take two nested arrays for the key parts and create a new object with the result.
var object = { tier2_rightend: 10766, tier1_rightend: 10766, tier1_leftend: 2719, tier1_leftstart: 1, tier2_maxjunctions: 2, tier2_leftend: 2719, tier2_leftstart: 1, tier2_minjunctions: 1, tier2_rightstart: 10275, tier1_minjunctions: 2, tier1_maxjunctions: 2, tier1_rightstart: 10275 },
result = {};
['tier1', 'tier2'].forEach(k => ['left', 'right'].forEach(l => {
var key = [k, l].join('_');
result[key] = object[key + 'start'] - object[key + 'end'];
}));
console.log(result);

How to move the data in one array to a separate array dynamically

I am using JavaScript for this.
This is what I have currently in my array
0 "mariam"
1 "jibrin"
2 "maths"
3 "Primary_1"
4 "period_3"
5 "Monday"
6 "subject=maths?name=mariam&jibrin "
7 "zainab"
8 "usman"
9 "maths"
10 "Nursery_2"
11 "period_3"
12 "Wednesday"
13 "subject=maths?name=zainab&usman "
Snapshot for better view can be found here
What I want to do is loop through this array split it where there is a value containing "subject=" and move the all the values up to that point into a new array.
So everything from index 0 to 6 will be moves to a new array from here since the value at index 6 will match the condition.
This will apply for the rest.
So this is the code I have so far:
var your_object = JSON.parse(response); // here I will have a JSON
var splitter = String(your_object).split(",");
var returnArray = [];
var tmp = [];
$(splitter).each(function (i,v) {
console.log(i,v);
if (v.includes("subject=")) {
//console.log("FOUND", i);
}
});
I am not sure how to go about moving it into a new array and dynamically handle this. Json at the minute only passed a full total size of 14 however it can be higher later on.
If a condition matches (i.e. value contains "subject=" then everything from the index at the start at the point where it matched needs moving to a new array which have to be dynamic.
I know how to remove from the current array but not sure how to dynamically move it into a new one.
If I did not made myself clear please ask and thank You
You could check if the item contains the subject substring and change the array for the result.
var data = ["mariam", "jibrin", "maths", "Primary_1", "period_3", "Monday", "subject=maths?name=mariam&jibrin ", "zainab", "usman", "maths", "Nursery_2", "period_3", "Wednesday", "subject=maths?name=zainab&usman "],
result = data.reduce(function (r, a, i, aa) {
r[r.length - 1].push(a);
if (a.slice(0, 8) === 'subject=' && i + 1 < aa.length) {
r.push([]);
}
return r;
}, [[]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Finding order between many events

Currently solving a puzzle and looking for some tips on sorting by events ordered. I would like to know what exactly is the procedure I should be following. Consider this
I input a number, then there n inputs
each input has two events, where of for event1 event2, and event1 happens before event2.
Consider the input
6
Eatfood Cuthair
Eatfood BrushTeeth
School EatFood
School DoHair
DoHair Meeting
Meeting Brushteeth
The output will be
school -> dohair-> eatfood -> meeting -> cuthair -> brushteeth
In that order. Since if we write everything down, school is indeed the first thing that occurs, and then dohair is second. If more than one possible ordering exists simply output one. You may assume that all events are connected in some way, and no circular dependencies exist.
What I am thinking about doing is making two arrays, one which has all eventOne's and all eventTwo's. I'm not really sure where to go from here though. I would like to do this in javascript. Thanks! Any hints or algorithms are suggested
Another Input
6
vote_140_prof announce_140_prof
vote_140_prof first_day_of_classes
dev_shed_algo vote_140_prof
dev_shed_algo do_hair
do_hair big_meeting
big_meeting first_day_of_classes
Output
dev_shed_algo do_hair vote_140_prof big_meeting announce_140_prof first_day_of_classes
I found the solution file on my computer, its in python which I don't know, but hopefully this will help others understand the problem
from collections import defaultdict
def toposort(graph, roots):
res = [i for i in roots]
queue = [i for i in roots]
while queue:
for i in graph[queue.pop(0)]:
if i not in res:
res.append(i)
queue.append(i)
return res
graph = defaultdict(set)
a_set = set()
b_set = set()
for i in range(int(input())):
a, b = input().split()
a_set.add(a)
b_set.add(b)
graph[a].add(b)
print(" ".join(i for i in toposort(graph, a_set - b_set)))
My attempt
var words =
'vote_140_prof announce_140_prof vote_140_prof first_day_of_classes devshed_algo vote_140_prof dev_shed_algo do_hair do_hair big_meeting big_meeting first_day_of_classes';
var events = words;
events = events.split(/\s+/);
console.log(events);
var obj = {};
for (var i = 0 ; i < events.length ; i++)
{
var name = events[i];
if(obj[name] === undefined )
{
obj[name] = [];
}
obj[name].push(events[i%2 === 1 ? i-1 : i+1]);
}
console.log(obj);
FORMATING
function sequenceEvents(pairs){
var edges = pairs.reduce(function(edges,pair){
edges.set(pair[0],[]).set(pair[1],[]);
new Map();
});
pairs.forEach(function(edges,pair){
edges.set(pair[0],[]).set(pair[1],[]);
});
var result = [];
while(edges.size){
var children = new Set([].concat.apply([],[...edges.value()]));
var roots = [...edges.keys()].filter(function(event){
!children.has(event);
});
if(!roots.length) throw "Cycle detected";
roots.forEach(function(root){
result.push(root);
edges.delete(root);
});
}
return result;
}
The Python algorithm is not correct. It will fail for this input:
3
A B
A C
C B
It will output:
A, B, C
...which conflicts with the last rule. This is because it wrongly assumes that the children of any root-event can be safely added to the result, and depend on no other events. In the above case, it will identify A as root, and B and C as its children. Then it will pop of C from that list, and add it to the result, without seeing that C depends on B, which is not in the result yet.
As others have noted, you need to make sure the capitalisation is consistent. Brushteeth and BrushTeeth are considered different events. The same is true for EatFood and Eatfood.
I provide here a solution. I hope the inline comments explain well enough what is happening:
function sequenceEvents(pairs) {
// Create a Map with a key for each(!) event,
// and add an empty array as value for each of them.
var edges = pairs.reduce(
// Set both the left and right event in the Map
// (duplicates get overwritten)
(edges, pair) => edges.set(pair[0], []).set(pair[1], []),
new Map() // Start with an empty Map
);
// Then add the children (dependent events) to those arrays:
pairs.forEach( pair => edges.get(pair[0]).push(pair[1]) );
var result = [];
// While there are still edges...
while (edges.size) {
// Get the end points of the edges into a Set
var children = new Set( [].concat.apply([], [...edges.values()]) );
// Identify the parents, which are not children, as roots
var roots = [...edges.keys()].filter( event => !children.has(event) );
// As we still have edges, we must find at least one root among them.
if (!roots.length) throw "Cycle detected";
roots.forEach(root => {
// Add the new roots to the result, all events they depend on
// are already in the result:
result.push(root);
// Delete the edges that start with these events, since the
// dependency they express has been fulfilled:
edges.delete(root);
});
}
return result;
}
// I/O
var input = document.querySelector('#input');
var go = document.querySelector('#go');
var output = document.querySelector('#output');
go.onclick = function() {
// Get lines from input, ignoring the initial number
// ... then split those lines in pairs, resulting in
// an array of pairs of events
var pairs = input.value.trim().split(/\n/).slice(1)
.map(line => line.split(/\s+/));
var sequence = sequenceEvents(pairs);
output.textContent = sequence.join(', ');
}
Input:<br>
<textarea id="input" rows=7>6
EatFood CutHair
EatFood BrushTeeth
School EatFood
School DoHair
DoHair Meeting
Meeting BrushTeeth
</textarea>
<button id="go">Sequence Events</button>
<div id="output"></div>
Without arrow functions nor apply
As in comments you indicated you like to have the code without arrow functions first:
function sequenceEvents(pairs) {
// Create a Map with a key for each(!) event,
// and add an empty array as value for each of them.
var edges = pairs.reduce(function (edges, pair) {
// Set both the left and right event in the Map
// (duplicates get overwritten)
return edges.set(pair[0], []).set(pair[1], []);
}, new Map() ); // Start with an empty Map
// Then add the children (dependent events) to those arrays:
pairs.forEach(function (pair) {
edges.get(pair[0]).push(pair[1]);
});
var result = [];
// While there are still edges...
while (edges.size) {
// Get the end points of the edges into a Set
var children = new Set(
[...edges.values()].reduce(function (children, value) {
return children.concat(value);
}, [] )
);
// Identify the parents, which are not children, as roots
var roots = [...edges.keys()].filter(function (event) {
return !children.has(event);
});
if (!roots.length) throw "Cycle detected";
roots.forEach(function (root) {
// Add the new roots to the result, all events they depend on
// are already in the result:
result.push(root);
// Delete the edges that start with these events, since the
// dependency they express has been fulfilled:
edges.delete(root);
});
}
return result;
}
Okay. Here's my shot at the problem.
var makePair = function(i0, i1) {
return {start: i0, end: i1};
};
var mp = makePair;
var makeBefores = function(pairs) {
var befores = {};
pairs.forEach(function(pair) {
if (befores[pair.end] == null) {
befores[pair.end] = [pair.start];
} else {
befores[pair.end].push(pair.start);
}
if (befores[pair.start] == null) {
befores[pair.start] = [];
}
});
for (var key in befores) {
console.log("after " + key + "there is:");
for (var i = 0; i < befores[key].length; i++) {
console.log(befores[key][i]);
}
}
return befores;
};
var shouldGoBefore = function(item, before) {
for (var i = 0; i < before.length; i++) {
if (item == before[i]) {
return true;
}
}
return false;
};
var sortEvents = function(pairs) {
if (pairs.length === 0) {
return [];
}
if (pairs.length === 1) {
return [pairs[0].start, pairs[0].end];
}
console.log(pairs);
var befores = makeBefores(pairs);
var sorted = [];
for (var key in befores) {
var added = false;
for (var i = 0; i < sorted.length; i++) {
console.log('checking if ' + sorted[i] + ' should go before ' + befores[key]);
if (shouldGoBefore(sorted[i], befores[key])) {
//console.log("splicing");
sorted.splice(i, 0, key);
added = true;
break;
}
}
if (!added) {
sorted.push(key);
}
}
return sorted.reverse();
}
var pairs = [mp('vote_140_prof','announce_140_prof'),mp('vote_140_prof','first_day_of_classes'),mp('dev_shed_algo','vote_140_prof'),mp('dev_shed_algo','do_hair'),mp('do_hair','big_meeting'),mp('big_meeting','first_day_of_classes'),mp('announce_140_prof','first_day_of_classes')];
console.log(sortEvents(pairs));
One reason things may not have been working is your test data had inconsistent capitalization. The results of this run are:
Array [ "School", "EatFood", "CutHair", "Meeting", "BrushTeeth", "DoHair" ]
I'm going to test it on your other data set but I believe this fulfills the prompt. I'm going to write up how it works in a minute.
Note that this doesn't do any file IO or reading of lines. It takes the input to sortEvents as an array of objects that have a start and end property, which I provided a helper method makePair to create.
The solution works by building a dictionary of what elements go before which others.
If you had an input like:
a->b
c->a
c->b
b->d
The dictionary would be like this:
a->[c],
c->[],
b->[a,c],
d->[b]
Then we use an array as a sort of linked list and we go through it seeing whether we have to insert something. So for example, if we are trying to see where a should be inserted and the list is c, then we would look at the dictionary for c, see that c is in it and then we know that before a there is supposed to be c, therefore we have to insert a after c
Take a hash map start with first event and put it's value to 0 than whenever you encounter event 2 check the value of event 1 in hash than put a smaller value for event 2. Once complete sort hash by value.
Bingo
line = input();
myMap = dict()
i=0
while i < line:
event1 = raw_input()
event2 = raw_input()
if event1 in myMap :
myMap[event2] = myMap[event1]+1
elif event2 in myMap:
myMap[event1] = myMap[event2]-1
else :
myMap[event1] = 0
myMap[event2] = 1
i=i+1
print i
print myMap
I don't know why some people downvoted but yes it's working, Atleast on both your samples
Sample input and output
6
eatfood
cuthair
1
{'eatfood': 0, 'cuthair': 1}
eatfood
brushteeth
2
{'brushteeth': 1, 'eatfood': 0, 'cuthair': 1}
school
eatfood
3
{'brushteeth': 1, 'eatfood': 0, 'school': -1, 'cuthair': 1}
school
dohair
4
{'brushteeth': 1, 'eatfood': 0, 'school': -1, 'cuthair': 1, 'dohair': 0}
dohair
meeting
5
{'school': -1, 'brushteeth': 1, 'cuthair': 1, 'dohair': 0, 'eatfood': 0, 'meeting': 1}
meeting
brushteeth
6
{'school': -1, 'brushteeth': 2, 'cuthair': 1, 'dohair': 0, 'eatfood': 0, 'meeting': 1}
The code is in python, feel free to convert in javascript

Categories

Resources