From b9b707927728d98f59703b927c7bef32ac3a7150 Mon Sep 17 00:00:00 2001 From: "Simon A. Eugster" Date: Mon, 19 Oct 2015 16:10:12 +0200 Subject: [PATCH] Verifier for Reading Problems --- README.md | 2 +- exercises/ex0-intro/intro.js | 5 +- .../ex1-vertices-edges/vertices-edges.js | 5 +- .../ex2-graph-structure/graph-structure.js | 5 +- .../problem-reading-problems.md | 19 ++- .../ex3-reading-problems/reading-problems.js | 57 ++++++++ .../ex3-reading-problems/samples/sample1.in | 9 ++ .../solution-reading-problems.js | 127 ++++++++++++++++++ library/tools.js | 49 ++++++- runner.js | 3 + 10 files changed, 269 insertions(+), 12 deletions(-) create mode 100644 exercises/ex3-reading-problems/reading-problems.js create mode 100644 exercises/ex3-reading-problems/samples/sample1.in create mode 100644 exercises/ex3-reading-problems/solution-reading-problems.js diff --git a/README.md b/README.md index 36230e8..cade53c 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ to solve various types of problems. More exercises will be added. Current progre Create a Graph data structure which manages the vertices and edges from the previous exercise. - Reading Problems ————————————· + Reading Problems ————————————·—————————————————·———— Read large graphs from files. Under Siege —————————————————· diff --git a/exercises/ex0-intro/intro.js b/exercises/ex0-intro/intro.js index 8ad4f95..06e1db8 100644 --- a/exercises/ex0-intro/intro.js +++ b/exercises/ex0-intro/intro.js @@ -2,7 +2,10 @@ var path = require( 'path' ), verify = require( 'adventure-verify' ), tools = require( '../../library/tools' ); -exports.problem = tools.mdProblem( path.join( __dirname, 'problem-intro.md' ), 'problem-intro.pdf' ); +exports.problem = tools.mdProblem( { + mdSource: path.join( __dirname, 'problem-intro.md' ), + pdfName: 'problem-intro.pdf' +} ); exports.solution = tools.mdSolution( path.join( __dirname, 'solution-intro.js' ) ); exports.verify = verify( { modeReset: true }, function checker( args, t ) { diff --git a/exercises/ex1-vertices-edges/vertices-edges.js b/exercises/ex1-vertices-edges/vertices-edges.js index a123c57..ec5a63a 100644 --- a/exercises/ex1-vertices-edges/vertices-edges.js +++ b/exercises/ex1-vertices-edges/vertices-edges.js @@ -2,7 +2,10 @@ var path = require( 'path' ), verify = require( 'adventure-verify' ), tools = require( '../../library/tools' ); -exports.problem = tools.mdProblem( path.join( __dirname, 'problem-vertices-edges.md' ), 'problem-vertices-edges.pdf' ); +exports.problem = tools.mdProblem( { + mdSource: path.join( __dirname, 'problem-vertices-edges.md' ), + pdfName: 'problem-vertices-edges.pdf' +} ); exports.solution = tools.mdSolution( path.join( __dirname, 'solution-vertices-edges.js' ) ); exports.verify = verify( { modeReset: true }, function checker( args, t ) { diff --git a/exercises/ex2-graph-structure/graph-structure.js b/exercises/ex2-graph-structure/graph-structure.js index 0a00454..f5c7f59 100644 --- a/exercises/ex2-graph-structure/graph-structure.js +++ b/exercises/ex2-graph-structure/graph-structure.js @@ -2,7 +2,10 @@ var path = require( 'path' ), verify = require( 'adventure-verify' ), tools = require( '../../library/tools' ); -exports.problem = tools.mdProblem( path.join( __dirname, 'problem-graph-structure.md' ), 'problem-graph-structure.pdf' ); +exports.problem = tools.mdProblem( { + mdSource: path.join( __dirname, 'problem-graph-structure.md' ), + pdfName: 'problem-graph-structure.pdf' +} ); exports.solution = tools.mdSolution( path.join( __dirname, 'solution-graph-structure.js' ) ); diff --git a/exercises/ex3-reading-problems/problem-reading-problems.md b/exercises/ex3-reading-problems/problem-reading-problems.md index 9fb26fd..0b6cdd4 100644 --- a/exercises/ex3-reading-problems/problem-reading-problems.md +++ b/exercises/ex3-reading-problems/problem-reading-problems.md @@ -23,6 +23,7 @@ You will be provided input files of the following form: 1 a Each input file contains a list of vertices and a list of edges. The lists are separated by an empty line. +There is always at least one vertex. You should use the functionality provided by Node.js for reading files. See the literature for details. @@ -31,15 +32,25 @@ You should use the functionality provided by Node.js for reading files. See the Export a function `readGraph` which takes the file name of the input file, reads it into a graph and returns this object. For example, the following code should be supported: - var graph = readGraph( 'graph1.in' ); + readGraph( 'graph1.in', function() { + var neighbours = graph.neighbours( 'A' ); + // Would return an array containing the vertices [ a, 1, is-it-42 ] for the example graph above. + } ); + - var neighbours = graph.neighbours( 'A' ); - // Would return an array containing the vertices [ a, 1, is-it-42 ] for the example graph above. You can re-use code from the previous exercise, and you may use the following template: - function readGraph() { + + /** + * Read a graph from a file. + * @param {String} inFilePath Absolute path to the input file + * @param {function(graph: Graph)} callback Callback function, called when Graph is read. + */ + function readGraph( inFilePath, callback ) { // Your code here + // When your graph is read, call + // callback( yourGraphObject ); } module.exports = readGraph; diff --git a/exercises/ex3-reading-problems/reading-problems.js b/exercises/ex3-reading-problems/reading-problems.js new file mode 100644 index 0000000..fef7190 --- /dev/null +++ b/exercises/ex3-reading-problems/reading-problems.js @@ -0,0 +1,57 @@ +var path = require( 'path' ), + verify = require( 'adventure-verify' ), + tools = require( '../../library/tools' ); + +exports.problem = function () { + return tools.mdProblem( { + mdSource: path.join( __dirname, 'problem-reading-problems.md' ), + pdfName: 'problem-reading-problems.pdf', + sampleSource: path.join( __dirname, 'samples' ), + sampleDest: 'samples-reading-problems' + } ); +}; + +exports.solution = tools.mdSolution( path.join( __dirname, 'solution-reading-problems.js' ) ); + +exports.verify = verify( { modeReset: true }, function checker( args, t ) { + var reader = require( path.resolve( args[ 0 ] ) ), + verifier = require( './solution-reading-problems' ), + sorter = function ( a, b ) { + return a.id.localeCompare( b.id ); + }, + inFile = path.join( __dirname, 'samples', 'sample1.in' ); + + console.log( 'Testing against ' + inFile ); + + reader( inFile, function ( testGraph ) { + verifier( inFile, function ( verifierGraph ) { + var verticesFound = { c: 0, total: 0 }, + correctNbs = { c: 0, total: 0 }; + verifierGraph._vertices.forEach( function ( v ) { + verticesFound.total++; + if ( testGraph.v( v.id ) ) { + verticesFound.c++; + var verifiedNeighbours = v.neighbours().sort( sorter ), + testNeighbours = testGraph.v( v.id ).neighbours().sort( sorter ); + + correctNbs.total++; + if ( testNeighbours.length === verifiedNeighbours.length ) { + if ( verifiedNeighbours.every( function ( el, ix ) { + return el.id == testNeighbours[ ix ].id; + } ) ) { + correctNbs.c++; + } + } + } + } ); + + t.equal( verticesFound.c, verticesFound.total, 'Correct vertices' ); + t.equal( correctNbs.c, correctNbs.total, 'Vertices with correct neighbours' ); + + t.end(); + + } ); + } ); + + +} ); \ No newline at end of file diff --git a/exercises/ex3-reading-problems/samples/sample1.in b/exercises/ex3-reading-problems/samples/sample1.in new file mode 100644 index 0000000..c5ac9a1 --- /dev/null +++ b/exercises/ex3-reading-problems/samples/sample1.in @@ -0,0 +1,9 @@ +A +a +1 +is-it-42 + +A a +A 1 +A is-it-42 +1 a \ No newline at end of file diff --git a/exercises/ex3-reading-problems/solution-reading-problems.js b/exercises/ex3-reading-problems/solution-reading-problems.js new file mode 100644 index 0000000..747e7de --- /dev/null +++ b/exercises/ex3-reading-problems/solution-reading-problems.js @@ -0,0 +1,127 @@ +var fs = require( 'fs' ), + readline = require( 'readline' ); + +/** + * Vertex in a graph + * @param {*} id This vertex's ID + * @returns {Vertex} + */ +var Vertex = function ( id ) { + + this.id = id; + this._edges = []; + + return this; +}; +Vertex.prototype = { + neighbours: function () { + return this._edges.map( function ( edge ) { + return edge.vTo; + } ); + }, + addEdge: function ( edge ) { + this._edges.push( edge ); + } +}; + +/** + * Directed edge in a graph + * @param {Vertex} vFrom Source vertex + * @param {Vertex} vTo Target vertex + * @returns {Edge} + */ +var Edge = function ( vFrom, vTo ) { + + /** @type {Vertex} */ + this.vFrom = vFrom; + /** @type {Vertex} */ + this.vTo = vTo; + + this.vFrom.addEdge( this ); + + return this; +}; + +/** + * Graph containing edges and vertices + * @param vertices + * @param edges + * @constructor + */ +var Graph = function ( vertices, edges ) { + + /** + * We use a ES6 Map for storing vertices. Maps have the advantage over objects that + * they can be iterated over with forEach. + * @type {Map.} + */ + this._vertices = new Map(); + + /** + * If you do not know why we don't just use `this` in the forEach loop, read this article: + * http://javascriptplayground.com/blog/2012/04/javascript-variable-scope-this/ + */ + var me = this; + + // Read and store all vertices + vertices.forEach( function ( id ) { + me._vertices.set( id, new Vertex( id ) ); + } ); + + // Create the edges + edges.forEach( function ( pair ) { + var idFrom = pair[ 0 ], + idTo = pair[ 1 ]; + new Edge( me.v( idFrom ), me.v( idTo ) ); + } ); + +}; +Graph.prototype = { + /** + * Retrieve a vertex identified by the given ID. + * @param id Vertex ID + * @returns {Vertex} + */ + v: function ( id ) { + return this._vertices.get( id ); + } +}; + +/** + * Read a graph from a file. + * @param {String} inFilePath Absolute path to the input file + * @param {function(graph: Graph)} callback Callback function, called when Graph is read. + */ +function readGraph( inFilePath, callback ) { + + var vertices = [], + edges = [], + verticesRead = false; + + readline.createInterface( { + input: fs.createReadStream( inFilePath ) + } ).on( 'line', function ( line ) { + if ( verticesRead ) { + var edge = line.split( ' ' ); + if ( edge.length == 2 ) { + console.log( 'Edge: ', edge ); + edges.push( edge ); + } else { + console.log( 'No edge: ', line ); + } + } else { + if ( line.length == 0 ) { + console.log( 'All vertices read.' ); + verticesRead = true; + } else { + console.log( 'Vertex: ', line ); + vertices.push( line ); + } + } + } ).on( 'close', function () { + console.log( 'Read.' ); + callback( new Graph( vertices, edges ) ); + } ); +} + +module.exports = readGraph; \ No newline at end of file diff --git a/library/tools.js b/library/tools.js index da06099..f8099ff 100644 --- a/library/tools.js +++ b/library/tools.js @@ -5,15 +5,56 @@ var fs = require( 'fs' ), module.exports = {}; -module.exports.mdProblem = function ( mdFile, outPdfFile ) { +function copySamples( fromDir, toDir ) { + + if ( fs.existsSync ) { + if ( !fs.existsSync( toDir ) ) { + fs.mkdirSync( toDir ); + } + } + if ( fs.accessSync ) { + try { + fs.accessSync( toDir ); + } catch ( e ) { + fs.mkdirSync( toDir ); + } + } + fs.readdir( fromDir, function ( err, files ) { + var messages = 'Creating some sample .in files for you ...'; + if ( err ) throw err; + files.filter( function ( name ) { + return name.substr( -3 ) === '.in'; + } ).map( function ( name ) { + return { + fromPath: path.join( fromDir, name ), + toPath: path.join( toDir, name ) + }; + } ).forEach( function ( data ) { + messages += '\n* Creating `' + data.toPath + '`'; + fs.createReadStream( data.fromPath ).pipe( fs.createWriteStream( data.toPath ) ); + } ); + console.log( md( messages ) ); + } ); +} + +/** + * + * @param {{mdSource:string, pdfName:string, sampleSource:string=, sampleDest:string=}} opts + * @returns {Function} + */ +module.exports.mdProblem = function ( opts ) { return function () { - var out = path.resolve( outPdfFile ); - markdownpdf().from( mdFile ).to( out, function () { + if ( opts.sampleSource && opts.sampleDest ) { + copySamples( opts.sampleSource, opts.sampleDest ); + } + + var out = path.resolve( opts.pdfName ); + markdownpdf().from( opts.mdSource ).to( out, function () { console.log( md( 'Created `' + out + '` -- if you prefer to read this problem as PDF' ) ); } ); - return md( fs.readFileSync( mdFile, { encoding: 'utf8' } ) ); + return md( fs.readFileSync( opts.mdSource, { encoding: 'utf8' } ) ); } }; module.exports.mdSolution = function ( solutionFile ) { diff --git a/runner.js b/runner.js index 58dfb27..248ccf8 100755 --- a/runner.js +++ b/runner.js @@ -18,5 +18,8 @@ shop.add( 'Vertices and Edges', function () { shop.add( 'Graph Structure', function () { return require( './exercises/ex2-graph-structure/graph-structure' ); } ); +shop.add( 'Reading Problems', function () { + return require( './exercises/ex3-reading-problems/reading-problems' ); +} ); shop.execute( process.argv.slice( 2 ) ); \ No newline at end of file