Insight UBC: Deliverable 2

Goals and specs

1. Add and remove a dataset containing information about UBC classrooms

Goals and specs

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)

Goals and specs

2. Answer advanced queries about UBC courses and rooms.

Goals and specs

Examples:

"what's the average of CPSC 340 ?"

2. Answer advanced queries about UBC courses and rooms.

Goals and specs

2. Answer advanced 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"
   }
}

Goals and specs

{"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 advanced queries about UBC courses. (current solution)

Goals and specs

2. Answer advanced 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!

Goals and specs

2. 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|applykey) ',')* (key|applykey) ']'
SORT ::= 'ORDER: ' ('{ dir:'  DIRECTION ', keys: [ ' ORDERKEY (',' ORDERKEY)* ']}') | ORDERKEY
DIRECTION ::= 'UP' | 'DOWN'  
ORDERKEY ::= key | applykey

GROUP ::= 'GROUP: [' (key ',')* key ']'                                                          
APPLY ::= 'APPLY: [' (APPLYRULE (', ' APPLYRULE )* )? ']'  
APPLYRULE ::= '{' applykey ': {' APPLYTOKEN ':' key '}}'
APPLYTOKEN ::= 'MAX' | 'MIN' | 'AVG' | 'COUNT' | 'SUM'                           

key ::= string '_' string
inputstring ::= [^*]* // zero or more of any character except asterisk.
applykey ::= [^_]+ // one or more of any character except underscore.

Activity #1

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?

~5 min

addDataset(...)

new code

kind?

existing code

parse courses

parse rooms

Keep data structure in a variable

Save it to disk

Activity #1. 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) {
            // 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 (section in results) {
                        // do something
                    }
                } catch (err) {
                    // do something
                }
            });
 
        } 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?

1. Code Readability

2. Code Maintainability

3. Code Testability


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 (section in results) {
                        // do something
                    }
                } catch (err) {
                    // do something
                }
            });
        else if (kind === InsightDatasetKind.X) {
            // 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.Y) {
            // 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.Z) {
            // 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 {
            // 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?

1. Code Readability

2. Code Maintainability

3. Code Testability

Activity #2

Clone the repo at 

https://github.ugrad.cs.ubc.ca/CPSC310-2018W-T1/tutorial_abstractions.git

 

Extend the functionality of the addNumberToProcess method in the SimpleMathMachine class to allow the input of words for the numbers 0-99 (e.g. input 'sixty-three' for 63).

 

For simplicity, assume only valid inputs are passed.

~10-15 min

private parseWord(input: string) : number {
    let a = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve',     
    'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];
    let b = ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
    let idx = a.indexOf(input);
        
    if (idx > -1) {
        return idx
    }
    else {
        let words = input.split('-');
        let num1 = b.indexOf(words[0]) + 2;
        let num2 = a.indexOf(words[1]);

        return(num1 * 10 + num2);
    }
}

Refactoring

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

Activity #3

Now that we have a glimpse about how to encapsulate some of the dataset behaviour into methods, try and think of at least 3 methods you could extract within your addDataset method with your partner.

 

  - Does this help you integrating the new rooms dataset?

  - How can you be sure that your code still works after refactoring?

~5 min

Activity #3

 

  - Does this help you integrating the new rooms dataset?

Your code should be open for extension, but closed for modification [more to come]

Test suites! Never change your code if you don't have tests that cover that functionality

  - How can you be sure that your code still works after refactoring?

D2 - Further clarifications

The rooms dataset

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

zip.files

?

?

single file
e.g. AAC

your datastructure

parse5 datastructure

parse5

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

HTTP

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

address

URL-encoded address

status: 200,
body: {"lat":49.26125,"lon":-123.24807}

request

response

* Take a look at https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol for a more comprehensive explanation of HTTP

client

server

const request = require('request');

request({ 
     method: 'GET', 
     uri: 'http://cs310.ugrad.cs.ubc.ca:11316/api/v1/
project_<CSID1>_<CSID2>/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.

HTTP

const request = require('request');

request({ 
     method: 'GET', 
     uri: 'http://cs310.ugrad.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"
}

HTTP

* https://github.com/request/request

* This code will not work in your project ! You are supposed to use the http package instead of request.

Activity #4

Extend the functionality of the addNumberToProcess method in SimpleMathMachine again, this time to allow the input of expressions (e.g. '(3+sqrt(2))')

 

 - Doing this all ourselves would be a lot of work; fortunately someone else has done the hard work for us already

- Use the math.js RESTful api (details at http://api.mathjs.org/) to compute the results of the expressions

~10-15 min


request({ 
     method: 'GET', 
     uri: <fill in with api request here>,
     gzip: true,
     encoding: 'utf8' // This specifies that the
                      // returned data should be a string

}).on('data', function(data) {
     // decompressed data as it is received
     console.log('decoded chunk: ' + data)
}).on('response', function(response) {
     console.log("status: " + response.statusCode);
});

HTTP

* https://github.com/request/request

* This code will not work in your project ! You are supposed to use the http package instead of request.

Made with Slides.com