Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nikalaikina committed Apr 3, 2016
0 parents commit 40855b5
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*.class
*.log

# sbt specific
.cache
.history
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/

# Scala-IDE specific
.scala_dependencies
.worksheet
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ~_~

java -jar "c:\my_folder_with_settings"
17 changes: 17 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name := "sp"

version := "1.0"

scalaVersion := "2.11.8"

assemblyJarName in assembly := "dreamvoyage.jar"

mainClass in assembly := Some("com.github.nikalaikina.Main")

libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.5"

libraryDependencies += "org.json4s" % "json4s-jackson_2.11" % "3.3.0"

libraryDependencies += "org.json4s" % "json4s-native_2.11" % "3.3.0"

libraryDependencies += "com.typesafe" % "config" % "1.3.0"
8 changes: 8 additions & 0 deletions config/settings.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
homeCities = VNO, MSQ
cities = BCN, BUD, BGY, AMS, FCO, PRG
dateFrom = 10/08/2016
dateTo=30/09/2016
daysFrom = 8
daysTo = 14
cost = 200
citiesCount = 3
1 change: 1 addition & 0 deletions project/assembly.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.2")
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version = 0.13.8
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logLevel := Level.Warn
77 changes: 77 additions & 0 deletions src/main/scala/com/github/nikalaikina/FlightsProvider.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.github.nikalaikina

import java.sql.Timestamp
import java.time.LocalDate
import java.time.format.DateTimeFormatter

import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.params.HttpConnectionParams
import org.json4s._
import org.json4s.jackson.JsonMethods._

case class Flight(flyFrom: String, flyTo: String, price: Float, date: LocalDate)

private case class FlightJson(flyFrom: String, flyTo: String, price: Float, dTime: Long) {
def date = new Timestamp(dTime * 1000).toLocalDateTime.toLocalDate
}

class FlightsProvider(val cities: List[String], val dateFrom: LocalDate, val dateTo: LocalDate) {
val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
implicit val formats = DefaultFormats

var map: Map[Tuple2[String, String], List[Flight]] = Map()

for (c1 <- cities; c2 <- cities; if !(c1 == c2)) {
val flights: List[Flight] = for (f <- getFlightsFromServer(c1, c2, dateFrom, dateTo))
yield new Flight(f.flyFrom, f.flyTo, f.price, f.date)
map += (Tuple2(c1, c2) -> flights)
}
println("Got flights info")

private def getFlightsFromServer(from: String, to: String, dateFrom: LocalDate, dateTo: LocalDate) = {
val urlPattern = s"https://api.skypicker.com/flights?flyFrom=$from&to=$to&dateFrom=${formatter.format(dateFrom)}&dateTo=${formatter.format(dateTo)}&directFlight=1&price_to=70"
parseFlights(Http.get(urlPattern))
}

def getFlights(from: String, to: String, dateFrom: LocalDate, dateTo: LocalDate): List[Flight] = {
val flights: Option[List[Flight]] = map.get(Tuple2(from, to))
if (flights.isEmpty) List()
else flights.get.filter(f => !f.date.isBefore(dateFrom) && !f.date.isAfter(dateTo))
}

private def parseFlights(string: String): List[FlightJson] = {
val elements: JValue = parse(string) \ "data"
for (e <- elements.children) yield e.extract[FlightJson]
}
}

private object Http {
def get(url: String,
connectionTimeout: Int = 10000,
socketTimeout: Int = 10000): String = {
val httpClient = buildHttpClient(connectionTimeout, socketTimeout)
val httpResponse = httpClient.execute(new HttpGet(url))
val entity = httpResponse.getEntity
var content = ""
if (entity != null) {
val inputStream = entity.getContent
content = io.Source.fromInputStream(inputStream).getLines.mkString
inputStream.close()
}
httpClient.getConnectionManager.shutdown()
content
}

private def buildHttpClient(connectionTimeout: Int, socketTimeout: Int): DefaultHttpClient = {
val httpClient = new DefaultHttpClient
val httpParams = httpClient.getParams
HttpConnectionParams.setConnectionTimeout(httpParams, connectionTimeout)
HttpConnectionParams.setSoTimeout(httpParams, socketTimeout)
httpClient.setParams(httpParams)
httpClient
}
}



58 changes: 58 additions & 0 deletions src/main/scala/com/github/nikalaikina/Logic.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.github.nikalaikina

import java.io.PrintWriter
import java.time.LocalDate
import java.time.temporal.ChronoUnit

import scala.collection.immutable.IndexedSeq
import scala.collection.mutable

class Logic(val settings: Settings) {
val flightsProvider = new FlightsProvider(settings.cities, settings.dateFrom, settings.dateTo)

def writeAnswer(writer: PrintWriter) = {
var queue = mutable.Queue[Route]()
queue ++= (for (city <- settings.homeCities; day <- getFirstDays) yield new Route(city, day))

while (queue.nonEmpty) {
val current = queue.dequeue
val day = current.days
if (isFine(current)) {
writer.println(current)
} else if (day < settings.daysTo && current.cost < settings.cost) {
processNode(queue, current)
}
}
}

private def isFine(route: Route): Boolean = {
(route.flights.size > 3
&& settings.homeCities.contains(route.curCity)
&& route.cost < settings.cost
&& route.cities(settings.homeCities) >= settings.citiesCount
&& route.days >= settings.daysFrom
&& route.days <= settings.daysTo)
}

private def processNode(queue: mutable.Queue[Route], current: Route) = {
for (city <- settings.cities) {
if (!(current.curCity == city)) {
val flights = getFlights(current, city)
if (flights.nonEmpty) {
queue += new Route(current, flights.minBy(f => f.price))
}
}
}
}

private def getFlights(route: Route, city: String) = {
flightsProvider.getFlights(route.curCity, city, route.date.plusDays(2), route.date.plusDays(settings.daysTo))
}

private def getFirstDays: IndexedSeq[LocalDate] = {
val from = settings.dateFrom
val to: LocalDate = settings.dateTo.minusDays(settings.daysFrom)
val n = ChronoUnit.DAYS.between(from, to).toInt
for (i <- 1 to n) yield from.plusDays(i)
}
}
32 changes: 32 additions & 0 deletions src/main/scala/com/github/nikalaikina/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.github.nikalaikina

import java.io.{File, PrintWriter}

import com.typesafe.config.{Config, ConfigFactory}

object Main extends App {
val settingsFileName = "\\settings.properties"
val outputFilename = "\\output.txt"

if (args.isEmpty) {
Console.err.println("You didn't passed path to settings")
sys.exit(1)
}
val configPath = args(0)
val config: Config = ConfigFactory.parseFileAnySyntax(new File(configPath + settingsFileName))
val settings = try {
new Settings(config)
} catch {
case e => Console.err.println(s"Error while parsing your settings file $e")
sys.exit(1)
}
println("Parsed settings")
val pw = new PrintWriter(new File(configPath + outputFilename))
try {
new Logic(settings).writeAnswer(pw)
} finally {
pw.flush()
pw.close()
}
println("Answer is in " + configPath + outputFilename)
}
25 changes: 25 additions & 0 deletions src/main/scala/com/github/nikalaikina/Route.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.github.nikalaikina

import java.time.LocalDate
import java.time.temporal.ChronoUnit

class Route(val firstCity: String, val date: LocalDate) {
var flights: List[Flight] = List()

def this(node: Route, flight: Flight) {
this(node.firstCity, node.date)
flights = node.flights :+ flight
}

def days = if (flights.isEmpty) 0L else ChronoUnit.DAYS.between(date, flights.last.date)

def cities(except: List[String]) = {
flights.map(f => f.flyTo).distinct.count(c => !except.contains(c))
}

def cost = flights.map(f => f.price).sum

def curCity = if (flights.isEmpty) firstCity else flights.last.flyTo

override def toString = s"${flights.size} $cost\t$flights"
}
22 changes: 22 additions & 0 deletions src/main/scala/com/github/nikalaikina/Settings.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.github.nikalaikina

import java.time.LocalDate
import java.time.format.DateTimeFormatter

import com.typesafe.config.Config


class Settings(val config: Config) {
val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")

val homeCities: List[String] = getList(config.getString("homeCities"))
val cities: List[String] = homeCities ++ getList(config.getString("cities"))
val dateFrom = LocalDate.parse(config.getString("dateFrom"), formatter)
val dateTo = LocalDate.parse(config.getString("dateTo"), formatter)
val daysFrom = config.getInt("daysFrom")
val daysTo = config.getInt("daysTo")
val cost = config.getInt("cost")
val citiesCount = config.getInt("citiesCount")

def getList(string: String) = string.split(",").toList.map(s => s.trim)
}

0 comments on commit 40855b5

Please sign in to comment.