1. Add and remove a dataset containing information about UBC classrooms
1. Add and remove a dataset containing information about UBC classrooms
rooms.zip
addDataset(...)
Parse it
Keep data structure in a variable
Save it to disk
Caching
same workflow as courses.zip BUT with different file types
InsightDatasetKind.Rooms
(kind)
2. Answer queries about either UBC courses or rooms.
Examples:
"what's the average of CPSC 340 ?"
2. Answer queries about either UBC courses or rooms.
2. Answer queries about UBC courses. (current solution)
{
"WHERE":{
"AND": [
{ "IS":{ "courses_id": "*340*" } },
{ "IS":{ "courses_dept": "*cpsc*" }}
]
},
"OPTIONS":{
"COLUMNS":[
"courses_id", "courses_avg"
],
"ORDER":"courses_avg"
}
}
{"result": [
{ "courses_id": "340",
"courses_avg": 68.4 },
{ "courses_id": "340",
"courses_avg": 68.4 },
{ "courses_id": "340",
"courses_avg": 72.65 },
{ "courses_id": "340",
"courses_avg": 72.65 },
{ "courses_id": "340",
"courses_avg": 72.94 },
{ "courses_id": "340",
"courses_avg": 72.94 },
...
]}
{
"WHERE":{
"AND": [
{ "IS":{ "courses_id": "*340*" } },
{ "IS":{ "courses_dept": "*cpsc*" }}
]
},
"OPTIONS":{
"COLUMNS":[
"courses_id", "courses_avg"
],
"ORDER":"courses_avg"
}
}
not what we
were looking for
2. Answer queries about UBC courses. (current solution)
2. Answer queries about UBC courses. (new query engine)
{
"result": [
{
"courses_id": "340",
"overallAvg": 75.69
}
]
}
{
"WHERE":{
"AND": [
{ "IS":{ "courses_id": "*340*" } },
{ "IS":{ "courses_dept": "*cpsc*" }}
]
},
"OPTIONS":{
"COLUMNS":[
"courses_id", "overallAvg"
]
},
"TRANSFORMATIONS":{
"GROUP":[ "courses_id"],
"APPLY": [
{
"overallAvg": {
"AVG": "courses_avg"
}
}
]
}
}
this is the right result!
2. Answer queries about UBC courses. New EBNF:
QUERY ::='{'BODY ', ' OPTIONS (', ' TRANSFORMATIONS)? '}'
BODY ::= 'WHERE:{' (FILTER)? '}'
OPTIONS ::= 'OPTIONS:{' COLUMNS ', ' (SORT)?'}'
TRANSFORMATIONS ::= 'TRANSFORMATIONS: {' GROUP ', ' APPLY '}'
FILTER ::= (LOGICCOMPARISON | MCOMPARISON | SCOMPARISON | NEGATION)
LOGICCOMPARISON ::= LOGIC ':[{' FILTER ('}, {' FILTER )* '}]'
MCOMPARISON ::= MCOMPARATOR ':{' key ':' number '}'
SCOMPARISON ::= 'IS:{' key ':' [*]? inputstring [*]? '}' // inputstring may have option * characters as wildcards
NEGATION ::= 'NOT :{' FILTER '}'
LOGIC ::= 'AND' | 'OR'
MCOMPARATOR ::= 'LT' | 'GT' | 'EQ'
COLUMNS ::= 'COLUMNS:[' (key ',')* key ']'
SORT ::= 'ORDER: ' ('{ dir:' DIRECTION ', keys: [ ' key (',' key)* ']}' | key)
DIRECTION ::= 'UP' | 'DOWN'
GROUP ::= 'GROUP: [' (key ',')* key ']'
APPLY ::= 'APPLY: [' (APPLYKEY (', ' APPLYKEY )* )? ']'
APPLYKEY ::= '{' key ': {' APPLYTOKEN ':' key '}}'
APPLYTOKEN ::= 'MAX' | 'MIN' | 'AVG' | 'COUNT' | 'SUM'
key ::= string '_' string
inputstring ::= [^*]* // zero or more of any character except asterisk.
Take a few minutes and discuss with your partner what strategies could be used to integrate the new data set into your existing solution.
Share your strategy (not your code) with other teams.
- Are they equal?
- How they differ?
- Which one better suits our problem?
- Wait. Do we have a problem? What is our problem?
addDataset(...)
new code
kind?
existing code
parse courses
parse rooms
Keep data structure in a variable
Save it to disk
return new Promise(function (fulfill, reject) {
try {
const myZip = new Zip();
if (kind === InsightDatasetKind.Courses) {
// Extract the content
zip = myZip.extract();
for (file in files inside zip) {
try{
contents = file.getContent();
for (section in results) {
// do something
}
} catch (err) {
// do something
}
});
// more code here
} else if (kind === InsightDatasetKind.Rooms) {
// We are just going to copy and paste the code above and generate ourselves a problem
} else {
// keep doing the same strategy until our code is doomed
}
// more code here
// even more code
// ...
} catch (err) {
// do something
}
});
Do we have a problem?
return new Promise(function (fulfill, reject) {
try {
const myZip = new Zip();
if (kind === InsightDatasetKind.Courses) {
// Extract the content
zip = myZip.extract();
for (file in files inside zip) {
try{
contents = file.getContent();
for (section in results) {
// do something
}
} catch (err) {
// do something
}
});
// more code here
} else if (kind === InsightDatasetKind.Rooms) {
// Extract the content
zip = myZip.extract();
for (file in files inside zip) {
try{
contents = file.getContent();
for (rooms in results) {
// do something
}
} catch (err) {
// do something
}
});
// more code here
} else {
// keep doing the same strategy until our code is doomed
}
// more code here
// even more code
// ...
} catch (err) {
// do something
}
});
1. software readability
2. software maintainability
3. software testability
Do we have a problem?
Jack the Ripper will claim his 5th victim in the DatasetController district....
return new Promise(function (fulfill, reject) {
try {
const myZip = new JSZip();
if (kind === InsightDatasetKind.Courses) {
// Extract the content
zip = myZip.extract();
for (file in files inside zip) {
try{
contents = file.getContent();
for (section in results) {
// do something
}
} catch (err) {
// do something
}
});
} else if (kind === InsightDatasetKind.Rooms) {
// We are just going to copy and paste the code above and generate ourselves a problem
} else {
// keep doing the same strategy until our code is doomed
}
// more code here
// even more code
// ...
} catch (err) {
// do something
}
});
return new Promise(function (fulfill, reject) {
try {
const myZip = new JSZip();
if (kind === InsightDatasetKind.Courses) {
// Extract the content
zip = myZip.extract();
for (file in files inside zip) {
contents = this.parseCoursesFile(file);
});
} else if (kind === InsightDatasetKind.Rooms) {
// We are just going to copy and paste the code above and generate ourselves a problem
} else {
// keep doing the same strategy until our code is doomed
}
// more code here
// even more code
// ...
} catch (err) {
// do something
}
});
calls the method that encapsulates the behavior of parsing a single file
private parseCoursesFile(file): <figure out this type> {
const result = <something>;
try{
contents = file.getContent();
for (section in results) {
// do something
}
} catch (err) {
// do something
}
return result;
}
Is the process of restructuring existing code without changing its behavior -- Wikipedia
Extract method: roughly moves some code to a smaller and well contained method
Now that we have a glimpse about how to encapsulate some of the dataset behavior into methods. Discuss with your partner about at least 3 methods that you can extract for the addDataset method.
- Does this help you integrating the new rooms dataset?
- How can you be sure that your code still works after refactoring?
- Does this help you integrating the new rooms dataset?
Your code should be open for extension, but close for modification [more to come]
Test suites! Never change your code if you don't have tests that cover that functionality
- Methods e.g.
- parseCourses ( zip )
- parseCoursesFile ( file )
- parseRooms ( zip )
- parseRoomsFile ( file )
- saveToDisk ( your datasets data structure )
- How can you be sure that your code still works after refactoring?
index.htm
/campus/
/campus/discover/
/campus/discover/buildings-and-classrooms/
/campus/discover/buildings-and-classrooms/AAC
/campus/discover/buildings-and-classrooms/ACEN
/campus/discover/buildings-and-classrooms/ACU
/campus/discover/buildings-and-classrooms/AERL
/campus/discover/buildings-and-classrooms/ALRD
...
- Files in HTML format, parsable using the parse5 package
- Each file other than index.htm represents a building and its rooms
- All buildings linked from the index should be considered valid buildings
const document = parse5.parse(<my file>) as parse5.AST.Default.Document;*
* code snippet taken from http://inikulin.github.io/parse5/
* sample html/output taken from http://astexplorer.net/#/1CHlCXc4n4
<html>
<body>
<h1>
<p>
My first heading
My first paragraph
- Inspect the html files in the dataset, take a look at them and try to find useful information
tree node for that element
current html element that I'm inspecting
GET http://skaha.cs.ubc.ca:11316/api/v1/team666/6245%20Agronomy%20Road%20V6T%201Z4
6245 Agronomy Road V6T 1Z4
6245%20Agronomy%20Road%20V6T%201Z4
status: 200,
body: {"lat":49.26125,"lon":-123.24807}
* Take a look at https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol for a more comprehensive explanation of HTTP
const request = require('request');
request({
method: 'GET',
uri: 'http://skaha.cs.ubc.ca:11316/api/v1/team6/6245%20Agronomy%20Road%20V6T%201Z4',
gzip: true
}).on('response', function(response) {
console.log("status: " + response.statusCode);
}).on('data', function(data) {
// decompressed data as it is received
console.log('decoded chunk: ' + data)
});
status: 200
decoded chunk: {
"lat":49.26125,
"lon":-123.24807
}
* https://github.com/request/request
* This code will not work in your project ! You are supposed to use the http package instead of request.
const request = require('request');
request({
method: 'GET',
uri: 'http://skaha.cs.ubc.ca:11316/api/v1/???',
gzip: true
}).on('data', function(data) {
// decompressed data as it is received
console.log('decoded chunk: ' + data)
}).on('response', function(response) {
console.log("status: " + response.statusCode);
});
status: 404
decoded chunk: {
"code":"ResourceNotFound",
"message":"/api/v1/ does not exist"
}
* https://github.com/request/request
* This code will not work in your project ! You are supposed to use the http package instead of request.
[
{ "courses_uuid": "1", "courses_instructor": "Jean", "courses_avg": 90, "courses_title" : "310"},
{ "courses_uuid": "2", "courses_instructor": "Jean", "courses_avg": 80, "courses_title" : "310"},
{ "courses_uuid": "3", "courses_instructor": "Casey", "courses_avg": 95, "courses_title" : "310"},
{ "courses_uuid": "4", "courses_instructor": "Casey", "courses_avg": 85, "courses_title" : "310"},
{ "courses_uuid": "5", "courses_instructor": "Kelly", "courses_avg": 74, "courses_title" : "210"},
{ "courses_uuid": "6", "courses_instructor": "Kelly", "courses_avg": 78, "courses_title" : "210"},
{ "courses_uuid": "7", "courses_instructor": "Kelly", "courses_avg": 72, "courses_title" : "210"},
{ "courses_uuid": "8", "courses_instructor": "Eli", "courses_avg": 85, "courses_title" : "210"},
]
{
"WHERE": {
"GT": { "courses_avg": 70 }
},
"OPTIONS": {
"COLUMNS": ["courses_title", "overallAvg"]
},
"TRANSFORMATIONS": {
"GROUP": ["courses_title"],
"APPLY": [{
"overallAvg": {
"AVG": "courses_avg"
}
}]
}
}
[
{ "courses_uuid": "1", "courses_instructor": "Jean", "courses_avg": 90, "courses_title" : "310"},
{ "courses_uuid": "2", "courses_instructor": "Jean", "courses_avg": 80, "courses_title" : "310"},
{ "courses_uuid": "3", "courses_instructor": "Casey", "courses_avg": 95, "courses_title" : "310"},
{ "courses_uuid": "4", "courses_instructor": "Casey", "courses_avg": 85, "courses_title" : "310"},
{ "courses_uuid": "5", "courses_instructor": "Kelly", "courses_avg": 74, "courses_title" : "210"},
{ "courses_uuid": "6", "courses_instructor": "Kelly", "courses_avg": 78, "courses_title" : "210"},
{ "courses_uuid": "7", "courses_instructor": "Kelly", "courses_avg": 72, "courses_title" : "210"},
{ "courses_uuid": "8", "courses_instructor": "Eli", "courses_avg": 85, "courses_title" : "210"},
]
210 group
310 group
[
{ "courses_uuid": "1", "courses_instructor": "Jean",
"courses_avg": 90, "courses_title" : "310"},
{ "courses_uuid": "2", "courses_instructor": "Jean",
"courses_avg": 80, "courses_title" : "310"},
{ "courses_uuid": "3", "courses_instructor": "Casey",
"courses_avg": 95, "courses_title" : "310"},
{ "courses_uuid": "4", "courses_instructor": "Casey",
"courses_avg": 85, "courses_title" : "310"},
]
[
{ "courses_uuid": "5", "courses_instructor": "Kelly",
"courses_avg": 74, "courses_title" : "210"},
{ "courses_uuid": "6", "courses_instructor": "Kelly",
"courses_avg": 78, "courses_title" : "210"},
{ "courses_uuid": "7", "courses_instructor": "Kelly",
"courses_avg": 72, "courses_title" : "210"},
{ "courses_uuid": "8", "courses_instructor": "Eli",
"courses_avg": 85, "courses_title" : "210"},
]
avg: 77.25
avg: 87.5
let aux = [
{x:1, y:1}, {x:2, y:2}, {x:1, y:1},
{x:4, y:2}, {x:3, y:3}, {x:6, y:3},
{x:4, y:3}, {x:-1, y:3}, {x:5, y:2},
{x:1, y:2}, {x:2, y:2}, {x:3, y:1},
{x:4, y:2}, {x:-2, y:3}, {x:2, y:4},
{x:4, y:1}, {x:2, y:3}, {x:1, y:5},
{x:9, y:3},
]
- Suppose you are given the grouped set below where each element has 2 coordinates, x and y.
- Someone decided to apply the LINEAR function to that group
- Write a method that computes the LINEAR
var linear = (acc, cur) => acc + Math.pow(cur.x, 2) / Math.abs(cur.y - 6)
aux.reduce(linear, 0)
- for loop
var acc = 0;
for (cur of aux) {
acc += Math.pow(cur.x, 2) / Math.abs(cur.y - 6)
}
- reduce
both achieve the same result
- Linear is not protected against divisions by 0 [{ x: ?, y = 6}].
What happens in that case?
[{x:2, y:6}, {x:3, y:4}].reduce(linear, 0)
Infinity
D2 postponed tasks
Me trying to assist you in the day of the deadline
team that finished before grace period
1. Describe what you're trying to do
2. Describe what is happening
3. Write a test case that encapsulate 1 and 2
4. Work using that single test as means to isolate your problem
.on('data', function(data) {
// decompressed data as it is received
console.log('decoded chunk: ' + data)
})
* Network courses cover this in more detail
complete response body does not fit the bandwidth
Hence, we break it small chunks that can be sent through the pipeline.
The client has to rebuild the response on its own.
Thankfully, our response is small enough to fit in a single package