Skip to content

My solution to the challenge #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
119 changes: 119 additions & 0 deletions modules/suggestions_functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//Converts TSV file to JSON for easier data manipulation
function tsvJSON(tsv) {
const lines = tsv.split('\n');
const headers = lines.slice(0, 1)[0].split('\t');
return lines.slice(1, lines.length).map(line => {
const data = line.split('\t');
return headers.reduce((obj, nextKey, index) => {
obj[nextKey] = data[index];
return obj;
}, {});
});
}


function name_comparison(city_name,query,lat,lng){
const namearr = city_name.split('')
const queryarr = query.split('')
const distance = getEditDistance(city_name,query)
const score = lat && lng ? ((namearr.length - distance)/namearr.length)/2 : ((namearr.length - distance)/namearr.length) //if(there are lat and lng params.then cap the score to 0.5(since the other half will be calculated from the distance given from the lat and lng))
return score

}

// Algorithm that calculates the difference between 2 strings(Levenshtein distance)
function getEditDistance(a, b){
if(a.length == 0) return b.length;
if(b.length == 0) return a.length;
let matrix = [];
for(let i = 0; i <= b.length; i++){
matrix[i] = [i];
}
for(let j = 0; j <= a.length; j++){
matrix[0][j] = j;
}
for(let i = 1; i <= b.length; i++){
for(let j = 1; j <= a.length; j++){
if(b.charAt(i-1) == a.charAt(j-1)){
matrix[i][j] = matrix[i-1][j-1];
} else {
matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
Math.min(matrix[i][j-1] + 1, // insertion
matrix[i-1][j] + 1)); // deletion
}
}
}
return (matrix[b.length][a.length]); // returns an integer,0 if strings are identical n if they are not nE(0,+infinite)
};


//Parallel sorting of two arrays based in one condition

function swap(arr,i){
const temp = arr[i]
arr[i] = arr[i+1]
arr[i+1] = temp
}


function bubble_Sort(a,b,method)
{
let swapp;
let n = a.length-1;
let x=a;
do {
swapp = false;
for (let i=0; i < n; i++)
{ if(method === 'descending'){
if (x[i] <= x[i+1]){
swap(x,i)
swap(b,i)
swapp = true
}
}else{
if (x[i] >= x[i+1]){
swap(x,i)
swap(b,i)
swapp = true;
}
}
}
n--;
} while (swapp);
return x;
}


//Algorithm that calculates the avg distance to travel from LAT1-LNG1 to LAT2-LNG2
//Math-Formula: https://www.movable-type.co.uk/scripts/latlong.html
function distance(lat1, lon1, lat2, lon2, unit) {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
const radlat1 = Math.PI * lat1/180;
const radlat2 = Math.PI * lat2/180;
const theta = lon1-lon2;
const radtheta = Math.PI * theta/180;
let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180/Math.PI;
dist = dist * 60 * 1.1515;
if (unit=="K") { dist = dist * 1.609344 }
if (unit=="N") { dist = dist * 0.8684 }
return dist;
}
}


module.exports = {
tsvJSON,
name_comparison,
getEditDistance,
swap,
bubble_Sort,
distance
}
707 changes: 707 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "backend-coding-challenge",
"version": "1.0.0",
"description": "(inspired by https://github.com/busbud/coding-challenge-backend-c)",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"heroku-postbuild":"npm install"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NickKarvounakis/backend-coding-challenge.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/NickKarvounakis/backend-coding-challenge/issues"
},
"homepage": "https://github.com/NickKarvounakis/backend-coding-challenge#readme",
"dependencies": {
"compression": "^1.7.4",
"d3": "^5.9.7",
"d3-fetch": "^1.1.2",
"express": "^4.17.1",
"morgan": "^1.9.1",
"node-fetch": "^2.6.0"
}
}
70 changes: 70 additions & 0 deletions routes/suggestions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const express = require('express')
const router = express.Router()
const fs = require('fs')
const model = require('../modules/suggestions_functions')



function cities(json,query,lat,lng){
let object = []
let scores = []
let distances = []
let counter = 0
json.forEach((city,index) => {
if(city.name){
if((city.name.toLowerCase()).includes(query.toLowerCase())){
const score = model.name_comparison(city.name,query,lat,lng)
const dist = model.distance(lat,lng,city.lat,city.long)
scores.push(score)
object.push({ name:`${city.name}, ${city.admin1}, ${city.tz}`, latitude:city.lat, longitude:city.long, score:scores[counter]})
distances.push(dist)
counter++
}
else { //checks the alt-names for every city if there wasn't a match with the name
if(city.alt_name){
const alt = city.alt_name.split(',')
for(let i=0;i<alt.length-1;i++){
if((alt[i].toLowerCase()).includes(query.toLowerCase())){
const scorex = model.name_comparison(alt[i],query,lat,lng)
const distx = model.distance(lat,lng,city.lat,city.long)
scores.push(scorex)
object.push({ name:`${city.name}, ${city.admin1}, ${city.tz}`, latitude:city.lat, longitude:city.long, score:scores[counter]})
distances.push(distx)
counter++
break;
}
}}
}
}
})
//if(lat and lng query parameters are passed)
if(lat && lng){
model.bubble_Sort(distances,object)
for(let i=distances.length-1; i >= 0; i--){
object[distances.length - (i+1)]['score'] += (i+1)/(distances.length * 2)
scores[distances.length - (i+1)] += (i+1)/(distances.length * 2)
}
}
else{
model.bubble_Sort(scores,object,'descending')
}
return object
}



router.get('/',(req,res,next) => {
const query = req.query.q
if(!query)
res.json({ message: 'You need to enter a valid query' })
const lat = req.query.latitude
const lng = req.query.longitude
const tsv = fs.readFileSync('./data/cities_canada-usa.tsv',{ encoding: 'utf8' });
const json = model.tsvJSON(tsv)
const response = cities(json,query,lat,lng)
res.json({ suggestions:response })
})



module.exports = router
40 changes: 40 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const express = require('express')
const {createServer} = require('http')
const compression = require('compression')
const morgan = require('morgan')
const path = require('path')

const app = express()
const dev = app.get('env') !== 'production'

if(!dev){
app.disable('x-powered-by')
app.use(compression())
app.use(morgan('common'))
}else {
app.use(morgan('dev'))
}

app.use(express.json())

app.get('/',(req,res) => {
res.send({ message: 'Enter /suggestions?q=<<CITYNAMEHERE>> to get started' })
})

const SuggestionRouter = require('./routes/suggestions')
app.use('/suggestions',SuggestionRouter)

app.get('*', function(req, res){
res.statusCode = 404
res.json({ message: 'Error,route not found',statusCode: res.statusCode });
});


const server = createServer(app)
const normalizePort = port => parseInt(port, 10)
const PORT = normalizePort(process.env.PORT || 3000)

server.listen(PORT,(err) => {
if(err) throw err
console.log('SERVER STARTED')
})