├── .gitignore ├── README.md ├── build.sbt ├── circle.yml ├── project ├── build.properties └── plugins.sbt └── src ├── main └── scala │ └── com │ └── github │ └── pathikrit │ └── scalgos │ ├── AStar.scala │ ├── BinaryTree.scala │ ├── BitHacks.scala │ ├── Combinatorics.scala │ ├── Counter.scala │ ├── DisjointSet.scala │ ├── DivideAndConquer.scala │ ├── DynamicProgramming.scala │ ├── FenwickTree.scala │ ├── Geometry.scala │ ├── Graph.scala │ ├── Greedy.scala │ ├── Implicits.scala │ ├── IntervalMap.scala │ ├── LinearAlgebra.scala │ ├── Macros.scala │ ├── Memo.scala │ ├── NetworkFlow.scala │ ├── NumberTheory.scala │ ├── OverlappingIntervals.scala │ ├── Randomized.scala │ ├── SegmentTree.scala │ ├── UnionFind.scala │ └── games │ ├── Card.scala │ ├── Maze.scala │ └── Sudoku.scala └── test └── scala └── com └── github └── pathikrit └── scalgos ├── BinaryTreeSpec.scala ├── BitHacksSpec.scala ├── CombinatoricsSpec.scala ├── CounterSpec.scala ├── DisjointSetSpec.scala ├── DivideAndConquerSpec.scala ├── DynamicProgrammingSpec.scala ├── GeometrySpec.scala ├── GraphSpec.scala ├── GreedySpec.scala ├── ImplicitsSpec.scala ├── IntervalMapSpec.scala ├── LinearAlgebraSpec.scala ├── MacrosSpec.scala ├── NumberTheorySpec.scala ├── OverlappingIntervalSpec.scala ├── RandomData.scala ├── RandomizedSpec.scala ├── SegmentTreeSpec.scala └── games ├── CardSpec.scala └── SudokuSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # Git Ignore compiled from https://github.com/github/gitignore 2 | 3 | # OSX # 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | .Spotlight-V100 8 | .Trashes 9 | *~ 10 | 11 | # Windows # 12 | Thumbs.db 13 | ehthumbs.db 14 | Desktop.ini 15 | $RECYCLE.BIN/ 16 | *.cab 17 | *.msi 18 | *.msm 19 | *.msp 20 | 21 | # IntelliJ # 22 | *.iml 23 | *.ipr 24 | *.iws 25 | out/ 26 | .idea/ 27 | .idea_modules/ 28 | atlassian-ide-plugin.xml 29 | 30 | # Eclipse # 31 | *.pydevproject 32 | .metadata 33 | .gradle 34 | bin/ 35 | tmp/ 36 | *.tmp 37 | *.bak 38 | *.swp 39 | *~.nib 40 | local.properties 41 | .settings/ 42 | .loadpath 43 | .externalToolBuilders/ 44 | *.launch 45 | .cproject 46 | .buildpath 47 | .target 48 | .texlipse 49 | 50 | # Java # 51 | *.class 52 | *.log 53 | .mtj.tmp/ 54 | *.jar 55 | *.war 56 | *.ear 57 | 58 | # sbt # 59 | cache/ 60 | .history/ 61 | .lib/ 62 | dist/ 63 | target/ 64 | lib_managed/ 65 | src_managed/ 66 | project/boot/ 67 | project/plugins/project/ 68 | 69 | # Scala # 70 | .scala_dependencies 71 | .worksheet 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI][circleCiImg]][circleCiLink] [![codecov][codecovImg]][codecovLink] [![Codacy][codacyImg]][codacyLink] 2 | 3 | Goals 4 | ===== 5 | * [Learn Scala](http://stackoverflow.com/tags/scala/info) 6 | * Text book [implementations](src/main/scala/com/github/pathikrit/scalgos) of common algorithms in idiomatic functional Scala 7 | * No external [dependencies](build.sbt) (except [specs2](http://etorreborre.github.io/specs2/) for tests) 8 | * Good [tests](src/test/scala/com/github/pathikrit/scalgos) and documentation 9 | 10 | Building 11 | ======== 12 | * Install git, scala and sbt: `brew install git scala sbt` 13 | * Clone project: `git clone https://github.com/pathikrit/scalgos.git; cd scalgos` 14 | * Build and run tests: `sbt test` 15 | 16 | 17 | [circleCiImg]: https://img.shields.io/circleci/project/pathikrit/scalgos/master.svg 18 | [circleCiLink]: https://circleci.com/gh/pathikrit/scalgos 19 | 20 | [codecovImg]: https://img.shields.io/codecov/c/github/pathikrit/scalgos/master.svg 21 | [codecovLink]: http://codecov.io/github/pathikrit/scalgos?branch=master 22 | 23 | [codacyImg]: https://img.shields.io/codacy/7628da9b32734c1c96b55b5650aa96be.svg 24 | [codacyLink]: https://www.codacy.com/app/pathikritscalgos/dashboard 25 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "scalgos" 2 | version := "1.0-SNAPSHOT" 3 | description := "Algorithms in Scala" 4 | licenses += ("MIT", url("http://opensource.org/licenses/MIT")) 5 | organization := "com.github.pathikrit" 6 | 7 | scalaVersion := "2.11.7" 8 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-language:_") 9 | 10 | libraryDependencies ++= Seq( 11 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 12 | "org.specs2" %% "specs2" % "2.4.1" % Test 13 | ) 14 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | java: 3 | version: oraclejdk8 4 | 5 | test: 6 | override: 7 | - sbt clean coverage test 8 | post: 9 | - bash <(curl -s https://codecov.io/bash) 10 | # - sbt coverageReport 11 | # - sbt coverageAggregate 12 | # - sbt codacyCoverage 13 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0") 2 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.9") 3 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0") 4 | //addSbtPlugin("com.codacy" % "sbt-codacy-coverage" % "1.2.0") 5 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/AStar.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | import Implicits.Updateable 6 | 7 | /** 8 | * The result of an A* search 9 | * 10 | * @param cost the total cost to reach goal 11 | * @param path the path from start to goal (where path.head is start and path.last is goal) 12 | */ 13 | case class Result[Node](cost: Double, path: Seq[Node]) 14 | 15 | /** 16 | * Template to run A* algorithm 17 | * @tparam Node encapsulates each position/state in search space 18 | */ 19 | abstract class AStar[Node] { 20 | 21 | /** 22 | * Run A* from starting node 23 | * O(E + V log V) - each edge is examined atmost once and priority queue operations are log V 24 | * 25 | * @param start starting node 26 | * @param isGoal true iff we are at goal 27 | * @return Some(Result) if goal found else None 28 | */ 29 | def run(start: Node, isGoal: Node => Boolean): Option[Result[Node]] = { 30 | val score = mutable.Map(start -> 0d) withDefaultValue Double.PositiveInfinity 31 | val priority = Ordering by {n: Node => score(n) + heuristic(n)} 32 | val queue = mutable.TreeSet(start)(priority) 33 | val parent = mutable.Map.empty[Node, Node] 34 | val visited = mutable.Set.empty[Node] 35 | 36 | def reScore(current: Node)(n: Node) = { 37 | score(n) = score(current) + distance(current, n) 38 | } 39 | 40 | while (queue.nonEmpty) { 41 | val current = queue.removeFirst 42 | if (isGoal(current)) { 43 | val trace = mutable.ArrayBuffer.empty[Node] 44 | var (v, cost) = (current, 0d) 45 | while (parent contains v) { 46 | cost += distance(parent(v), v) 47 | v +=: trace 48 | v = parent(v) 49 | } 50 | return Some(Result(cost, start +: trace.toSeq)) 51 | } 52 | // TODO: if edge in visited, we have overestimation 53 | neighbors(current) filterNot visited.contains foreach {n => 54 | if(score(n) >= score(current) + distance(current, n)) { 55 | queue updatePriority (n, reScore(current)) 56 | parent(n) = current 57 | } 58 | } 59 | visited += current 60 | } 61 | None 62 | } 63 | 64 | /** 65 | * Find neighbors of given node 66 | * @param n given node 67 | * @return find all nodes that have edges from n 68 | */ 69 | def neighbors(n: Node): Iterable[Node] 70 | 71 | /** 72 | * Calculate known distance between 2 nodes 73 | * Guaranteed to be called for only vertices that are @see neighbours 74 | * 75 | * @param from start node 76 | * @param to end node 77 | * @return distance between @param from and @param to 78 | */ 79 | def distance(from: Node, to: Node) = 1d 80 | 81 | /** 82 | * Admissible heuristic distance from node n to goal 83 | * estimated cost from current node to goal 84 | * must never over-estimate i.e. heuristic(x) <= dist(x,y) + heuristic(y) for all x,y 85 | * If not known, simply use 0 (obviously fails if dist(x,y) can be negative) 86 | * 87 | * @param n input node 88 | * @return estimated heuristic distance from node to goal 89 | */ 90 | def heuristic(n: Node) = 0d 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/BinaryTree.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.Ordering.Implicits._ 4 | 5 | /** 6 | * Collection of algorithms pertaining to Binary Trees 7 | */ 8 | object BinaryTree { 9 | 10 | /** 11 | * A Binary Search Tree 12 | */ 13 | sealed trait BST[A] { 14 | val value: A 15 | } 16 | 17 | /** 18 | * A way to represent a full BST i.e. no nodes with 1 child 19 | */ 20 | object BST { 21 | 22 | /** 23 | * An internal node of the full BST 24 | */ 25 | case class Node[A: Ordering](left: BST[A], override val value: A, right: BST[A]) extends BST[A] { 26 | require(left.value <= value && value <= right.value) 27 | } 28 | 29 | /** 30 | * A leaf node of the full BST 31 | */ 32 | case class Leaf[A: Ordering](override val value: A) extends BST[A] 33 | } 34 | 35 | /** 36 | * A BinaryTree 37 | */ 38 | type Tree[A] = Option[Node[A]] 39 | 40 | /** 41 | * A binary tree node 42 | * @param left left sub-tree 43 | * @param entry the value stored at the node 44 | * @param right right sub-tree 45 | */ 46 | case class Node[A](left: Tree[A], entry: A, right: Tree[A]) 47 | 48 | /** 49 | * Reconstruct a BST from its pre-order traversal in O(n * depth) 50 | * Assume elements in BST are unique 51 | * O(n) 52 | * 53 | * @param preOrder pre-order traversal of BST 54 | * @return reconstructed BST 55 | */ 56 | def reconstructBST[A: Ordering](preOrder: Seq[A]): Tree[A] = preOrder match { 57 | case Nil => None 58 | case root :: children => 59 | val (left, right) = children partition {_ < root} 60 | Some(Node(reconstructBST(left), root, reconstructBST(right))) 61 | } 62 | 63 | /** 64 | * Pre-order traverse a tree 65 | * O(n) 66 | * 67 | * @param root the root of the tree 68 | * @return the pre-order traversal 69 | */ 70 | def preOrderTraversal[A](root: Tree[A]): List[A] = root match { 71 | case None => Nil 72 | case Some(Node(left, entry, right)) => entry :: preOrderTraversal(left) ::: preOrderTraversal(right) 73 | } 74 | 75 | /** 76 | * Reconstruct a binary tree from its in-order and pre-order traversals in O(n * depth) 77 | * Assume elements in tree are unique 78 | * require (inOrder.length == preOrder.length) 79 | * 80 | * @param inOrder in-order traversal of binary tree 81 | * @param preOrder pre-order traversal of binary tree 82 | * @return reconstructed tree 83 | */ 84 | def reconstruct[A](inOrder: Seq[A], preOrder: Seq[A]): Tree[A] = preOrder match { 85 | case Nil => None 86 | case root :: children => 87 | val (leftIn, _ :: rightIn) = inOrder splitAt (inOrder indexOf root) 88 | val (leftPre, rightPre) = children splitAt leftIn.length 89 | Some(Node(reconstruct(leftIn, leftPre), root, reconstruct(rightIn, rightPre))) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/BitHacks.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | /** 4 | * Collection of bit hacks - Hacker's Delight style 5 | */ 6 | object BitHacks { 7 | 8 | /** 9 | * Temporary solution to increase precision for +,-,* (not /) to 30 decimal digits (100 bits) 10 | * by doing calculations in both double & longs 11 | * double (first 13 digits correct) and longs (last 18 digits correct because calculation is mod 2^64) 12 | * When the digits on the border of long and double are ..99999... or ...00000..., it may fail (e.g. 10^30 fails) 13 | */ 14 | implicit class ExtendedArithmetic(a: Long) { 15 | 16 | def +~(b: Long) = combine(a.toDouble + b, a+b) 17 | 18 | def -~(b: Long) = combine(a.toDouble - b, a-b) 19 | 20 | def *~(b: Long) = combine(a.toDouble * b, a*b) 21 | 22 | private[this] def combine(x: Double, y: Long) = { 23 | val l = 18 24 | val sx = "%.0f" format x 25 | if (sx.length <= l) { 26 | y.toString 27 | } else { 28 | var sl = sx.substring(0, sx.length()-l).scanLeft(0l)((i, c) => 10*i + c - '0').last 29 | for (i <- 1 to l) sl *= 10 30 | sx + (s"%0${l}d" format y - sl) 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * @return toggle case of c 37 | */ 38 | def toggleCase(c: Char) = (c^32).toChar 39 | 40 | /** 41 | * @return the tuple swapped 42 | */ 43 | def xorSwap(a: (Int, Int)) = { 44 | var (x, y) = a 45 | x ^= y 46 | y ^= x 47 | x ^= y 48 | (x, y) 49 | } 50 | 51 | /** 52 | * @return the tuple swapped 53 | */ 54 | def noTempSwap(a: (Int, Int)) = { 55 | var (x, y) = a 56 | x = x - y 57 | y = x + y 58 | x = y - x 59 | (x, y) 60 | } 61 | 62 | /** 63 | * Binary gcd algorithm 64 | * O(log(max(a,b)) 65 | * 66 | * @return largest number g such that a%g == 0 and b%g == 0 67 | */ 68 | def gcd(a: Int, b: Int): Int = (a, b) match { 69 | case _ if a < 0 => gcd(-a, b) 70 | case _ if b < 0 => gcd(a, -b) 71 | case (0, 0) => throw new IllegalArgumentException 72 | case (0, _) => b 73 | case (_, 0) => a 74 | case _ if a == b => a 75 | case _ => (a&1, b&1) match { 76 | case (0, 0) => gcd(a>>1, b>>1)<<1 77 | case (0, 1) => gcd(a>>1, b) 78 | case (1, 0) => gcd(a, b>>1) 79 | case (1, 1) => if (a>b) gcd(b, a-b) else gcd(a, b-a) 80 | } 81 | } 82 | 83 | /** 84 | * Binary search by bit toggling from MSB to LSB 85 | * O(64) bit-wise operations for Longs (O(32) for Ints) 86 | * 87 | * We can easily modify this to search for smallest y such that is false by doing bitBinSearch(not(f)) + 1 88 | * See: https://codeforces.com/contest/785/submission/25516887 89 | * 90 | * DO NOT use this when f is defined only between certain ranges 91 | * because it won't be a function that can be binary searched then 92 | * 93 | * @return Some(x) such that x is the largest number for which f is true 94 | * If no such x is found, None 95 | */ 96 | def bitBinSearch(f: Long => Boolean): Option[Long] = { 97 | var p = 0L 98 | var n = Long.MinValue 99 | var t = n >>> 1 100 | while (t > 0) { 101 | if (f(p|t)) p |= t 102 | if (f(n|t)) n |= t 103 | t >>= 1 104 | } 105 | Seq(p, n) find f 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Combinatorics.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.math.Ordering.Implicits._ 4 | 5 | import Implicits._ 6 | import Memo._ 7 | 8 | /** 9 | * collection of algorithms related to combinatorics 10 | */ 11 | object Combinatorics { 12 | 13 | // TODO: make these implicit methods .combinations, .combinations(n) etc 14 | 15 | /** 16 | * Iterate over all 2^n combinations - Different from the Scala Collection's combinations... 17 | * 18 | * @param s sequence to do combination over 19 | * @return all 2^n ways of choosing elements from s 20 | */ 21 | def combinations[A](s: Seq[A]) = for {i <- 0 to s.length; j <- s combinations i} yield j 22 | 23 | /** 24 | * All 2^^n possible combos of n from 0 to 1< (0 until n).map(j => ((i>>j)&1) == 1)) 29 | 30 | /** 31 | * Combinations with repeats e.g. (2, Set(A,B,C)) -> AA, AB, AC, BA, BB, BC, CA, CB, CC 32 | * 33 | * @return all s.length^n combinations 34 | */ 35 | def repeatedCombinations[A](s: Set[A], n: Int) : Traversable[List[A]] = n match { 36 | case 0 => List(Nil) 37 | case _ => for {(x, xs) <- s X repeatedCombinations(s, n-1)} yield x :: xs 38 | } 39 | 40 | /** 41 | * Generates all n^s.length combinations 42 | * O(n) 43 | * Think of as instead of boolean (2 states), we have n states for each cell in s 44 | * Also, equivalent to adding 1 in base n 45 | * Call with initially all zeroes in s 46 | * 47 | * @return next combination of n 48 | */ 49 | def nextCombination(s: Seq[Int], n: Int): List[Int] = s match { 50 | case Nil => Nil 51 | case x :: xs if x < 0 || x >= n => throw new IllegalArgumentException 52 | case x :: xs if x == n-1 => 0 :: nextCombination(xs, n) 53 | case x :: xs => x+1 :: xs 54 | } 55 | 56 | /** 57 | * Find next permutation of s - do not use this, simply use the one from Scala collections library instead 58 | * Call with Seq(0,0,0,0,1,1) for example to 6C2 59 | * O(n) 60 | * 61 | * @return Some(p) if next permutation exists or None if s is already in decreasing order 62 | */ 63 | def nextPermutation[A: Ordering](s: List[A]) = indexToOpt(s zip s.tail lastIndexWhere {e => e._1 < e._2}) map { 64 | case p => 65 | val e = s(p) 66 | val n = s lastIndexWhere {e < _} 67 | val (a, b) = s.swap(p, n) splitAt (p + 1) 68 | a ::: b.reverse 69 | } 70 | 71 | /** 72 | * Find nth Permutation 73 | * O(l*l) 74 | * 75 | * @return nth permutation of {0, 1, ... , l-1} 76 | */ 77 | def nthPermutation(l: Int)(n: Int) = { 78 | implicit def toInt(b: BigInt): Int = b.toInt 79 | require(l >=0 && n >= 0 && n < (l!)) 80 | def select(s: List[Int], i: Int): List[Int] = s.length match { 81 | case 0 => Nil 82 | case k => 83 | val d = (k - 1)! 84 | val e = s(i/d) 85 | e :: select(s - e, i%d) 86 | } 87 | select(List(0 until l: _*), n) 88 | } 89 | 90 | /** 91 | * @return memoized function to calculate C(n,r) 92 | */ 93 | val c: (Int, Int) ==> BigInt = Memo { 94 | case (_, 0) => 1 95 | case (n, r) if r > n/2 => c(n, n - r) 96 | case (n, r) => c(n - 1, r - 1) + c(n - 1, r) 97 | } 98 | 99 | /** 100 | * @return a stream of longs such that k bits of it are set and max total bits = n 101 | */ 102 | def choose(n: Int, k: Int) = ((1L< 103 | val u = -c&c 104 | val v = c + u 105 | v + (v^c)/u/4 106 | } takeWhile {i => (i>>n) == 0} 107 | 108 | /** 109 | * Number of ways to permute n objects which has r.length kinds of items 110 | * O(n*r) 111 | * 112 | * @param r r(i) is number of types of item i 113 | * 114 | * @return n!/(r0! * r1! * ....) * (n - r.sum)! 115 | */ 116 | def choose(n: Int, r: Seq[Int]): BigInt = r match { 117 | case Nil => 1 118 | case x :: Nil => c(n, x) 119 | case x :: y :: z => c(x + y, y) * choose(n, (x + y) :: z) 120 | } 121 | 122 | val naturals = 1 `...` 123 | 124 | val wholes = 0 `...` 125 | 126 | /** 127 | * Stream of factorials 128 | */ 129 | val factorial: Stream[BigInt] = 1 #:: (naturals map {i => i*factorial(i - 1)}) 130 | 131 | /** 132 | * Stream of fibonacci numbers 133 | */ 134 | val fibonacci: Stream[BigInt] = 0 #:: fibonacci.scanLeft(BigInt(1)){_ + _} 135 | 136 | /** 137 | * Stream of catalan numbers 138 | */ 139 | val catalan: Stream[BigInt] = 1 #:: (naturals map {i => (4*i - 2)*catalan(i - 1)/(i + 1)}) 140 | 141 | /** 142 | * Number of ways of selecting (1 to n) items such that none of the items are in its own position 143 | * TODO: Proof 144 | * TODO: nextPartialDerangement 145 | * O(n) 146 | * 147 | * @return memoized function to count derangements 148 | */ 149 | val derangement: Int ==> BigInt = Memo { 150 | case n if n%2 == 0 => n*derangement(n - 1) + 1 151 | case n if n%2 == 1 => n*derangement(n - 1) - 1 152 | case _ => 0 // negative n 153 | } 154 | 155 | /** 156 | * @return Number of ways to arrange [1 to n] such that exactly k of them are in own position 157 | */ 158 | def partialDerangement(n: Int, k: Int) = c(n, k) * derangement(n - k) 159 | 160 | private[this] implicit def toBigInt(i: Int): BigInt = BigInt(i) 161 | } 162 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Counter.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | /** 6 | * A simple counter class that keeps count of objects 7 | * @tparam A type of item to count 8 | */ 9 | class Counter[A] extends mutable.Map[A, Int] { 10 | 11 | private[this] val delegate = mutable.Map.empty[A, Int] withDefaultValue 0 12 | 13 | /** 14 | * Increment counter of kv._1 by kv._2 15 | */ 16 | override def +=(kv: (A, Int)): this.type = { 17 | delegate(kv._1) = delegate(kv._1) + kv._2 18 | this 19 | } 20 | 21 | /** 22 | * Decrement the counter of key by 1 23 | */ 24 | override def -=(key: A) = this += ((key, -1)) 25 | 26 | /** 27 | * Get count of item 28 | * @param key count how many occurrences of key 29 | * @return Always returns Some(x) where x is occurrences of key. 30 | * Some(0) is returned instead of None if key is not present or its count is zero 31 | */ 32 | override def get(key: A) = Some(delegate(key)) 33 | 34 | override def iterator = delegate.iterator 35 | 36 | /** 37 | * Count each item in items 38 | */ 39 | def +=(items: A*): this.type = { 40 | items foreach {i => this += ((i, 1))} 41 | this 42 | } 43 | } 44 | 45 | /** 46 | * Companion object to Counter 47 | */ 48 | object Counter { 49 | 50 | /** 51 | * Can be used as a multiset too 52 | * TODO: add setops? 53 | */ 54 | type MultiSet[A] = Counter[A] 55 | 56 | /** 57 | * @return new empty counter 58 | */ 59 | def empty[A] = new Counter[A] 60 | 61 | /** 62 | * Create new counter from given objects 63 | */ 64 | def apply[A](items: A*) = { 65 | val c = Counter.empty[A] 66 | c += (items: _*) 67 | c 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/DisjointSet.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | /** 6 | * A disjoint-set data structure (also called union-find data structure) 7 | * Has efficient union and find operations in amortised O(a(n)) time (where a is the inverse-Ackermann function) 8 | * TODO: Support delete 9 | * TODO: extend scala collection 10 | * 11 | * @tparam A types of things in set 12 | */ 13 | class DisjointSet[A] { 14 | import DisjointSet.Node 15 | private[this] val parent = mutable.Map.empty[A, Node[A]] 16 | 17 | private[this] implicit def toNode(x: A) = { 18 | assume(contains(x)) 19 | parent(x) 20 | } 21 | 22 | /** 23 | * @return true iff x is known 24 | */ 25 | def contains(x: A) = parent contains x 26 | 27 | /** 28 | * Add a new singleton set with only x in it (assuming x is not already known) 29 | */ 30 | def +=(x: A) = { 31 | assume(!contains(x)) 32 | parent(x) = new Node(x) 33 | } 34 | 35 | /** 36 | * Union the sets containing x and y 37 | */ 38 | def union(x: A, y: A) = { 39 | val (xRoot, yRoot) = (x.root, y.root) 40 | if (xRoot != yRoot) { 41 | if (xRoot.rank < yRoot.rank) { // change the root of the shorter/less-depth one 42 | xRoot.parent = yRoot 43 | } else if (xRoot.rank > yRoot.rank) { 44 | yRoot.parent = xRoot 45 | } else { 46 | yRoot.parent = xRoot 47 | xRoot.rank += 1 // else if there is tie, increment 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * @return the root (or the canonical element that contains x) 54 | */ 55 | def apply(x: A) = x.root.entry 56 | 57 | /** 58 | * @return Iterator over groups of items in same set 59 | */ 60 | def sets = parent.keys groupBy {_.root.entry} values 61 | } 62 | 63 | object DisjointSet { 64 | /** 65 | * Each internal node in DisjointSet 66 | */ 67 | private[DisjointSet] class Node[A](val entry: A) { 68 | /** 69 | * parent - the pointer to root node (by default itself) 70 | * rank - depth if we did not do path compression in find - else its upper bound on the distance from node to parent 71 | */ 72 | var (parent, rank) = (this, 0) 73 | 74 | def root: Node[A] = { 75 | if (parent != this) { 76 | parent = parent.root // path compression 77 | } 78 | parent 79 | } 80 | } 81 | 82 | /** 83 | * @return empty disjoint set 84 | */ 85 | def empty[A] = new DisjointSet[A] 86 | 87 | /** 88 | * @return a disjoint set with each element in its own set 89 | */ 90 | def apply[A](elements: A*) = { 91 | val d = empty[A] 92 | elements foreach {e => d += e} 93 | d 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/DivideAndConquer.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.math.Ordered._ 4 | 5 | import Implicits.{FuzzyDouble, BooleanExtensions, IntExtensions} 6 | 7 | /** 8 | * Collection of divide and conquer algorithms 9 | */ 10 | object DivideAndConquer { 11 | 12 | /** 13 | * Finds largest rectangle (parallel to axes) under histogram with given heights and unit width 14 | * Basically creates min-heap with smallest height at root plus its left and right 15 | * O(n * depth-of-heap) 16 | * A faster O(n) worst case DP algorithm exists 17 | * 18 | * @param heights heights of histogram 19 | * @return area of largest rectangle under histogram 20 | */ 21 | def maxRectangleInHistogram(heights: Seq[Int]): Int = if (heights isEmpty) 0 else { 22 | val (left, smallest :: right) = heights splitAt (heights indexOf heights.min) 23 | maxRectangleInHistogram(left) max smallest * heights.length max maxRectangleInHistogram(right) 24 | } 25 | 26 | /** 27 | * Generic binary search in [min,max] f to achieve target goal 28 | * O(log n) 29 | * 30 | * @param f the function to binary search over - must be monotonically increasing 31 | * @param min starting minimum guess 32 | * @param max starting maximum guess 33 | * @param avg mid function usually (min+max)/2 34 | * @param goal target to achieve 35 | * @tparam A input type of f 36 | * @tparam B output type of f 37 | * @return x such that f(x) is as close to goal as possible 38 | */ 39 | def binarySearch[A: Ordering, B: Ordering](f: A => B, min: A, max: A, avg: (A, A) => A, goal: B): A = { 40 | val mid = avg(min, max) 41 | if (min < mid && mid < max) { 42 | f(mid) compare goal match { 43 | case 1 => binarySearch(f, min, mid, avg, goal) 44 | case -1 => binarySearch(f, mid, max, avg, goal) 45 | case 0 => mid 46 | } 47 | } else { 48 | mid 49 | } 50 | } 51 | 52 | /** 53 | * Find smallest x in [min,max] where f is true 54 | * 55 | * @return Some(x) if such an x is found in [min,max] else None 56 | */ 57 | def binarySearch[A: Ordering](f: A => Boolean, min: A, max: A, avg: (A, A) => A): Option[A] = { 58 | val mid = avg(min, max) 59 | val ok = f(mid) 60 | if (min < mid && mid < max) { 61 | if (ok) binarySearch(f, mid, max, avg) else binarySearch(f, min, mid, avg) 62 | } else { 63 | ok then mid 64 | } 65 | } 66 | 67 | /** 68 | * Ternary search for maxima/minima of f in (left,right) 69 | * O (log n) 70 | * f must be U (or upside-down U) between left and right 71 | * 72 | * @param max true if search for maxima i.e. f is U else false 73 | * @return x such that f(x) is maximum (or minimum) in f assuming f is unimodal on [left,right] 74 | */ 75 | def ternarySearch[A: Ordering](left: Double, right: Double, f: Double => A, max: Boolean = true): Double = { 76 | assume(right >~ left) 77 | val (l, r) = ((2*left + right)/3, (left + 2*right)/3) 78 | if (l ~= r) { 79 | (l + r)/2 // sometimes good idea (l-delta) to (r+delta) minBy/maxBy f 80 | } else if (f(l) > f(r) ^ max) { 81 | ternarySearch(l, right, f, max) 82 | } else { 83 | ternarySearch(left, r, f, max) 84 | } 85 | } 86 | 87 | /** 88 | * Ternary search on integer domain 89 | * @see http://codeforces.com/blog/entry/11497 90 | * @param left 91 | * @param right 92 | * @param f 93 | * @param max 94 | * @tparam A 95 | * @return 96 | */ 97 | def integerTernarySearch[A: Ordering](left: Int, right: Int, f: Int => A, max: Boolean = true): Int = { 98 | if (left < right) { 99 | val mid = (left + right) / 2 100 | if (f(mid) > f(mid + 1) ^ max) { 101 | integerTernarySearch(left, mid, f, max) 102 | } else { 103 | integerTernarySearch(mid + 1, right, f, max) 104 | } 105 | } else { 106 | left 107 | } 108 | } 109 | 110 | /** 111 | * Recursive algorithm of exponentiation by squaring 112 | * O(log b) 113 | * 114 | * @return a^b 115 | */ 116 | def intPow(a: Int, b: Int): Long = if (b == 0) 1 else { 117 | val h = intPow(a, b/2) 118 | h * h * (if (b%2 == 0) 1 else a) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/DynamicProgramming.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.math.Ordering.Implicits._ 4 | import scala.collection.mutable 5 | 6 | import Implicits._ 7 | import Memo._ 8 | 9 | /** 10 | * Collection of DP algorithms 11 | */ 12 | object DynamicProgramming { 13 | 14 | /** 15 | * Subset sum algorithm - can we achieve sum t using elements from s? 16 | * O(s.map(abs).sum * s.length) 17 | * 18 | * @param s set of integers 19 | * @param t target 20 | * @return true iff there exists a subset of s that sums to t 21 | */ 22 | def isSubsetSumAchievable(s: List[Int], t: Int) = { 23 | type DP = Memo[(List[Int], Int), (Int, Int), Boolean] 24 | implicit def encode(key: (List[Int], Int)) = (key._1.length, key._2) 25 | 26 | lazy val f: DP = Memo { 27 | case (_, 0) => true // 0 can always be achieved using empty list 28 | case (Nil, _) => false // we can never achieve non-zero if we have empty list 29 | case (a :: as, x) => f(as, x - a) || f(as, x) // try with/without a.head 30 | } 31 | 32 | f(s, t) 33 | } 34 | 35 | /** 36 | * Subset sum algorithm - How can we achieve sum t using elements from s? 37 | * O(s.map(abs).sum * s.length) 38 | * Can be modified to for simple without replacement coin change problem 39 | * 40 | * @param s set of integers 41 | * @param t target 42 | * @return all subsets of s that sum to t 43 | */ 44 | def subsetSum(s: List[Int], t: Int) = { 45 | type DP = Memo[(List[Int], Int), (Int, Int), Seq[Seq[Int]]] 46 | implicit def encode(key: (List[Int], Int)) = (key._1.length, key._2) 47 | 48 | lazy val f: DP = Memo { 49 | case (Nil, 0) => Seq(Nil) 50 | case (Nil, _) => Nil 51 | case (a :: as, x) => f(as, x - a).map(_ :+ a) ++ f(as, x) 52 | } 53 | 54 | f(s, t) 55 | } 56 | 57 | /** 58 | * Partition a sequence into two partitions such that difference of their sum is minimum 59 | * O(s.length * s.sum) 60 | * 61 | * @param s list to partition 62 | * @return a partition of s into a and b s.t. |a.sum - b.sum| is minimum 63 | */ 64 | def closestPartition(s: List[Int]) = { 65 | type DP = Memo[(List[Int], Int), (Int, Int), Option[Seq[Int]]] 66 | implicit def encode(key: (List[Int], Int)) = (key._1.length, key._2) 67 | 68 | lazy val f: DP = Memo { 69 | case (_, 0) => Some(Nil) 70 | case (Nil, _) => None 71 | case (a :: as, x) => f(as, x - a).map(_ :+ a) orElse f(as, x) 72 | } 73 | 74 | val possible = f(s, _: Int) // check if _ can be created using all elements of s 75 | (s.sum/2 --> 0 firstDefined possible).get // find largest such x < s.sum/2 (always a solution at 0) 76 | } 77 | 78 | /** 79 | * KnapSack problem 80 | */ 81 | object KnapSack { 82 | 83 | /** 84 | * @param value value of this item - more the better 85 | * @param weight weight of this item - less the better 86 | */ 87 | case class Item(value: Int, weight: Int) { 88 | require(weight > 0) 89 | } 90 | 91 | /** 92 | * O(maxWeight * items.length) 93 | * 0-1 knapsack problem 94 | * @return sublist of items such that total weight < maxWeight and sum of values is maximized 95 | */ 96 | def apply(items: List[Item], maxWeight: Int): List[Item] = { 97 | require(maxWeight >= 0) 98 | 99 | type DP = Memo[(List[Item], Int), (Int, Int), (Int, List[Item])] 100 | implicit def encode(key: (List[Item], Int)) = (key._1.length, key._2) 101 | 102 | lazy val f: DP = Memo { 103 | case (i :: is, w) if w >= i.weight => 104 | val ((v1, c1), (v2, c2)) = (f(is, w), f(is, w - i.weight)) 105 | if (v2 + i.value > v1) (v2 + i.value, i :: c2) else (v1, c1) 106 | 107 | case (i :: is, w) if w > 0 => f(is, w) 108 | 109 | case _ => (0, Nil) 110 | } 111 | 112 | f(items, maxWeight)._2 113 | } 114 | 115 | /** 116 | * Generalized version of the problem 117 | * O(?) 118 | * 119 | * @param items counts of available items (multiple copies of the item can be available) 120 | * @param maxCount maximum number of items we can select 121 | * @param maxWeight maximum weight we can use 122 | * @return subCounter of items such that total weight < maxWeight, count < maxCount and sum of values is maximized 123 | */ 124 | def apply(items: Counter[Item], maxCount: Int, maxWeight: Int): Counter[Item] = ??? 125 | } 126 | 127 | /** 128 | * Calculate edit distance between 2 sequences 129 | * O(s1.length * s2.length) 130 | * 131 | * @param delete cost of delete operation 132 | * @param insert cost of insert operation 133 | * @param replace cost of replace operation 134 | * @return Minimum cost to convert s1 into s2 using delete, insert and replace operations 135 | */ 136 | def editDistance[A](s1: Seq[A], s2: Seq[A], delete: Int = 1, insert: Int = 1, replace: Int = 1) = { 137 | assume(delete > 0 && insert > 0 && replace > 0) 138 | 139 | type DP = Memo[(Seq[A], Seq[A]), (Int, Int), Int] 140 | implicit def encode(key: (Seq[A], Seq[A])) = (key._1.length, key._2.length) 141 | 142 | lazy val f: DP = Memo { 143 | case (a, Nil) => a.length * (delete min insert) 144 | case (Nil, b) => b.length * (delete min insert) 145 | case (a :: as, b :: bs) if a == b => f(as, bs) 146 | case (a, b) => (delete + f(a, b.tail)) min (insert + f(a.tail, b)) min (replace + f(a.tail, b.tail)) 147 | } 148 | 149 | f(s1, s2) 150 | } 151 | 152 | /** 153 | * Generate all possible valid brackets 154 | * O(C(n)) = O(4^n / n^1.5) 155 | * Number of brackets = C(n) i.e. the n-th Catalan number 156 | * because C(n) = sigma(i = 0 to n-1 C(i)*C(n-i)) 157 | * 158 | * @return memoized function to generate all possible valid n-pair bracket strings 159 | */ 160 | val validBrackets: Int ==> IndexedSeq[String] = Memo { 161 | case 0 => IndexedSeq("") 162 | case n => for { 163 | i <- 0 until n 164 | (a, b) <- validBrackets(i) X validBrackets(n-i-1) 165 | } yield s"($a)$b" 166 | } 167 | 168 | /** 169 | * Find longest common subsequence (not necessarily contiguous) of 2 sequences 170 | * O(a.length * b.length) since each item in cache is filled exactly once in O(1) time 171 | * 172 | * @param a first sequence 173 | * @param b second sequence 174 | * @return a longest common subsequence of a and b 175 | * if multiple possible lcs, return the one that is "earliest" in a 176 | */ 177 | def longestCommonSubsequence[A](a: List[A], b: List[A]) = { 178 | type DP = Memo[(List[A], List[A]), (Int, Int), List[A]] 179 | implicit def encode(key: (List[A], List[A])) = (key._1.length, key._2.length) 180 | 181 | implicit val c: Ordering[List[A]] = Ordering by {_.length} 182 | 183 | lazy val f: DP = Memo { 184 | case (Nil, _) | (_, Nil) => Nil 185 | case (x :: xs, y :: ys) if x == y => x :: f(xs, ys) 186 | case (x, y) => c.max(f(x.tail, y), f(x, y.tail)) 187 | } 188 | 189 | f(a, b) 190 | } 191 | 192 | /** 193 | * Find longest (strictly) increasing subsequence 194 | * O(n log n) 195 | * Proof of correctness by induction 196 | * 197 | * @param s input sequence 198 | * @return return longest increasing subsequence of a 199 | */ 200 | def longestIncreasingSubsequence[A: Ordering](s: Seq[A]) = { 201 | val cache = mutable.Map(0 -> Seq.empty[A]) // cache(i) is longest increasing sequence of length i 202 | def longest = cache.size - 1 203 | 204 | /** 205 | * Find i such that (cache(i) :: a) is a valid increasing sequence where start <= i <= end 206 | * O(log n) since we binary search 207 | * TODO: use the DivideAndConquer.binarySearch 208 | * 209 | * @param a element to be inserted 210 | * @param start start index of best 211 | * @param end end index of best 212 | * @return the longest item from best[start..end] where a can be appended to 213 | */ 214 | def findCandidate(a: A, start: Int = 0, end: Int = longest): Int = { 215 | if (start == end) { 216 | start 217 | } else { 218 | assert(end > start) 219 | val mid = (start + end + 1)/2 // bias towards right to handle 0,1 case since best(0).last is invalid 220 | if (cache(mid).last < a) findCandidate(a, mid, end) else findCandidate(a, start, mid-1) 221 | } 222 | } 223 | 224 | for (item <- s) { 225 | // Fredman-Knuth speedup: Quickly check if we can extend current best before doing binary search 226 | val position = if (cache.size > 1 && cache(longest).last < item) longest else findCandidate(item) 227 | cache(position+1) = cache(position) :+ item // end element of smaller list < end elements of larger lists 228 | } 229 | 230 | cache(longest) 231 | } 232 | 233 | /** 234 | * @return (max, min) such that 235 | * max(i) = largest sum achievable from first i elements 236 | * min(i) = smallest sum achievable from first i elements 237 | */ 238 | def bounds(s: Seq[Int]) = { 239 | def f(comp: (Int, Int) => Int) = s.scanLeft(0){(sum, i) => comp(sum + i, sum)} 240 | val order = Ordering[Int] 241 | (f(order.max), f(order.min)) 242 | } 243 | 244 | /** 245 | * Find the maximum sum of a contiguous sub array 246 | * O(n) Kadane's algorithm 247 | * 248 | * @param s 249 | * @return the maximum contiguous sub array sum 250 | */ 251 | def maxSubArraySum(s: Seq[Int]) = s.scanLeft(0){_ + _ max 0}.max 252 | } 253 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/FenwickTree.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | /** 4 | * Calculate prefix sums and support updates 5 | * @see http://codeforces.com/contest/635/submission/16433484 6 | */ 7 | trait BitIndexTree { 8 | /** 9 | * O(log n) 10 | * @return sum of elements in [0, i] 11 | */ 12 | def prefixSum(i: Int): Int 13 | 14 | /** 15 | * O(log n) 16 | * 17 | * @return sum of all elements in [from, to] 18 | */ 19 | def sum(from: Int, to: Int): Int = prefixSum(to) - prefixSum(from - 1) 20 | 21 | /** 22 | * Adds delta to the ith element 23 | * O(log n) 24 | */ 25 | def +=(i: Int, delta: Int): Unit 26 | } 27 | 28 | class FenwickTree(n: Int) extends BitIndexTree { 29 | private[this] val tree = Array.ofDim[Int](n) 30 | 31 | override def prefixSum(i: Int) = if (i < 0) 0 else tree(i) + prefixSum((i & (i + 1)) - 1) 32 | 33 | override def +=(i: Int, delta: Int) = if (i < n) { 34 | tree(i) += delta 35 | this += (i | (i + 1), delta) 36 | } 37 | } 38 | 39 | class UpdateableFenwickTree(n: Int) extends FenwickTree(n) { 40 | private[this] val data = Array.ofDim[Int](n) 41 | 42 | def update(i: Int, value: Int) = { 43 | this += (i, value - data(i)) 44 | data(i) = value 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Geometry.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | import java.lang.Math._ 5 | 6 | import Implicits.FuzzyDouble 7 | 8 | /** 9 | * Collection of geometrical algorithms 10 | */ 11 | object Geometry { 12 | 13 | /** 14 | * Represents a point (x,y) 15 | */ 16 | implicit class Point(tuple: (Double, Double)) { 17 | val (x, y) = tuple 18 | def manhattan = x+y 19 | } 20 | 21 | /** 22 | * Represents a vector between a and b 23 | */ 24 | implicit class Vector(ends: (Point, Point)) { 25 | val (a, b) = ends 26 | def X(c: Point) = crossProduct(a, b, c) 27 | def length = sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y)) 28 | } 29 | 30 | /** 31 | * Represents a 2D shape or polygon 32 | * A simple wrapper around Java's GeneralPath class 33 | * 34 | * @param points points on the shape 35 | */ 36 | case class Shape(points: Set[Point]) { 37 | private[this] val polygon = new java.awt.geom.GeneralPath() 38 | 39 | points.toList match { //TODO: .toList is non-deterministic! 40 | case p1 :: ps => 41 | polygon moveTo (p1.x, p1.y) 42 | ps foreach {p => polygon lineTo (p.x, p.y)} 43 | polygon.closePath() 44 | case _ => 45 | } 46 | 47 | def contains(p: Point) = polygon contains (p.x, p.y) 48 | } 49 | 50 | /** 51 | * Cross product of Segment(a, b) and Segment(a, c) 52 | * Twice the signed area of triangle formed by (a,b,c) i.e. if 0 then collinear 53 | * if <0 counter-clockwise turn from (a,b) to (a,c) and vice-versa 54 | * determinant | a.x a.y a.z | 55 | * | b.x b.y b.z | 56 | * | c.x c.y c.z | 57 | * @return cross product of segment(a,b) and segment(a,c) 58 | */ 59 | def crossProduct(a: Point, b: Point, c: Point) = (b.x - a.x)*(c.y - a.y) - (c.x - a.x)*(b.y - a.y) 60 | 61 | /** 62 | * @return Area of triangle a,b,c 63 | */ 64 | def areaOfTriangle(a: Point, b: Point, c: Point) = crossProduct(a, b, c).abs/2 65 | 66 | /** 67 | * @return true iff a,b,c are on the same line i.e. crossProduct is 0 68 | */ 69 | def isCollinear(a: Point, b: Point, c: Point) = (b.x - a.x)*(c.y - a.y) ~= (c.x - a.x)*(b.y - a.y) 70 | 71 | /** 72 | * Check if 2 segments intersect 73 | * TODO: proof? 74 | * @return true iff i and j intersects 75 | */ 76 | def intersects(i: Vector, j: Vector) = (i X j.a) * (i X j.b) <= 0 && (j X i.a) * (j X i.b) <= 0 77 | 78 | /** 79 | * Finds convex hull using Graham Scan 80 | * O(n log n) because of the sortBy 81 | * Else each point is pushed/popped atmost twice (one each for reverse) in constant time in halfHull 82 | * 83 | * @param points input points 84 | * @return points on the convex hull of the given points (including collinear points) 85 | */ 86 | def grahamScan(points: Set[Point]) = { 87 | assume(points.size >= 3) 88 | type Hull = mutable.ArrayStack[Point] 89 | 90 | /** 91 | * Akl-Touissant Heuristic: Discard points in interior of the quadrilateral formed by top, left, bottom, right 92 | * @param points set of input points 93 | * @return sorted remaining points 94 | */ 95 | def discardInteriorPoints(points: Set[Point]) = { 96 | def extremities: Array[Point => Double] = Array(_.x, -_.x, _.y, -_.y) 97 | val extremes = extremities map {points minBy _} 98 | val quad = Shape(extremes.toSet) 99 | ((points filterNot quad.contains) ++ extremes).toSeq.sortBy(p => (p.x, p.y)) 100 | } 101 | 102 | def turnLeft(h: Hull, p: Point) = { 103 | while(h.size > 1 && crossProduct(h(1), h(0), p) < 0) { // if crossProduct = 0, collinear, if > 0 "right turn" 104 | h pop 105 | } 106 | h push p 107 | h 108 | } 109 | 110 | def halfHull(points: Seq[Point]) = points.foldLeft(new Hull)(turnLeft) 111 | val sorted = discardInteriorPoints(points) 112 | (halfHull(sorted) ++ halfHull(sorted.reverse)).toSet 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Graph.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | import Implicits._ 6 | 7 | /** 8 | * A semi mutable weighted graph representation using adjacency list 9 | * Can add/remove/update edges but cannot add/remove vertices 10 | * 11 | * @param numberOfVertices Number of vertices in graph 12 | * @param isDirected true iff a directed graph 13 | */ 14 | class Graph(val numberOfVertices: Int, val isDirected: Boolean = true) { 15 | import Graph.EndPoints 16 | 17 | private[this] val adjacencyList = Array.fill(numberOfVertices){mutable.Map.empty[Int, Double] withDefaultValue Double.PositiveInfinity} 18 | 19 | private[this] implicit class Edge(points: EndPoints) { 20 | val (u, v) = points 21 | assume(hasVertices(u, v)) 22 | } 23 | 24 | /** 25 | * Edge between points 26 | * This is more readable alternative to traditional g(u)(v) i.e. g(u->v) 27 | * 28 | * @param points (from,to) 29 | * @return edge value (else 0 if from==to or +infinity if from and to has no edge) 30 | */ 31 | def apply(points: EndPoints): Double = if (points.u == points.v) 0.0 else adjacencyList(points.u)(points.v) 32 | 33 | /** 34 | * curried alternative to @see apply(EndPoints) 35 | * 36 | * @param u from 37 | * @param v to 38 | * @return edge value of u->v 39 | */ 40 | def apply(u: Int)(v: Int): Double = this(u->v) 41 | 42 | /** 43 | * Check if edge exists 44 | * @param points (from,to) 45 | * @return true iff from->to edge exists 46 | */ 47 | def has(points: EndPoints) = adjacencyList(points.u) contains points.v 48 | 49 | /** 50 | * @return true iff all vertices in graph 51 | */ 52 | def hasVertices(vs: Int*) = vs forall vertices.contains 53 | 54 | /** 55 | * @return neighbors of u 56 | */ 57 | def neighbours(u: Int) = adjacencyList(u).keySet 58 | 59 | /** 60 | * Update edges 61 | * To remove use -= 62 | * 63 | * @param points (from, to) 64 | * @param weight (from,to) = weight 65 | */ 66 | def update(points: EndPoints, weight: Double) = { 67 | adjacencyList(points.u)(points.v) = weight 68 | if (!isDirected) { 69 | adjacencyList(points.v)(points.u) = weight 70 | } 71 | } 72 | 73 | /** 74 | * Delete an edge between (from,to) 75 | * @param points (from,to) 76 | */ 77 | def -=(points: EndPoints) = { 78 | adjacencyList(points.u) -= points.v 79 | if (!isDirected) { 80 | adjacencyList(points.v) -= points.u 81 | } 82 | } 83 | 84 | /** 85 | * @return vertices in graph 86 | */ 87 | def vertices = adjacencyList.indices 88 | 89 | /** 90 | * @return edges in graph 91 | */ 92 | def edges = for (u <- vertices; v <- neighbours(u)) yield u->v 93 | 94 | /** 95 | * @return the adjacency matrix of this graph 96 | */ 97 | def adjacencyMatrix = Array.tabulate(numberOfVertices, numberOfVertices){(u, v) => this(u->v)} 98 | } 99 | 100 | /** 101 | * Collection of graph algorithms 102 | */ 103 | object Graph { 104 | 105 | private[Graph] type EndPoints = (Int, Int) 106 | 107 | /** 108 | * Run Dijkstra's shortest path algorithm 109 | * Basically runs A* with heuristic=0 110 | * 111 | * @param g input graph 112 | * @param start starting vertex 113 | * @param goal end vertex 114 | * @return result of A* search 115 | */ 116 | def dijkstra(g: Graph, start: Int, goal: Int) = new AStar[Int] { 117 | assume(g hasVertices (start, goal)) 118 | def neighbors(n: Int) = g neighbours n 119 | override def distance(from: Int, to: Int) = g(from -> to) 120 | } run (start, _ == goal) 121 | 122 | /** 123 | * Run Floyd-Warshall all pair shortest path algorithm on g 124 | * O(V*V*V) 125 | * TODO: handle negative weight cycle f(i)(i) < 0 126 | * 127 | * @param g input graph 128 | * @return the minimum distance matrix of g i.e. f(x)(y) = minimum distance between x and y 129 | * if x and y on negative weight cycle f(x)(y) = -infinity 130 | * if x and y disconnected then f(x)(y) = +infinity 131 | */ 132 | def floydWarshall(g: Graph) = { 133 | val f = g.adjacencyMatrix 134 | 135 | for (k <- g.vertices; i <- g.vertices; j <- g.vertices) { 136 | f(i)(j) = f(i)(j) min (f(i)(k) + f(k)(j)) 137 | } 138 | 139 | f 140 | } 141 | 142 | /** 143 | * Run Tarjan's strongly connected component algorithm in G 144 | * O(E + V) - each edge is examined once 145 | * - each vertex is pushed/popped once 146 | * Trivially finds all cycles too 147 | * TODO: Return a DisjointSet? 148 | * TODO: http://apps.topcoder.com/forums/?module=Thread&threadID=785825&mc=1#1714843 149 | * 150 | * @param g input graph 151 | * @return the set of strongly connected components 152 | * either a set is of size 1 or for every pair of vertex u,v in each set v is reachable from u 153 | */ 154 | def stronglyConnectedComponents(g: Graph) = { 155 | var count = 0 156 | val (index, lowLink) = (mutable.Map.empty[Int, Int], mutable.Map.empty[Int, Int]) 157 | val stack = mutable.Stack[Int]() //TODO: try empty here 158 | val inProcess = mutable.LinkedHashSet.empty[Int] 159 | val components = mutable.Queue.empty[Set[Int]] 160 | 161 | def dfs(u: Int) { 162 | index(u) = count // set u.index to lowest unused count 163 | lowLink(u) = count 164 | stack push u 165 | inProcess += u 166 | count += 1 167 | 168 | g neighbours u foreach {v => 169 | if(!(index contains v)) { 170 | dfs(v) 171 | lowLink(u) = lowLink(u) min lowLink(v) 172 | } else if (inProcess contains v) { 173 | lowLink(u) = lowLink(u) min index(v) 174 | } 175 | } 176 | 177 | if (index(u) == lowLink(u)) { 178 | var v = -1 179 | val scc = mutable.Set.empty[Int] 180 | do { 181 | v = stack.pop() 182 | inProcess -= v 183 | scc += v 184 | } while(u != v) 185 | components += scc.toSet 186 | } 187 | } 188 | 189 | //todo: g.vertices filterNot index.contains foreach dfs 190 | for { 191 | u <- g.vertices if !(index contains u) 192 | } dfs(u) 193 | components.toSeq 194 | } 195 | 196 | /** 197 | * Run Bellman-Ford algorithm for finding 198 | * Handles negative weights 199 | * TODO: Negative weight cycle? 200 | * TODO: Improvements 201 | * TODO: Proof of correctness 202 | * TODO: Why does parent work for all vertices? can there be cycles in parent? 203 | * O(VE) 204 | * 205 | * @param g input graph 206 | * @param source starting vertex 207 | * @return (d, p) where d(i) is shortest distance from source to i 208 | * if negative cycle then d(i) is negative infinity 209 | * and p(j) = parent of vertex j - follow back to source for path 210 | * should either end in -1 or a loop if d(i) is positive infinity 211 | */ 212 | def bellmanFord(g: Graph, source: Int) = { 213 | val distance = Array.tabulate(g.numberOfVertices){g(source)} 214 | val parent = Array.fill(g.numberOfVertices)(-1) // TODO: use Option instead of -1 215 | 216 | for { 217 | i <- 1 until g.numberOfVertices 218 | (u, v) <- g.edges if distance(v) >= distance(u) + g(u->v) 219 | } { 220 | distance(v) = distance(u) + g(u->v) 221 | parent(v) = u 222 | } 223 | 224 | (distance.toSeq, parent.toSeq) 225 | } 226 | 227 | /** 228 | * Kruskal's Minimum Spanning Tree algorithm 229 | * O(E log V) 230 | * 231 | * @return list of edges in the MST 232 | */ 233 | def kruskalsMst(g: Graph) = { 234 | val (d, mst) = (DisjointSet(g.vertices: _*), mutable.Set.empty[EndPoints]) 235 | for ((u, v) <- g.edges sortBy g.apply if d(u) != d(v)) { 236 | d union (u, v) 237 | mst += u->v 238 | } 239 | mst.toSet 240 | } 241 | 242 | /** 243 | * Prim's Minimum Spanning Tree algorithm 244 | * O(VE) ? 245 | * 246 | * @return list of edges in the MST 247 | */ 248 | def primsMst(g: Graph) = g.vertices.toList match { 249 | case Nil => Set.empty[EndPoints] 250 | case v :: vs => 251 | val (seen, unseen, mst) = (mutable.Set(v), mutable.Set(vs: _*), mutable.Set.empty[EndPoints]) 252 | while(unseen nonEmpty) { 253 | val (u, v) = seen X unseen minBy g.apply 254 | unseen -= v 255 | seen += v 256 | mst += u->v 257 | } 258 | mst.toSet 259 | } 260 | 261 | /** 262 | * Breadth first search from source in g 263 | * If we replace queue with stack, we get DFS 264 | * O(V + E) - each vertex/edge is examined atmost once 265 | * 266 | * @param f Apply f to each vertex in bfs order from source 267 | * @return If f is true at a vertex v, return Some(v) else None 268 | */ 269 | def bfs(g: Graph, source: Int, f: Int => Boolean): Option[Int] = { 270 | val (seen, queue) = (mutable.Set.empty[Int], mutable.Queue.empty[Int]) 271 | 272 | def visit(i: Int) = { 273 | seen += i 274 | queue += i 275 | } 276 | 277 | visit(source) 278 | 279 | while (queue nonEmpty) { 280 | val u = queue.dequeue() 281 | if (f(u)) { 282 | return Some(u) 283 | } 284 | g neighbours u filterNot seen foreach visit 285 | } 286 | 287 | None 288 | } 289 | 290 | /** 291 | * Recursive depth first search from u in g 292 | * TODO: Change BFS, DFS to cost too 293 | * O(V + E) - each vertex/edge is examined atmost once 294 | * 295 | * @param f Apply f to each vertex in dfs order from source 296 | * @return If f is true at a vertex v, return Some(v) else None 297 | */ 298 | def dfs(g: Graph, u: Int, f: Int => Boolean, seen: Set[Int] = Set.empty[Int]): Option[Int] = 299 | if (f(u)) Some(u) else g neighbours u filterNot seen firstDefined (dfs(g, _, f, seen + u)) 300 | 301 | /** 302 | * Topological sort this graph 303 | * O(V + E) - same cost as a DFS 304 | * 305 | * @param root This algo assumes graph is not disjoint and has a root which has edges to every node and no incoming edge 306 | * @param g 307 | * @return Map from node to maxDepth 308 | */ 309 | def topologicalSort(g: Graph, root: Int): Map[Int, Int] = { 310 | val depth = mutable.Map.empty[Int, Int] withDefaultValue 0 311 | def dfs(visited: Set[Int])(u: Int): Unit = { 312 | require(!visited(u), s"Cycle detected involving $u") 313 | g.neighbours(u) filterNot visited foreach dfs(visited + u) 314 | depth(u) = depth(u) max visited.size 315 | } 316 | dfs(Set.empty)(root) 317 | depth.toMap 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Greedy.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | /** 4 | * Collection of greedy algorithms 5 | */ 6 | object Greedy { 7 | /** 8 | * Stack based solution to maximum rectangle in histogram problem 9 | * stack always has (h, x) such that h is in increasing order and x is the earliest index at which h can be spanned 10 | * O(n) - stack can be atmost size of remaining; no recursive step repeats previous; size of remaining never increases 11 | * 12 | * @param heights heights of histogram 13 | * @return area of largest rectangle under histogram 14 | */ 15 | def maxRectangleInHistogram(heights: List[Int]): Int = { 16 | def solve(stack: List[(Int, Int)], remaining: List[(Int, Int)]): Int = { 17 | def area(x: Int, y: Int) = (heights.length - remaining.length - x) * y 18 | (stack, remaining) match { 19 | case ( Nil, Nil) => 0 20 | case ((y, x) :: rest, Nil) => solve( rest, remaining) max area(x, y) 21 | case ((y, x) :: rest, (h, _) :: hs) if h <= y => solve( rest, (h, x) :: hs) max area(x, y) 22 | case ( _, block :: hs) => solve(block :: stack, hs) 23 | } 24 | } 25 | solve(Nil, heights.zipWithIndex) 26 | } 27 | 28 | //TODO: stable marraige 29 | //TODO: 2SAT 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Implicits.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | /** 6 | * Implicits that enhance default library stuff 7 | */ 8 | object Implicits { //TODO: Move to package.scala 9 | 10 | /** 11 | * Sometimes its convenient to map true to 1 and false to 0 12 | */ 13 | implicit def toInt(x: Boolean): Int = if (x) 1 else 0 14 | 15 | /** 16 | * Better floating point comparison with a tolerance of eps = 1e-9 17 | * @param x treats x as a range [x-eps, x+eps] 18 | */ 19 | implicit class FuzzyDouble(x: Double)(implicit eps: Double = 1e-9) { 20 | 21 | /** 22 | * @return true iff x > y+eps 23 | */ 24 | def >~(y: Double) = x > y+eps 25 | 26 | /** 27 | * @return true iff x >= y-eps 28 | */ 29 | def >=~(y: Double) = x >= y-eps 30 | 31 | /** 32 | * @return true iff x < y-eps 33 | */ 34 | def ~<(y: Double) = x < y-eps 35 | 36 | /** 37 | * @return true iff x <= y+eps 38 | */ 39 | def ~=<(y: Double) = x <= y+eps 40 | 41 | /** 42 | * @return true iff x in [y-eps, y+eps] 43 | */ 44 | def ~=(y: Double) = ~=<(y) && >=~(y) 45 | } 46 | 47 | /** 48 | * Extension to Booleans 49 | */ 50 | implicit class BooleanExtensions(b: Boolean) { 51 | def then[A](f: => A): Option[A] = if (b) Some(f) else None 52 | } 53 | 54 | /** 55 | * Extensions to Ints 56 | */ 57 | implicit class IntExtensions(x: Int) { 58 | /** 59 | * @return m = x mod y such that we preserve the relation (x/y)*y + m == x 60 | */ 61 | def mod(y: Int) = x - (x/y)*y 62 | 63 | def ! = Combinatorics factorial x 64 | 65 | def `...` = Stream from x 66 | 67 | /** 68 | * @return range that goes forward or backward depending on x and y 69 | */ 70 | def -->(y: Int) = x to y by (if (x < y) 1 else -1) 71 | 72 | /** 73 | * For doing 5 times f 74 | */ 75 | def times[A](f: => A) = 1 to x map {i => f} 76 | } 77 | 78 | /** 79 | * Extensions to Longs 80 | */ 81 | implicit class LongExtensions(x: Long) { 82 | /** 83 | * count set bits 84 | */ 85 | def bitCount = java.lang.Long.bitCount(x) 86 | } 87 | 88 | implicit class TraversableExtension[A](t: Traversable[A]) { 89 | def firstDefined[B](f: A => Option[B]): Option[B] = t collectFirst Function.unlift(f) 90 | } 91 | 92 | /** 93 | * Let's you use X instead of double for-loops 94 | */ 95 | implicit class Crossable[A](as: Traversable[A]) { 96 | def X[B](bs: Traversable[B]) = for {a <- as; b <- bs} yield (a, b) 97 | } 98 | 99 | /** 100 | * Supports map inversions 101 | */ 102 | implicit class Invertible[K, V](map: Map[K, V]) { 103 | 104 | /** 105 | * Invert a map[K,V] to map[V, Iterable[K]] 106 | */ 107 | def invert = map groupBy {_._2} mapValues {_ map (_._1)} 108 | } 109 | 110 | /** 111 | * Supports priority updates and quick remove mins 112 | */ 113 | implicit class Updateable[A](queue: mutable.TreeSet[A]) { 114 | /** 115 | * Remove and return the smallest value from a TreeSet queue 116 | * O(log n) 117 | */ 118 | def removeFirst = { 119 | val head = queue.head 120 | queue -= head 121 | head 122 | } 123 | 124 | /** 125 | * Hack to update priority of a node by deleting and re-adding 126 | * O (log n) 127 | * 128 | * @param node node whose priority is being updated 129 | * @param update a function given a node updates its priority (is called for given node) 130 | */ 131 | def updatePriority(node: A, update: A => Unit) { 132 | queue -= node 133 | update(node) 134 | queue += node 135 | } 136 | } 137 | 138 | /** 139 | * To get around the fact that indexOf returns -1 for missing instead of None. 140 | */ 141 | def indexToOpt(idx: Int) = idx >= 0 then idx 142 | 143 | /** 144 | * @return If predicate is true, return Some(f) else None 145 | */ 146 | def when[A](predicate: Boolean)(f: => A): Option[A] = predicate then f 147 | 148 | /** 149 | * Support some more operations on lists 150 | */ 151 | implicit class RichList[A](l: List[A]) { 152 | 153 | /** 154 | * @return a list with items in position i and j swapped 155 | */ 156 | def swap(i: Int, j: Int) = l.updated(i, l(j)).updated(j, l(i)) 157 | 158 | /** 159 | * Remove 1 element 160 | * 161 | * @param elem element to remove 162 | * @return a list with first occurrence of elem removed 163 | */ 164 | def -(elem: A): List[A] = l match { 165 | case Nil => Nil 166 | case x :: xs if x == elem => xs 167 | case x :: xs => x :: (xs - elem) 168 | } 169 | } 170 | 171 | /** 172 | * Let's us easily specify lazy streams that is a function of the last element 173 | */ 174 | implicit class Streamer[A](start: A) { 175 | def `...`(f: A => A) = Stream.iterate(start)(f) 176 | } 177 | 178 | /** 179 | * F#'s forward pipe operator 180 | */ 181 | implicit class PipedFunctions[A](x: => A) { 182 | def |>[B](f: A => B) = f(x) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/IntervalMap.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | /** 6 | * A data structure that supports interval updates 7 | * e.g. map(5 -> 60000) = "hello" would set all keys in [5, 60000) to be "hello" 8 | */ 9 | trait IntervalMap[A] { 10 | import IntervalMap.Interval 11 | 12 | /** 13 | * Set all values in range to value 14 | * 15 | * @param r range 16 | * @param value value to set to 17 | */ 18 | def update(r: Interval, value: A) 19 | 20 | /** 21 | * Value at point x 22 | * 23 | * @return Some(y) if a value exists else None 24 | */ 25 | def apply(x: Int): Option[A] 26 | 27 | /** 28 | * Clear all mappings in given range 29 | * 30 | * @param r range 31 | */ 32 | def clear(r: Interval) 33 | 34 | /** 35 | * Extract to a seq 36 | * intervals are guaranteed to be disjoint and from left to right 37 | * adjacent intervals are guaranteed to have different values 38 | * Use this method for all scala collection goodies 39 | * 40 | * @return sequence of disjoint intervals that have values 41 | */ 42 | def toSeq: Seq[(Interval, A)] 43 | 44 | override def hashCode = toSeq.hashCode() 45 | 46 | override def equals(obj: Any) = obj match { 47 | case that: IntervalMap[A] => that.toSeq == this.toSeq 48 | case _ => false 49 | } 50 | 51 | override def toString = toSeq map {case (i, v) => s"$i : $v"} mkString ("{", ", ", "}") 52 | } 53 | 54 | /** 55 | * companion object for IntervalMap 56 | */ 57 | object IntervalMap { 58 | 59 | /** 60 | * @return a new empty IntervalMap 61 | * all operations are O(n) except toSeq which is O(n log n) (where n is number of disjoint segments) 62 | * TODO: Make these O(log n) 63 | */ 64 | def empty[A]: IntervalMap[A] = new SegmentedIntervalMap[A] 65 | 66 | /** 67 | * Models a half-closed interval [start, end) 68 | */ 69 | case class Interval(start: Int, end: Int) { 70 | require(start <= end) 71 | def overlaps(r: Interval) = start <= r.start && r.end <= end 72 | def contains(x: Int) = start <= x && x < end 73 | override def toString = s"[$start, $end)" 74 | } 75 | 76 | implicit val toInterval = Interval.tupled 77 | 78 | private[this] class SegmentedIntervalMap[A] extends IntervalMap[A] { 79 | 80 | private[this] val segments = mutable.Map.empty[Interval, A] 81 | 82 | override def update(r: Interval, value: A) = { 83 | clear(r) 84 | val a = segments collect { case (k @ Interval(_, r.start), `value`) => 85 | unset(k) 86 | k.start 87 | } 88 | val b = segments collect { case (k @ Interval(r.end, _), `value`) => 89 | unset(k) 90 | k.end 91 | } 92 | set((a.headOption getOrElse r.start) -> (b.headOption getOrElse r.end), value) 93 | } 94 | 95 | override def apply(x: Int) = segments find {_._1 contains x} map {_._2} 96 | 97 | override def clear(r: Interval) = { 98 | segments.keys filter r.overlaps foreach unset 99 | 100 | segments foreach {case (k, v) => 101 | if (k contains r.start) { 102 | unset(k) 103 | set(k.start -> r.start, v) 104 | } 105 | if (k contains r.end) { 106 | unset(k) 107 | set(r.end -> k.end, v) 108 | } 109 | } 110 | } 111 | 112 | override def toSeq = segments.toSeq.sortBy(_._1.start) 113 | 114 | private[this] def unset(i: Interval) = segments -= i 115 | private[this] def set(i: Interval, value: A) = segments(i) = value 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/LinearAlgebra.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | /** 4 | * Algorithms related to linear algebra 5 | */ 6 | object LinearAlgebra { 7 | 8 | /** 9 | * Implicit class to convert a 2d double array into a matrix with linear algebra ops 10 | */ 11 | implicit class Matrix2D(a: Array[Array[Double]]) { 12 | 13 | /** 14 | * O(n^2) matrix multiplication 15 | * 16 | * @param b matrix to multiply with 17 | * @return this X b 18 | */ 19 | def X(b: Matrix2D) = { 20 | val a = this 21 | require(a.cols == b.rows) 22 | Array.tabulate(a.rows, b.cols) { 23 | (i, j) => ((0 to cols) map {k => a(i, k) * b(k, j)}).sum 24 | } 25 | } 26 | 27 | /** 28 | * get matrix cell value 29 | * 30 | * @param i row 31 | * @param j col 32 | * @return value at ith row and jth column 33 | */ 34 | def apply(i: Int, j: Int) = a(i)(j) 35 | 36 | /** 37 | * @return num rows in matrix 38 | */ 39 | def rows = a.length 40 | 41 | /** 42 | * TODO: what if num rows == 0 43 | * @return num cols in matrix 44 | */ 45 | def cols = a(0).length 46 | } 47 | 48 | /** 49 | * @param coeffs coefficients of the polynomial 50 | */ 51 | case class Polynomial(coeffs: List[Double]) { 52 | 53 | /** 54 | * Evaluate a polynomial using Horner's rule 55 | * 56 | * @param x point at which to evaluate the polynomial 57 | * @return a0 + a1*x + a2*x*x + .... 58 | */ 59 | def apply(x: Double) = coeffs.foldRight(0.0){(c, a) => a*x + c} 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Macros.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.io.Source 4 | import scala.collection.mutable 5 | import scala.collection.JavaConversions._ 6 | 7 | /** 8 | * Collection of code snippets that do common tasks such as profiling, downloading a webpage, debugging variables etc 9 | */ 10 | object Macros { 11 | 12 | /** 13 | * Profile a block of code 14 | * e.g. use val (result, time, mem) = profile { ... code ... } 15 | * 16 | * @param code the block of code to profile 17 | * @tparam R return type of 18 | * @return (res, time, mem) where res is result of profiled code, time is time used in ms and mem is memory used in MB 19 | */ 20 | def profile[R](code: => R) = { 21 | import System.{currentTimeMillis => time} 22 | val t = time 23 | (code, time - t, Runtime.getRuntime.totalMemory>>20) 24 | } 25 | 26 | /** 27 | * @return the contents of url (usually html) 28 | */ 29 | def download(url: String) = (Source fromURL url).mkString 30 | 31 | /** 32 | * An LRU cache 33 | * @param maxEntries maximum number of entries to retain 34 | */ 35 | def lruCache[A, B](maxEntries: Int): mutable.Map[A, B] = new java.util.LinkedHashMap[A, B]() { 36 | override def removeEldestEntry(eldest: java.util.Map.Entry[A, B]) = size > maxEntries 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Memo.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | /** 4 | * Generic way to create memoized functions (even recursive and multiple-arg ones) 5 | * @see http://stackoverflow.com/questions/25129721/ for full explanation of this 6 | * 7 | * @param f the function to memoize 8 | * @tparam I input to f 9 | * @tparam K the keys we should use in cache instead of I 10 | * @tparam O output of f 11 | */ 12 | case class Memo[I <% K, K, O](f: I => O) extends (I => O) { 13 | import scala.collection.mutable.{Map => Dict} 14 | val cache = Dict.empty[K, O] 15 | override def apply(x: I) = cache getOrElseUpdate (x, f(x)) 16 | } 17 | 18 | object Memo { 19 | /** 20 | * Type of a simple memoized function e.g. when I = K 21 | */ 22 | type ==>[I, O] = Memo[I, I, O] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/NetworkFlow.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | /** 4 | * Collection of network flow related algorithms 5 | */ 6 | object NetworkFlow 7 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/NumberTheory.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | import Implicits._ 5 | 6 | import scala.annotation.tailrec 7 | 8 | /** 9 | * Collection of number theory algorithms 10 | */ 11 | object NumberTheory { 12 | 13 | /** 14 | * Runs sieve of Eratosthenes 15 | * O(n log n) - TODO: why? 16 | * 17 | * @param n number upto (inclusive) to run the sieve 18 | * @return bitset p s.t. p(x) <=> x is prime 19 | */ 20 | def sieveOfEratosthenes(n: Int) = { 21 | val numbers = 2 to n 22 | val isPrime = mutable.BitSet(numbers: _*) 23 | for (p <- numbers takeWhile {i => i*i <= n} if isPrime(p)) { 24 | isPrime --= p*p to n by p 25 | } 26 | isPrime.toImmutable 27 | } 28 | 29 | /** 30 | * Runs sieve of Sundaram 31 | * O(n log n) - TODO: why? 32 | * 33 | * @return all *odd* primes in [1,n] 34 | */ 35 | def sieveOfSundaram(n: Int) = { 36 | val s = mutable.BitSet(1 to n/2 : _*) 37 | for { 38 | i <- 1 to n/6 39 | j <- 1 to (n-2*i)/(4*i+1) // j s.t. i + j + 2ij <= n/2 todo: we go from j <- i to (2i+1)j? 40 | } s -= i + j + 2*i*j // all odd composites are of form (2i+1)(2j+1) = 2(i + j + 2ij) + 1 41 | s.toSeq.map(2*_ + 1) 42 | } 43 | 44 | /** 45 | * Euler's Totient Function or phi function 46 | * O(n log n) 47 | * 48 | * @return phi s.t. phi(n) = number of positive integers <= n that are co-prime to n 49 | */ 50 | def phis(n: Int) = { 51 | val phi = Array.tabulate(n + 1)(identity) 52 | for { 53 | i <- 2 to n if phi(i) == i 54 | j <- i to n by i 55 | } phi(j) = (phi(j)/i)*(i-1) 56 | phi 57 | } 58 | 59 | /** 60 | * O(c = 100) primality check 61 | * 62 | * @return true iff n is prime (with 2^(-100) probability of failure) 63 | */ 64 | def isPrime(n: Int) = BigInt(n) isProbablePrime 100 65 | 66 | /** 67 | * Euclid's algorithm to calculate gcd 68 | * O(log(max(a,b)) 69 | * 70 | * @return largest number g such that a%g == 0 and b%g == 0 71 | */ 72 | def gcd(a: Long, b: Long): Long = (a, b) match { 73 | case _ if a < 0 => gcd(-a, b) 74 | case _ if b < 0 => gcd(a, -b) 75 | case (_, 0) => assume(a!=0); a 76 | case _ => gcd(b, a%b) 77 | } 78 | 79 | /** 80 | * Uses Euclid's GCD algorithm 81 | * O(log(max(a,b)) 82 | * 83 | * @return least common (non-negative) multiple of a,b 84 | */ 85 | def lcm(a: Int, b: Int) = a/gcd(a,b) * b 86 | 87 | /** 88 | * Extended Euclidean algorithm to calculate Bézout's identity 89 | * @return (x,y) such that ax + by = gcd(a,b) 90 | */ 91 | def extendedEuclidean(a: Int, b: Int): (Int, Int) = (a, b) match { 92 | case _ if a < 0 => extendedEuclidean(-a, b) 93 | case _ if b < 0 => extendedEuclidean(a, -b) 94 | case (_, 0) => assume(a != 0); (1, 0) 95 | case _ => 96 | val (x, y) = extendedEuclidean(b, a%b) 97 | (y, x - (a/b)*y) 98 | } 99 | 100 | /** 101 | * Count multiples - surprisingly non trivial 102 | * O(n) 103 | * TODO: Proof 104 | * 105 | * @return number of multiples of c in [a,b] 106 | */ 107 | def numberOfMultiples(a: Int, b: Int, c: Int): Int = c.signum match { 108 | case -1 => numberOfMultiples(a, b, -c) 109 | case 1 if b >= a => (b + (b < 0))/c - (a - (a > 0))/c + (a <= 0 && b >= 0) 110 | case _ => throw new IllegalArgumentException 111 | } 112 | 113 | /** 114 | * List all unqiue divisors of n including 1 and n 115 | * O(sqrt(n)) 116 | * @param n 117 | * @return 118 | */ 119 | def divisors(n: Int) = { 120 | val divisors = Set.newBuilder[Int] 121 | for { 122 | i <- 1 to sqrt(n.toDouble).toInt if n%i == 0 123 | } divisors += i += n/i 124 | divisors.result() 125 | } 126 | 127 | /** 128 | * Count factors of all numbers upto n 129 | * @return f s.t. f(i) = number of factors of i (including 1 and i) 130 | */ 131 | def countFactors(n: Int) = { 132 | val f = Array[Int](n + 1) 133 | for { 134 | i <- 1 to n 135 | j <- i to n by i 136 | } f(j) = f(j) + 1 137 | f 138 | } 139 | 140 | /** 141 | * Pollard's rho factorization algorithm 142 | * O(n**(1/4)) -- birthday paradox 143 | * 144 | * @param n 145 | * @return A prime factor of n 146 | * If n itself is prime, will loop infinitely 147 | */ 148 | def pollardRho(n: Long): Option[Long] = Seq(2L, 3L, 5L, 7L).find(i => n%i == 0) orElse { 149 | def rand() = scala.util.Random.nextLong().abs%(n - 1) + 1 150 | 151 | def f(x: Long) = (x*x + 1)%n 152 | 153 | @tailrec 154 | def solve(tortoise: Long, hare: Long): Option[Long] = { 155 | gcd((tortoise - hare).abs, n) match { 156 | case 1 => solve(f(tortoise), f(f(hare))) 157 | case `n` => None // factor not found, very likely prime 158 | //case `n` => pollardRho(n) --> Use this line instead of above if you are sure input is composite 159 | case d => Some(d) 160 | } 161 | } 162 | 163 | solve(rand(), rand()) 164 | } 165 | 166 | def primeFactors(n: Long, acc: Counter[Long] = Counter.empty[Long]): acc.type = pollardRho(n) match { 167 | case Some(i) => primeFactors(n/i, acc += i) 168 | case _ => if (n > 1) acc += n else acc 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/OverlappingIntervals.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.mutable 4 | 5 | object OverlappingIntervals { 6 | type Interval = (Int, Int) 7 | 8 | /** 9 | * @return 10 | * A data structure that supports O(1) addition of an interval 11 | * and then O(1) querying of number of overlaps over any point 12 | */ 13 | def countOverlaps(intervals: TraversableOnce[Interval]): IndexedSeq[Int] = { 14 | val ends = mutable.IndexedSeq.empty[Int] 15 | intervals foreach { case (from, to) => 16 | ends(from) += 1 17 | ends(to) -= 1 18 | } 19 | ends.scanLeft(0)(_ + _) 20 | } 21 | 22 | /** 23 | * Merge a list of (possibly overlapping) closed-intervals into non-overlapping closed-intervals from left-to-right e.g.: 24 | * input: [(18, 19), (3, 9), (7, 10), (1, 5), (12, 17), (19, 21), (0, 6)] 25 | * output: [(0, 10), (12, 17), (18, 21)] 26 | */ 27 | def merge(intervals: Interval*): List[Interval] = { 28 | def solve(intervals: List[Interval]): List[Interval] = intervals match { 29 | case (x1, y1) :: (x2, y2) :: ps if y1 >= x2 => solve((x1, y1 max y2) :: ps) 30 | case p1 :: pt => p1 :: solve(pt) 31 | case _ => intervals 32 | } 33 | solve(intervals.toList.sorted) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/Randomized.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.util.Random 4 | import scala.math.Ordering.Implicits._ 5 | 6 | /** 7 | * Collection of randomized algorithms 8 | */ 9 | object Randomized { 10 | 11 | /** 12 | * Randomized algorithm to select the kth item in a sequence in amortised linear time 13 | * 14 | * @return the k-th item in s 15 | */ 16 | def quickSelect[A](s: Seq[A], k: Int)(implicit ord: Ordering[A]): A = { 17 | require(k >= 0 && k < s.size) 18 | val pivot = s((Math.random() * s.length).toInt) 19 | val table = s.groupBy(i => ord.compare(i, pivot)).withDefaultValue(Nil) 20 | val Seq(low, equal, high) = Seq(-1, 0, 1).map(table) 21 | if (k <= low.size) quickSelect(low, k) 22 | else if (k <= low.size + equal.size) pivot 23 | else quickSelect(high, k - low.size - equal.size) 24 | } 25 | 26 | /** 27 | * A Park-Miller Pseudo-random number generator (minimum standard one) 28 | * 29 | * @param i the seed e.g. use i = minstd(i) 30 | * @return a pseudo-random number 31 | */ 32 | def minstd(i: Int) = (i*16807) % Int.MaxValue 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/SegmentTree.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.reflect.ClassTag 4 | 5 | import Implicits._ 6 | 7 | /** 8 | * Segment tree: A data structure that lets you update indices and fold over intervals 9 | */ 10 | sealed trait SegmentTree[A] extends (Int => A) { 11 | def update(idx: Int, value: A): Unit 12 | def fold(z: A)(from: Int, to: Int): A 13 | } 14 | 15 | object SegmentTree { 16 | /** 17 | * @see http://codeforces.com/blog/entry/18051 18 | * O(n) to construct 19 | * O(1) for apply 20 | * O(log n) for update and fold 21 | * 22 | * @param f reducer commutative function, implementation can be changed to support non-commutative ones too 23 | * @tparam A 24 | */ 25 | def apply[A: ClassTag](data: Traversable[A])(f: (A, A) => A) = new SegmentTree[A] { 26 | private[this] val n = data.size 27 | private[this] val t = Array.ofDim[A](2*n) 28 | 29 | private[this] def build(i: Int) = t(i) = f(t(2*i), t(2*i + 1)) 30 | 31 | data.copyToArray(t, n) 32 | (n - 1) --> 1 foreach build 33 | 34 | override def apply(idx: Int) = t(idx + n) 35 | 36 | override def update(idx: Int, value: A) = { 37 | var p = idx + n 38 | t(p) = value 39 | while (p > 1) { 40 | p /= 2 41 | build(p) 42 | } 43 | } 44 | 45 | override def fold(z: A)(from: Int, to: Int) = { 46 | var result = z 47 | var l = from + n 48 | var r = to + n 49 | while (l <= r) { 50 | if (l%2 == 1) result = f(result, t(l)) 51 | if (r%2 == 0) result = f(t(r), result) 52 | l = (l + 1)/2 53 | r = (r - 1)/2 54 | } 55 | result 56 | } 57 | } 58 | 59 | def naive[A: ClassTag](data: Traversable[A])(f: (A, A) => A) = new SegmentTree[A] { 60 | private[this] val array: Array[A] = data.toArray 61 | override def apply(idx: Int) = array(idx) 62 | override def update(idx: Int, value: A) = array(idx) = value 63 | override def fold(z: A)(from: Int, to: Int) = array.slice(from, to + 1).foldLeft(z)(f) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/UnionFind.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.collection.generic.Clearable 4 | import scala.collection.mutable 5 | 6 | /** 7 | * Simpler implementation of @see DisjointSet 8 | */ 9 | class UnionFind[A] extends PartialFunction[A, A] with Clearable { 10 | private[this] val parent = mutable.Map.empty[A, A].withDefault(identity) 11 | 12 | private[this] def find(x: A): A = parent(x) match { 13 | case `x` => x 14 | case y => 15 | parent(x) = find(y) 16 | parent(x) 17 | } 18 | 19 | override def isDefinedAt(x: A) = parent.contains(x) 20 | 21 | override def apply(x: A) = find(x) 22 | 23 | def toMap: Map[A, A] = (parent.keySet ++ parent.values).map(u => u -> find(u)).toMap 24 | 25 | def sets: Map[A, Iterable[A]] = parent.keys.groupBy(find) 26 | 27 | def union(x: A, y: A): this.type = { 28 | // Randomized linking is O(a(n)) too: http://www.cis.upenn.edu/~sanjeev/papers/soda14_disjoint_set_union.pdf 29 | // If input is randomized we don't need randomization anyway: http://codeforces.com/blog/entry/21476 30 | // Without any linking heuristics but only path compression, it is O(log n) too: http://stackoverflow.com/questions/2323351/ 31 | if (scala.util.Random.nextBoolean()) parent(find(x)) = find(y) else parent(find(y)) = find(x) 32 | this 33 | } 34 | 35 | override def clear() = parent.clear() 36 | } 37 | 38 | object UnionFind { 39 | def apply[A](edges: Traversable[(A, A)]): UnionFind[A] = 40 | edges.foldLeft(new UnionFind[A]){case (state, (u, v)) => state.union(u, v)} 41 | } -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/games/Card.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos.games 2 | 3 | import collection.mutable 4 | 5 | import com.github.pathikrit.scalgos.Implicits.Crossable 6 | 7 | /** 8 | * Class to model a playing card 9 | */ 10 | case class Card(rank: Int, suit: Int) { 11 | override def toString = s"${Card ranks rank}${Card suits suit}" 12 | } 13 | 14 | object Card { 15 | implicit val rankOrdering = Ordering by {card: Card => card.rank} 16 | 17 | val (ranks, suits) = ("23456789TJQKA", "♣♠♦♥") 18 | 19 | val all = for {(rank, suit) <- ranks.indices X suits.indices} yield Card(rank, suit) 20 | 21 | def fromStr(s: String) = Card(ranks indexOf s(0).toUpper, "CSDH" indexOf s(1).toUpper) 22 | } 23 | 24 | /** 25 | * Class to model a deck of cards 26 | */ 27 | class Deck { 28 | val cards = mutable.Queue() ++ util.Random.shuffle(Card.all) 29 | 30 | def deal = cards.dequeue() 31 | 32 | def remove(discards: Set[Card]) = discards foreach {card => cards dequeueFirst {_ == card}} 33 | } 34 | 35 | /** 36 | * A class to model hand types in poker 37 | */ 38 | object PokerHandType extends Enumeration { 39 | val HighCard, OnePair, TwoPair, ThreeOfAKind, Straight, Flush, FullHouse, FourOfAKind, StraightFlush = Value 40 | 41 | /** 42 | * TODO: memoize this/bithack version 43 | * @return (h,s) where h is the hand type and s is sorted cards to break ties 44 | */ 45 | def classify(hand: Set[Card]) = { 46 | def rankMatches(card: Card) = hand count {_.rank == card.rank} 47 | val groups = hand groupBy rankMatches mapValues {_.toList.sorted} 48 | 49 | val isFlush = (hand groupBy {_.suit}).size == 1 50 | val isWheel = "A2345" forall {r => hand exists {_.rank == Card.ranks.indexOf(r)}} // A,2,3,4,5 straight 51 | val isStraight = groups.size == 1 && (hand.max.rank - hand.min.rank) == 4 || isWheel 52 | val (isThreeOfAKind, isOnePair) = (groups contains 3, groups contains 2) 53 | 54 | val handType = if (isStraight && isFlush) StraightFlush 55 | else if (groups contains 4) FourOfAKind 56 | else if (isThreeOfAKind && isOnePair) FullHouse 57 | else if (isFlush) Flush 58 | else if (isStraight) Straight 59 | else if (isThreeOfAKind) ThreeOfAKind 60 | else if (isOnePair && groups(2).size == 4) TwoPair 61 | else if (isOnePair) OnePair 62 | else HighCard 63 | 64 | val kickers = (1 until 5 flatMap groups.get).flatten.reverse 65 | require(hand.size == 5 && kickers.size == 5) 66 | (handType, if (isWheel) (kickers takeRight 4) :+ kickers.head else kickers) 67 | } 68 | 69 | import scala.math.Ordering.Implicits._ 70 | implicit val handOrdering: Ordering[Set[Card]] = Ordering.by(classify) 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/games/Maze.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos.games 2 | 3 | /** 4 | * Generic idea to solve maze style problems 5 | */ 6 | object Maze { 7 | 8 | implicit class Grid[A](a: Array[Array[A]]) { 9 | def apply(x: Int, y: Int) = for { 10 | i <- a lift x 11 | j <- i lift y 12 | } yield j 13 | } 14 | 15 | def explore[A](g: Array[Array[A]]) = for { 16 | x <- g.indices 17 | y <- g(x).indices 18 | dx <- -1 to 1 by 2 19 | dy <- -1 to 1 by 2 20 | i <- 1 to 1 // increase i for rook style 21 | v <- g.apply(x + i*dx, y + i*dy) 22 | } yield v 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/com/github/pathikrit/scalgos/games/Sudoku.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos.games 2 | 3 | import com.github.pathikrit.scalgos.Implicits.{BooleanExtensions, TraversableExtension} 4 | 5 | /** 6 | * Sudoku related algorithms 7 | * @param board underlying board (0 for empty cells) 8 | */ 9 | case class Sudoku(board: IndexedSeq[IndexedSeq[Int]]) { 10 | private[this] val n = board.length 11 | private[this] val s = Math.sqrt(n).toInt.ensuring(i => i*i == n, "Size must be a perfect square") 12 | require(board.forall(_.length == n), s"Board must be ${n}x$n") 13 | 14 | def solve: Option[Sudoku] = solve(0) 15 | 16 | /** 17 | * Solve this board 18 | * O(average elementary branching factor ^ empty cells) 19 | * 20 | * @param cell current cell (0 at start, 81 at end) 21 | * @return Some(solved board) if solution exists; else None 22 | */ 23 | private[Sudoku] def solve(cell: Int): Option[Sudoku] = (cell%n, cell/n) match { 24 | case (_, `n`) => isSolution then this 25 | case (r, c) if board(r)(c) > 0 => solve(cell + 1) 26 | case (r, c) => 27 | def used(i: Int) = Seq(board(r)(i), board(i)(c), board(s*(r/s) + i/s)(s*(c/s) + i%s)) 28 | def guess(x: Int) = (this(r, c) = x).solve(c + 1) 29 | 1 to n diff (board.indices flatMap used) firstDefined guess 30 | } 31 | 32 | /** 33 | * @return New board with (r,c) set to i 34 | */ 35 | def update(r: Int, c: Int, i: Int) = Sudoku(board.updated(r, board(r).updated(c, i))) 36 | 37 | /** 38 | * @return true iff board is in valid solved state 39 | */ 40 | def isSolution = { 41 | val expected = (1 to n).toSet 42 | board.indices forall {i => 43 | (board.indices map {c => board(i)(c)}).toSet == expected && 44 | (board.indices map {r => board(r)(i)}).toSet == expected && 45 | (board.indices map {j => board(s*(i/s) + j%s)(s*(i%s) + j/s)}).toSet == expected 46 | } 47 | } 48 | 49 | /** 50 | * @return pretty print sudoku board 51 | */ 52 | override def toString = board grouped s map {_ map {_ grouped s map {_ mkString " "} mkString " | "} mkString "\n"} mkString s"\n${"-" * 11 mkString " "}\n" 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/BinaryTreeSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.util.Random 4 | import org.specs2.mutable.Specification 5 | 6 | import BinaryTree._ 7 | 8 | class BinaryTreeSpec extends Specification { 9 | 10 | "reconstructBST" should { 11 | "do a round-trip" in { 12 | val preOrder = RandomData.list() 13 | preOrderTraversal(reconstructBST(preOrder)) must be equalTo preOrder 14 | }.pendingUntilFixed 15 | } 16 | 17 | "reconstruct" should { 18 | "do a round-trip" in { 19 | val inOrder = RandomData.list().distinct 20 | val preOrder = Random.shuffle(inOrder) 21 | preOrderTraversal(reconstruct(inOrder, preOrder)) must be equalTo preOrder 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/BitHacksSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import Implicits.Crossable 6 | import BitHacks._ 7 | 8 | class BitHacksSpec extends Specification { 9 | 10 | "flipCase" should { 11 | "work for characters" in todo 12 | // { 13 | // val lower = "abcdefghijklmnopqrstuvwxyz" 14 | // val upper = lower map flipCase 15 | // upper must be equalTo lower.toUpperCase 16 | // val lower2 = upper map flipCase 17 | // lower2 must be equalTo lower 18 | // } 19 | 20 | "fail for non characters" in todo 21 | } 22 | 23 | "swap" should { 24 | "work" in { 25 | examplesBlock { 26 | for (v <- (-100 to 100) X (-100 to 100)) { 27 | xorSwap(v) must be equalTo v.swap 28 | noTempSwap(v) must be equalTo v.swap 29 | } 30 | } 31 | } 32 | 33 | "work for Int.MaxValue & Int.MinValue" in todo 34 | } 35 | 36 | "gcd" should { 37 | "match euclid's algorithm" in { 38 | examplesBlock { 39 | for ((x,y) <- (-100 to 100) X (-100 to 100) if x!=0 || y!=0) { 40 | val g = gcd(x,y) 41 | x%g must be equalTo 0 42 | y%g must be equalTo 0 43 | g must be equalTo NumberTheory.gcd(x.toLong,y.toLong).toInt 44 | } 45 | } 46 | } 47 | 48 | "fail for (0,0)" in { 49 | gcd(0, 0) must throwA[IllegalArgumentException] 50 | } 51 | 52 | "work for Int.MaxValue and Int.MinValue" in todo 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/CombinatoricsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import Implicits._ 6 | import Memo._ 7 | import Combinatorics._ 8 | 9 | class CombinatoricsSpec extends Specification { 10 | 11 | "combinations" should { 12 | "work for 0 or 1 length" in todo 13 | 14 | "match the bitmask version" in todo 15 | 16 | "work for arbitrary input" in { 17 | val s = Seq(1, 2, 3) 18 | val result = combinations(s) map {_ mkString ("[", ",", "]")} 19 | result.length must be equalTo 1<n" in todo 100 | 101 | "match formula" in { 102 | examplesBlock { 103 | c.cache.size must be equalTo 0 104 | val t = 50 105 | for {n <- 0 to t; r <- 0 to n} { 106 | c(n, r) must be equalTo (n!)/(((n-r)!) * (r!)) 107 | } 108 | c.cache.size must be equalTo (t/2 + 1)*(t + 1) // TODO: why 109 | } 110 | } 111 | } 112 | 113 | "choose-bithacks" should { 114 | "fail for negative n or negative k" in todo 115 | 116 | "fail when k > n" in todo 117 | 118 | "match C(n,r)" in { 119 | examplesBlock { 120 | for { 121 | // TODO: start from 0? 122 | n <- 1 to 10 123 | r <- 1 to n 124 | } { 125 | val (a, e) = (choose(n, r), c(n, r)) 126 | a.length must be equalTo e.toInt 127 | a forall {i => i.bitCount == r} must beTrue 128 | } 129 | } 130 | } 131 | } 132 | 133 | "choose" should { 134 | "fail when n or one of rs is negative" in todo 135 | "fail when r.sum > n" in todo 136 | "work for empty r" in todo 137 | "work when r = 0 or 1" in todo 138 | "r.length = 1 must be same as c(n,r)" in todo 139 | "match formula" in { 140 | // todo for n <- 0 to 100, partitions <- 1 to n, randomly generate p n/p numbers 141 | // todo - what if n != r.sum? 142 | val n = 10 143 | val r = Seq(1,2,3,4) 144 | choose(n, r) must be equalTo((n!)/(r map factorial).product) 145 | } 146 | } 147 | 148 | "factorial" should { 149 | "fail for negative numbers" in todo 150 | 151 | "match known sequence" in todo 152 | } 153 | 154 | "fibonacci" should { 155 | "fail for negative numbers" in todo 156 | 157 | "match known sequence" in { 158 | val expected: List[BigInt] = List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55) 159 | ((0 to 10) map fibonacci).toList must be equalTo expected 160 | fibonacci(99) must be equalTo BigInt("218922995834555169026") 161 | } 162 | } 163 | 164 | "catalan" should { 165 | "fail for negative numbers" in todo 166 | 167 | "match known sequence" in { 168 | val expected: List[BigInt] = List(1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796) 169 | ((0 to 10) map catalan).toList must be equalTo expected 170 | } 171 | 172 | // "match recurrence relation" in { 173 | // examplesBlock { 174 | // def cat(n: Int): BigInt = ((0 until n) map {i => cat(i) * cat(n-i-1)}).sum 175 | // for (i <- 5 to 10) { 176 | // catalan(i) mustEqual cat(i) 177 | // } 178 | // } 179 | // } 180 | } 181 | 182 | "derangements" should { 183 | "fail for negative numbers" in todo 184 | "match the recurrence relation" in { 185 | 186 | lazy val d: Int ==> BigInt = Memo { 187 | case n if n < 0 => throw new IllegalArgumentException 188 | case 0 => 1 189 | case 1 => 0 190 | case n => (n-1) * (d(n-1) + d(n-2)) 191 | } 192 | 193 | examplesBlock { 194 | for (i <- 0 to 100) { 195 | derangement(i) must be equalTo d(i) 196 | } 197 | } 198 | } 199 | } 200 | 201 | "partialDerangement" should { 202 | "fail for negative numbers" in todo 203 | "match known sequence" in todo 204 | "be same as derangement for k = 0" in todo 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/CounterSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class CounterSpec extends Specification { 6 | "Counter" should { 7 | "count" in { 8 | val c = Counter.empty[String] 9 | c("world") must be equalTo 0 10 | c("hello") must be equalTo 0 11 | c("hello") = 2 12 | c("hello") must be equalTo 2 13 | c -= "hello" 14 | c("hello") must be equalTo 1 15 | c -= "hello" 16 | c("hello") must be equalTo 0 17 | c += ("hello", "world", "world") 18 | c("hello") must be equalTo 1 19 | c("world") must be equalTo 2 20 | c -= ("hello", "hello") 21 | c("hello") must be equalTo -1 22 | } 23 | 24 | "support map methods" in { 25 | val c = Counter("hello", "world", "hello", "world2") 26 | c count {_._2 > 1} must be equalTo 1 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/DisjointSetSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class DisjointSetSpec extends Specification { 6 | 7 | "DisjointSet" should { 8 | "work" in { 9 | val ds = DisjointSet(1 to 6: _*) 10 | (ds += 1) must throwA[AssertionError] 11 | ds union (2,3) 12 | ds union (4,5) 13 | ds union (5,6) 14 | ds union (0,6) must throwA[AssertionError] 15 | ds(1) must be equalTo 1 16 | ds(2) must be equalTo 2 17 | ds(3) must be equalTo 2 18 | ds(4) must be equalTo 4 19 | ds(5) must be equalTo 4 20 | ds(6) must be equalTo 4 21 | ds(7) must throwA[AssertionError] 22 | ds.sets must containTheSameElementsAs(Seq(Set(1), Set(2,3), Set(4,5,6))) 23 | } 24 | } 25 | 26 | //TODO: write scalacheck b/w DisjointSet and UnionFind 27 | 28 | "UnionFind" should { 29 | "work" in { 30 | val edges = Seq( 31 | 2 -> 3, 32 | 1 -> 2, 33 | 6 -> 4, 34 | 4 -> 5 35 | ) 36 | 37 | val ds = UnionFind(edges).toMap 38 | 39 | val expected = Map( 40 | 2 -> 1, 41 | 3 -> 1, 42 | 1 -> 1, 43 | 6 -> 4, 44 | 5 -> 4, 45 | 4 -> 4 46 | ) 47 | 48 | examplesBlock { 49 | expected.foreach({case (u, v) => assert(ds(u) === ds(v))}) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/DivideAndConquerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import DivideAndConquer._ 6 | 7 | class DivideAndConquerSpec extends Specification { 8 | 9 | "maxRectangleUnderHistogram" should { 10 | 11 | "work for small inputs" in { 12 | maxRectangleInHistogram(Nil) must be equalTo 0 13 | maxRectangleInHistogram(Seq(1)) must be equalTo 1 14 | maxRectangleInHistogram(Seq(1, 2)) must be equalTo 2 15 | maxRectangleInHistogram(Seq(2, 1)) must be equalTo 2 16 | maxRectangleInHistogram(Seq(1, 2, 3)) must be equalTo 4 17 | maxRectangleInHistogram(Seq(1, 3, 2)) must be equalTo 4 18 | maxRectangleInHistogram(Seq(3, 1, 2)) must be equalTo 3 19 | maxRectangleInHistogram(Seq(3, 1, 2)) must be equalTo 3 20 | maxRectangleInHistogram(Seq(2, 1, 3)) must be equalTo 3 21 | maxRectangleInHistogram(Seq(2, 3, 1)) must be equalTo 4 22 | } 23 | 24 | "work for zeroes" in { 25 | maxRectangleInHistogram(Seq(0)) must be equalTo 0 26 | maxRectangleInHistogram(Seq(0, 0)) must be equalTo 0 27 | } 28 | 29 | "fail for negative inputs" in todo 30 | 31 | "match the greedy algorithm" in { 32 | val heights = RandomData.positiveList() 33 | maxRectangleInHistogram(heights) must be equalTo Greedy.maxRectangleInHistogram(heights) 34 | } 35 | } 36 | 37 | "binarySearch" should { 38 | "work on discrete functions" in todo 39 | 40 | def avg(x: Double, y: Double) = (x+y)/2 41 | def sqrt(d: Double) = binarySearch[Double, Double](x => x*x, 0, d+1, avg, d) 42 | 43 | "work on continuous functions" in { 44 | def check(x: Double) = sqrt(x) must be ~(math.sqrt(x) +/- 1e-9) 45 | check(34) 46 | check(2) 47 | check(1e-19) 48 | //check(1e29) 49 | check(0) 50 | check(1) 51 | //todo: check for -ve inf, +ve inf and nans? 52 | } 53 | 54 | "fail to find when missing" in { 55 | //sqrt(-1) must be empty 56 | //sqrt(-0.0001) must be empty 57 | todo 58 | } 59 | 60 | "fail to find when max<=min" in todo 61 | "fail on boundary conditions" in todo 62 | } 63 | 64 | "ternarySearch" should { 65 | "find minima" in todo 66 | "find maxima" in todo 67 | "fail if f is not unimodal" in todo 68 | "fail if right < left" in todo 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/DynamicProgrammingSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import DynamicProgramming._ 6 | 7 | class DynamicProgrammingSpec extends Specification { 8 | 9 | "subsetSum" should { 10 | 11 | "work for empty set" in todo 12 | 13 | "work for only positive numbers" in todo 14 | 15 | "work for only negative numbers" in todo 16 | 17 | "always work for sum == 0" in todo 18 | 19 | "match brute force check" in { 20 | def bruteForceCheck(s: Seq[Int], t: Int) = Combinatorics.combinations(s) filter {_.sum == t} 21 | def normalized(sums: Seq[Seq[Int]]) = sums map {_.sorted} toSet //todo: why toSet? 22 | 23 | examplesBlock { 24 | for (i <- -50 to 50) { 25 | val nums = RandomData.list(length = 10) 26 | normalized(subsetSum(nums, i)) must be equalTo normalized(bruteForceCheck(nums, i)) 27 | } 28 | } 29 | } 30 | } 31 | 32 | "isSubsetSumAchievable" should { 33 | 34 | "work for empty set" in todo 35 | 36 | "work for only positive numbers" in todo 37 | 38 | "work for only negative numbers" in todo 39 | 40 | "always true for sum == 0" in todo 41 | 42 | "match brute force check" in { 43 | def bruteForceCheck(s: Seq[Int], t: Int) = Combinatorics.combinations(s) exists {_.sum == t} 44 | examplesBlock { 45 | for (i <- -50 to 50) { 46 | val nums = RandomData.list(length = 10) 47 | isSubsetSumAchievable(nums, i) must be equalTo bruteForceCheck(nums, i) 48 | } 49 | } 50 | } 51 | } 52 | 53 | "closestPartition" should { 54 | "work for trivial cases" in todo 55 | 56 | "match brute force algorithm" in { 57 | def closestDiff(s: Seq[Int]) = { 58 | val total = s.sum 59 | val sums = s.foldLeft(Set(0)){(p, i) => (p map {_ + i}) ++ p} 60 | sums minBy {i => (total - 2*i).abs} 61 | } 62 | 63 | examplesBlock { 64 | for(i <- 1 to 10000) { 65 | val n = RandomData.integer(end = 20) 66 | val nums = RandomData.list(n) 67 | val a = closestPartition(nums) 68 | val b = nums diff a 69 | val c = closestDiff(nums) 70 | val d = nums.sum - c 71 | (a.sum - b.sum).abs must be equalTo (c - d).abs 72 | } 73 | } 74 | } 75 | } 76 | 77 | "knapSack" should { 78 | import KnapSack._ 79 | 80 | "0-1 knapSack" should { 81 | "fail for invalid items or negative capacity" in todo 82 | "work for capacity = 0 or 1 and items.size = 0 or 1" in todo 83 | 84 | def totalValue(items: Seq[Item]) = {items map {_.value} sum} 85 | def totalWeight(items: Seq[Item]) = {items map {_.weight} sum} 86 | 87 | "match the brute force results" in { 88 | examplesBlock { 89 | for(i <- 1 to 100) { 90 | val n = 10 91 | val items = List.fill(n){Item(RandomData.integer(end = 20), RandomData.integer(start = 1, end = 10))} 92 | val maxWeight = RandomData.integer() 93 | val actual = KnapSack(items, maxWeight) 94 | val expected = Combinatorics.combinations(items) filter {totalWeight(_) <= maxWeight} maxBy totalValue 95 | 96 | totalValue(actual) mustEqual totalValue(expected) 97 | //actual mustEqual expected 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | "minimumChange" should { 105 | "fail for negative numbers" in todo 106 | "work for empty coin list" in todo 107 | "match brute force search" in todo 108 | } 109 | 110 | "editDistance" should { 111 | "work when either or both sequence is empty" in todo 112 | "work for completely mismatched sequences" in todo 113 | "work for arbitrary input" in todo 114 | } 115 | 116 | "validBrackets" should { 117 | "be list containing empty string for 0" in { 118 | validBrackets(0) must be equalTo IndexedSeq("") 119 | } 120 | 121 | "work for arbitrary input" in { 122 | validBrackets(1) must be equalTo IndexedSeq("()") 123 | validBrackets(2) must contain(exactly("()()", "(())")) 124 | validBrackets(3) must contain(exactly("()()()", "()(())", "(())()", "((()))", "(()())")) 125 | } 126 | 127 | "match catalan numbers" in { 128 | examplesBlock { 129 | for (i <- 0 to 10) { 130 | validBrackets(i).length must be equalTo Combinatorics.catalan(i).intValue 131 | } 132 | } 133 | } 134 | } 135 | 136 | "longestCommonSubsequence" should { 137 | implicit def toList(s: String) = s.toList 138 | 139 | "be empty if one of the input is empty" in { 140 | longestCommonSubsequence("hello", "") must beEmpty 141 | longestCommonSubsequence("", "nastenka") must beEmpty 142 | longestCommonSubsequence("", "") must beEmpty 143 | } 144 | 145 | "be empty if nothing in common" in { 146 | longestCommonSubsequence("abcdef", "ghijklmonopqr") must be empty 147 | } 148 | 149 | "work for arbitrary input" in { 150 | longestCommonSubsequence("patrick", "pathikrit") must be equalTo "patik" // TODO: patri? 151 | } 152 | } 153 | 154 | "longestIncreasingSubsequence" should { 155 | "be empty for empty sequence" in { 156 | longestIncreasingSubsequence(Seq[Int]()) must be empty 157 | } 158 | 159 | "have 1 item for decreasing sequence" in { 160 | longestIncreasingSubsequence(Seq(5, 4, 3, 2, 1)) must have length 1 161 | } 162 | 163 | "work for arbitrary input" in { 164 | val input = Seq(0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15) 165 | longestIncreasingSubsequence(input) must be equalTo Seq(0, 2, 6, 9, 11, 15) 166 | } 167 | 168 | "be same same as longestCommonSubsequence with sorted input" in { 169 | val s = RandomData.list().distinct // TODO: What happens when duplicates? 170 | val lis = longestIncreasingSubsequence(s) 171 | val expected = longestCommonSubsequence(s.sorted.reverse, s.reverse).reverse // bias towards "earlier" sequence 172 | lis must be equalTo expected 173 | lis.length must be equalTo longestCommonSubsequence(s, s.sorted).length 174 | } 175 | } 176 | 177 | "maxSubArraySum" should { 178 | "work on empty sequences" in { 179 | maxSubArraySum(Nil) must be equalTo 0 180 | } 181 | 182 | "work on small sequences" in { 183 | maxSubArraySum(Seq(1)) must be equalTo 1 184 | maxSubArraySum(Seq(-1)) must be equalTo 0 185 | maxSubArraySum(Seq(-1, -2)) must be equalTo 0 186 | maxSubArraySum(Seq(-1, 0, -2)) must be equalTo 0 187 | maxSubArraySum(Seq(-1, 0, 2)) must be equalTo 2 188 | maxSubArraySum(Seq(-1, 0)) must be equalTo 0 189 | maxSubArraySum(Seq(0)) must be equalTo 0 190 | maxSubArraySum(Seq(1)) must be equalTo 1 191 | } 192 | 193 | "match brute force algorithm" in { 194 | val s = RandomData.list() 195 | val sums = for (start <- s.indices; end <- start to s.length) yield s.slice(start, end).sum 196 | maxSubArraySum(s) must be equalTo sums.max 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/GeometrySpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import java.lang.Math._ 6 | 7 | import Geometry._ 8 | 9 | class GeometrySpec extends Specification { 10 | 11 | "intersects" should { 12 | "false when parallel" in todo 13 | "true when same" in todo 14 | "true when only endpoints touch" in todo 15 | "false when barely apart" in todo 16 | "false when on same line but different segment" in todo 17 | "work for arbitrary case" in todo 18 | } 19 | 20 | "crossProduct" should { 21 | "be zero when collinear" in todo 22 | "match determinant value" in todo 23 | } 24 | 25 | "areaOfTriangle" should { 26 | 27 | "be zero when collinear" in todo 28 | 29 | "match heron's formula" in { 30 | def heronsFormula(a: Point, b: Point, c: Point) = { 31 | val (ab, bc, ca) = ((a->b).length, (b->c).length, (c->a).length) 32 | val s = (ab + bc + ca)/2 33 | sqrt(s*(s-ab)*(s-bc)*(s-ca)) 34 | } 35 | 36 | def points = RandomData.points(howMany = 1).head 37 | val (a,b,c) = (points, points, points) 38 | areaOfTriangle(a, b, c) must be ~(heronsFormula(a, b, c) +/- 1e-9) 39 | } 40 | } 41 | 42 | "grahamScan" should { 43 | 44 | "fail for <3 points" in todo 45 | "work for triangles" in todo 46 | 47 | "include extremities" in { 48 | val points = RandomData.points() 49 | val hull = grahamScan(points) 50 | val extremities: Array[Point => Double] = Array(_.x, _.y, _.manhattan /*,p => (p.x - p.y).abs*/) 51 | val extremes = (extremities map {points minBy _}) ++ (extremities map {points maxBy _}) 52 | extremes.size must be greaterThanOrEqualTo 3 53 | //todo: hull must contain(allOf(extremes)) 54 | todo 55 | } 56 | 57 | "work for circles" in todo 58 | "match jarvis march" in todo 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/GraphSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import RandomData.Graphs 6 | import Graph._ 7 | import Implicits._ 8 | 9 | class GraphSpec extends Specification { 10 | 11 | "graph" should { 12 | "work" in todo 13 | } 14 | 15 | "dijkstra" should { 16 | "work for empty graphs" in { 17 | val g = Graphs.zero 18 | dijkstra(g, 0, 1) must throwA[AssertionError] 19 | dijkstra(g, 0, 0) must throwA[AssertionError] 20 | } 21 | 22 | "work for graphs with 1 or 2 vertices" in todo 23 | 24 | "work for graphs with no edges" in { 25 | val g = Graphs.noEdges 26 | examplesBlock { 27 | for { 28 | (u, v) <- g.vertices X g.vertices 29 | } dijkstra(g, u, v) match { 30 | case Some(Result(cost, path)) => 31 | cost must be equalTo 0 32 | u must be equalTo v 33 | 34 | case None => 35 | u mustNotEqual v 36 | } 37 | } 38 | } 39 | 40 | "may not work for negative edges" in { 41 | val g = new Graph(numberOfVertices = 4) 42 | g(0->1) = 5 43 | g(1->2) = 5 44 | g(0->2) = 1 45 | g(0->3) = 100 46 | g(3->1) = -200 47 | val Some(Result(distance, path)) = dijkstra(g, 0, 2) 48 | distance must be equalTo 1 // Dijkstra says shortest path is 1 49 | val (cost, paths) = bellmanFord(g, 0) 50 | cost(2) must be equalTo -95 // But, bellmanFord says its -95 51 | } 52 | 53 | "match floyd-warshall" in { 54 | val g = RandomData.graph() 55 | val f = floydWarshall(g) 56 | examplesBlock { 57 | for { 58 | (i, j) <- g.vertices X g.vertices 59 | d = dijkstra(g, i, j) 60 | } if (f(i)(j) isPosInfinity) { 61 | d must beNone 62 | } else { 63 | val Some(Result(distance, path)) = d 64 | (path.head -> path.last) must be equalTo (i -> j) 65 | distance must be ~(f(i)(j) +/- 1e-9) 66 | } 67 | } 68 | } 69 | } 70 | 71 | "floyd-warshall" should { 72 | "work for empty graphs" in todo 73 | "work for graphs with 1 or 2 vertices" in todo 74 | "work for graphs with no edges" in todo 75 | "work for arbitrary input" in todo 76 | "work for no path from start to end" in todo 77 | "work when start is goal" in todo 78 | "handle negative weight cycles" in todo 79 | "match bellman-ford algorithm" in todo 80 | } 81 | 82 | "minimum-spanning-tree" should { 83 | 84 | "kruskalsMst" should { 85 | "work for graphs with 0, 1 or 2 vertices" in todo 86 | "work for graphs with no edges" in todo 87 | "fail for disjoint graphs" in todo 88 | "fail for directed graphs" in todo 89 | } 90 | 91 | "primsMst" should { 92 | "work for graphs with 0, 1 or 2 vertices" in todo 93 | "work for graphs with no edges" in todo 94 | "fail for disjoint graphs" in todo 95 | "fail for directed graphs" in todo 96 | } 97 | 98 | "prims match kruskals" in { 99 | val g = RandomData.graph(numberOfVertices = 100, edgeDensity = 1, isPositiveEdges = true, isDirected = false) 100 | def cost(edges: Set[(Int, Int)]) = (edges map g.apply).sum 101 | val (p, k) = (primsMst(g), kruskalsMst(g)) 102 | cost(p) mustEqual cost(k) 103 | // todo: check for cycle, coverage, vertex incidence etc 104 | } 105 | } 106 | 107 | "stronglyConnectedComponents" should { 108 | /** 109 | * Given a graph and its strongly connected components check if all vertices are covered and mutual exclusion 110 | */ 111 | def checkCoverage(g: Graph, sccs: Seq[Set[Int]]) = { 112 | for { 113 | (s1, s2) <- sccs X sccs if s1 != s2 114 | } s1.intersect(s2) must be empty 115 | 116 | sccs.flatten must containTheSameElementsAs(g.vertices) 117 | } 118 | 119 | "work for empty graphs" in { 120 | val g = Graphs.zero 121 | val components = stronglyConnectedComponents(g) 122 | checkCoverage(g, components) 123 | components must be empty 124 | } 125 | 126 | "work for graphs with 1 or 2 vertices" in todo 127 | 128 | "work for graphs with no edges" in { 129 | val g = Graphs.noEdges 130 | val components = stronglyConnectedComponents(g) 131 | checkCoverage(g, components) 132 | components must be length g.numberOfVertices 133 | } 134 | 135 | "work on a clique" in { 136 | val g = Graphs.clique 137 | val components = stronglyConnectedComponents(g) 138 | checkCoverage(g, components) 139 | components must be length 1 140 | } 141 | 142 | "match floyd-warshall" in { 143 | val g = RandomData.graph(numberOfVertices = 20, edgeDensity = 0.1) 144 | val f = floydWarshall(g) 145 | 146 | def inCycle(u: Int, v: Int) = !f(u)(v).isPosInfinity && !f(v)(u).isPosInfinity 147 | 148 | def checkCycle(scc: Set[Int]) = for ((u, v) <- scc X g.vertices) (scc contains v) must be equalTo inCycle(u,v) 149 | 150 | val components = stronglyConnectedComponents(g) 151 | checkCoverage(g, components) 152 | examplesBlock {components foreach checkCycle} 153 | } 154 | } 155 | 156 | "bellman-ford" should { 157 | "work for empty graphs" in todo 158 | "work for graphs with 1 or 2 vertices" in todo 159 | "work for graphs with no edges" in todo 160 | "work for arbitrary input" in todo 161 | 162 | "handle negative weight cycles" in todo 163 | 164 | "match dijkstra for positive graphs" in { 165 | def trace(parents: Seq[Int], v: Int): Seq[Int] = if(parents(v) < 0) Seq(v) else trace(parents, parents(v)) :+ v 166 | 167 | val g = RandomData.graph() 168 | examplesBlock { 169 | for { 170 | u <- g.vertices 171 | (distances, parents) = bellmanFord(g, u) 172 | v <- g.vertices 173 | } dijkstra(g, u, v) match { 174 | case None => 175 | distances(v).isPosInfinity must beTrue 176 | // TODO: no path to parent or loop here 177 | 178 | case Some(Result(distance, path)) => 179 | distances(v) must be ~(distance +/- 1e-9) 180 | trace(parents, v) must be equalTo path // might be different paths with same cost - random chance of failure! 181 | } 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/GreedySpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import scala.collection.mutable 6 | 7 | import Greedy._ 8 | 9 | class GreedySpec extends Specification { 10 | "maxRectangleInHistogram" should { 11 | "work for small inputs" in todo 12 | 13 | "work for zeroes" in todo 14 | 15 | "fail for negative or zero widths inputs" in todo 16 | 17 | "match the naive implementation" in { 18 | def maxRectangleInHistogram2(heights: Seq[Int]) = { 19 | val stack = new mutable.Stack[(Int, Int)] 20 | var ans = 0 21 | for ((h, i) <- (heights :+ 0).zipWithIndex) { 22 | var pos = i 23 | while(stack.headOption.exists(_._1 > h)) { 24 | val (y, x) = stack.pop() 25 | ans = ans max (y * (i - x)) 26 | pos = x 27 | } 28 | stack push (h->pos) 29 | } 30 | ans 31 | } 32 | val blocks = RandomData.list(min = 0, max = 100) 33 | maxRectangleInHistogram(blocks) must be equalTo maxRectangleInHistogram2(blocks) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/ImplicitsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import Implicits._ 6 | 7 | class ImplicitsSpec extends Specification { 8 | "FuzzyDouble" should { 9 | "work" in todo 10 | } 11 | 12 | "IntExtensions" should { 13 | "have correct mod" in { 14 | examplesBlock { 15 | for { 16 | (x, y) <- (-100 to 100) X (-100 to 100) if y != 0 17 | m = x mod y 18 | } { 19 | ((x/y)*y + m) must be equalTo x 20 | (x-m)%y must be equalTo 0 21 | } 22 | } 23 | } 24 | } 25 | 26 | "ForwardPipe" should { 27 | "work" in todo 28 | } 29 | 30 | "RemovableList" should { 31 | "work" in todo 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/IntervalMapSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import IntervalMap._ 4 | 5 | import org.specs2.mutable.Specification 6 | 7 | class IntervalMapSpec extends Specification { 8 | 9 | "match brute force implementation" in { 10 | class DumbIntervalMap[A] extends IntervalMap[A] { 11 | import collection.mutable 12 | 13 | private val segments = mutable.Map.empty[Int, A] 14 | 15 | implicit def toRange(s: Interval) = s.start until s.end 16 | 17 | def update(r: Interval, value: A) = r foreach {i => segments(i) = value} 18 | 19 | def apply(x: Int) = segments get x 20 | 21 | def clear(r: Interval) = r foreach segments.remove 22 | 23 | def toSeq = throw new UnsupportedOperationException 24 | 25 | override def toString = segments.toSeq sortBy {_._1} map {case (i, v) => s"$i: $v"} mkString ", " 26 | } 27 | 28 | val map1 = IntervalMap.empty[Int] 29 | val map2 = new DumbIntervalMap[Int] 30 | val (n, r, p, v) = (10000, 100, 0.8, 20) 31 | 32 | examplesBlock { 33 | import RandomData._ 34 | for (i <- 1 to n) { 35 | val start = integer(-n, n) 36 | val end = integer(start, n+1) 37 | 38 | if(number() < p) { 39 | val value = integer(v) 40 | map1(start -> end) = value 41 | map2(start -> end) = value 42 | } else { 43 | map1 clear (start -> end) 44 | map2 clear (start -> end) 45 | } 46 | 47 | (-2*r to 2*r) forall {i => map1(i) == map2(i)} must beTrue 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/LinearAlgebraSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import RandomData.matrix 6 | 7 | import LinearAlgebra._ 8 | 9 | class LinearAlgebraSpec extends Specification { 10 | 11 | "multiply" should { 12 | 13 | "work for 0/1 matrices" in todo 14 | 15 | "fail for incorrect size" in todo 16 | 17 | "work" in { 18 | val (a, b) = (matrix(15, 20), matrix(20, 30)) 19 | val c = Array.ofDim[Double](a.rows, b.cols) 20 | 21 | for { 22 | i <- 0 until a.rows 23 | j <- 0 until b.cols 24 | k <- 0 until a.cols 25 | } c(i)(j) = c(i)(j) + a(i, k) * b(k, j) 26 | 27 | todo 28 | } 29 | } 30 | 31 | "evalPolynomial" should { 32 | 33 | "work for 0/1 length coeffs" in todo 34 | 35 | "match definition" in { 36 | def recursiveEval(coeffs: List[Double])(x: Double): Double = coeffs match { 37 | case Nil => 0 38 | case c :: cs => c + x*recursiveEval(cs)(x) 39 | } 40 | 41 | val n = 10 42 | val a = ((1 to n) map {i => RandomData.number(0, 10)}).toList 43 | val x = RandomData.number(-5, 5) 44 | Polynomial(a)(x) must be ~(recursiveEval(a)(x) +/- 1e-9) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/MacrosSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import Macros._ 6 | 7 | class MacrosSpec extends Specification { 8 | 9 | "profile" should { 10 | "work" in { 11 | val (result, time, memory) = profile { 12 | val x = 20 13 | def fib(n: Int): Int = if (n <= 1) n else fib(n-1) + fib(n-2) 14 | fib(x) 15 | } 16 | result must be equalTo 6765 17 | time must be greaterThan 0 18 | memory must be greaterThan 32 19 | } 20 | } 21 | 22 | "download" should { 23 | "work" in todo 24 | } 25 | 26 | "lruCache" should { 27 | "work" in todo 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/NumberTheorySpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable._ 4 | 5 | import scala.collection.mutable 6 | import scala.collection.immutable.BitSet 7 | 8 | import Implicits.Crossable 9 | import NumberTheory._ 10 | 11 | class NumberTheorySpec extends Specification { 12 | 13 | "sieveOfEratosthnes" should { 14 | "not work for negative numbers" in todo 15 | 16 | "work for n = 0,1,2,3" in todo 17 | 18 | "include n when n is a prime" in todo 19 | 20 | "match actual isPrime check" in { 21 | val n = 100000 22 | val primeSet = sieveOfEratosthenes(n) 23 | examplesBlock { 24 | for (i <- 1 to n) { 25 | primeSet(i) must be equalTo isPrime(i) 26 | } 27 | } 28 | } 29 | } 30 | 31 | "sieveOfSundaram" should { 32 | "not work for negative numbers" in todo 33 | 34 | "match the sieveOfEratosthenes" in todo 35 | // { 36 | // for (i <- 3 to 1000) { 37 | // BitSet(sieveOfSundaram(i): _*) must be equalTo (sieveOfEratosthenes(i) - 2) 38 | // } 39 | // } 40 | } 41 | 42 | "phi" should { 43 | "fail for negative/zero inputs" in todo 44 | 45 | "match brute force" in { 46 | val n = 100 47 | val phi = phis(n) 48 | def phi2(n: Int) = sieveOfEratosthenes(n).foldLeft(n){(phi, p) => if (phi%p == 0) phi - phi/p else phi} 49 | examplesBlock { 50 | for (i <- 1 to n) { 51 | phi(i) must be equalTo ((1 to i) count {gcd(i, _) == 1}) 52 | phi(i) must be equalTo phi2(i) 53 | } 54 | } 55 | } 56 | } 57 | 58 | "gcd" should { 59 | "match brute force" in { 60 | def commonMultiples(a: Int, b: Int) = {(1 to (a.abs max b.abs)) filter (g => a%g == 0 && b%g == 0)} 61 | examplesBlock { 62 | for ((x,y) <- (-100 to 100) X (-100 to 100) if x!=0 || y!=0) { 63 | val g = gcd(x,y) 64 | x%g must be equalTo 0 65 | y%g must be equalTo 0 66 | g must be equalTo commonMultiples(x,y).max 67 | } 68 | } 69 | } 70 | 71 | "fail for (0,0)" in { 72 | gcd(0,0) must throwA[AssertionError] 73 | } 74 | 75 | "work for Int.MaxValue and Int.MinValue" in todo 76 | } 77 | 78 | "extendedEuclidean algorithm" should { 79 | "match Bézout's identity" in { 80 | examplesBlock { 81 | for ((a,b) <- (-100 to 100) X (-100 to 100) if a!=0 || b!=0) { 82 | val (x,y) = extendedEuclidean(a,b) 83 | // todo: do we really need abs here? 84 | a.abs*x + b.abs*y must be equalTo gcd(a.toLong, b.toLong).toInt 85 | } 86 | } 87 | } 88 | 89 | "fail for (0,0)" in { 90 | extendedEuclidean(0,0) must throwA[AssertionError] 91 | } 92 | 93 | "work for Int.MaxValue and Int.MinValue" in todo 94 | } 95 | 96 | "lcm" should { 97 | "match brute force" in todo 98 | "work for (0,0)" in todo 99 | "work for Int.MaxValue & Int.MinValue" in todo 100 | } 101 | 102 | "numberOfMultiples" should { 103 | "fail for invalid inputs (c = 0 or a>b)" in todo 104 | 105 | "match brute force" in { 106 | val n = 100 107 | examplesBlock { 108 | for { 109 | c <- -n to n 110 | a <- -n to n 111 | b <- -n to n 112 | } if (a > b || c == 0) { 113 | numberOfMultiples(a,b,c) must throwA[IllegalArgumentException] 114 | } else { 115 | numberOfMultiples(a,b,c) must be equalTo ((a to b) count {_ % c == 0}) 116 | } 117 | } 118 | } 119 | 120 | "works for -2^31 to 2^31-1" in todo 121 | } 122 | 123 | "countFactors" should { 124 | "fail for invalid inputs e.g. negative n" in todo 125 | 126 | "match brute force in" in { 127 | def slowCount(k: Int) = (1 to k/2) count {i => k%i == 0} 128 | val n = 1000 129 | val f = countFactors(n) 130 | (0 to n) forall {i => f(i) == slowCount(i)} must beTrue 131 | }.pendingUntilFixed 132 | } 133 | 134 | "primeFactorization" should { 135 | "fail for invalid inputs e.g. <= 0" in todo 136 | "match product of factors" in todo 137 | 138 | "work" in { 139 | primeFactors(7477955261L).toMap must be equalTo Map( 140 | 104723L -> 1, 141 | 101L -> 2, 142 | 7L -> 1 143 | ) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/OverlappingIntervalSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable._ 4 | 5 | class OverlappingIntervalSpec extends Specification { 6 | 7 | "merge" should { 8 | "merge" in { 9 | val actual = OverlappingIntervals.merge((18, 19), (3, 9), (7, 10), (1, 5), (12, 17), (19, 21), (0, 6)) 10 | val expected = List((0, 10), (12, 17), (18, 21)) 11 | actual shouldEqual expected 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/RandomData.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import scala.util.Random._ 4 | 5 | import Implicits.Crossable 6 | import Geometry.Point 7 | import LinearAlgebra.Matrix2D 8 | 9 | /** 10 | * Has utility methods for random data generation 11 | */ 12 | object RandomData { 13 | 14 | /** 15 | * @return Random Integer in [start, end] 16 | */ 17 | def integer(start: Int = 0, end: Int = 100) = { 18 | assume(end > start) 19 | start + nextInt(end - start + 1) 20 | } 21 | 22 | /** 23 | * @return Random Double in [start, end] 24 | */ 25 | def number(start: Double = 0, end: Double = 1) = { 26 | assume(end > start) 27 | start + (end - start)*nextDouble() 28 | } 29 | 30 | /** 31 | * @return Random list of numbers in [min, max] of size = length 32 | */ 33 | def list(length: Int = 100, min: Int = -10, max: Int = 10) = List.fill(length)(integer(min, max)) 34 | 35 | /** 36 | * @return Random IndexedSeq of numbers in [min, max] of size = length 37 | */ 38 | def seq(length: Int = 100, min: Int = -10, max: Int = 10) = IndexedSeq.fill(length)(integer(min, max)) 39 | 40 | /** 41 | * @return Random list of numbers in [0, max] of size = length 42 | */ 43 | def positiveList(length: Int = 100, max: Int = 10) = list(length, 0, max) 44 | 45 | /** 46 | * @return Atmost howMany unique points in in rectangle (minX, minY) - (maxX, maxY) 47 | */ 48 | def points(minX: Int = -10, minY: Int = -10, maxX: Int = 10, maxY: Int = 10, howMany: Int = 100) = 49 | {for (i <- 1 to howMany) yield new Point((number(minX, maxX), number(minY, maxY)))}.toSet 50 | 51 | /** 52 | * Generate random graph 53 | * 54 | * @param numberOfVertices number of vertices in graph 55 | * @param edgeDensity number of edges = edge_density * v*v/2 56 | * @param isPositiveEdges if edges must be positive 57 | * @param isDirected true iff graph must be directed 58 | * @return a random graph 59 | */ 60 | def graph(numberOfVertices: Int = 100, edgeDensity: Double = 0.25, 61 | isPositiveEdges: Boolean = true, isDirected: Boolean = true) = { 62 | assume(numberOfVertices >= 0) 63 | assume(edgeDensity >= 0 && edgeDensity <= 1) 64 | 65 | val g = new Graph(numberOfVertices, isDirected) 66 | 67 | for { 68 | (i, j) <- g.vertices X g.vertices if i != j && number() < edgeDensity 69 | } g(i->j) = number(if (isPositiveEdges) 0 else -10, 10) 70 | 71 | g 72 | } 73 | 74 | /** 75 | * Some special trivial graphs 76 | */ 77 | object Graphs { 78 | def zero = graph(numberOfVertices = 0) 79 | def one = graph(numberOfVertices = 1) 80 | def two = graph(numberOfVertices = 2, edgeDensity = 1) 81 | def noEdges = graph(edgeDensity = 0) 82 | def clique = graph(edgeDensity = 1) 83 | } 84 | 85 | /** 86 | * Generate random matrix 87 | * 88 | * @param r rows 89 | * @param c columns 90 | * @return a random matrix with r rows and c columns 91 | */ 92 | def matrix(r: Int, c: Int): Matrix2D = Array.tabulate(r, c){(i, j) => number(-100, 100)} 93 | 94 | object Matrices { 95 | def square = matrix(10, 10) 96 | def rectangle = matrix(15, 20) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/RandomizedSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import Randomized._ 6 | 7 | class RandomizedSpec extends Specification { 8 | 9 | "quickSelect" should { 10 | "match naive sort based algorithm" in { 11 | val s = RandomData.list() 12 | val k = RandomData.integer(0, s.length-1) 13 | quickSelect(s, k) must be equalTo s.sorted.apply(k) 14 | } 15 | 16 | "match median-of-medians algorithm" in todo 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/SegmentTreeSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class SegmentTreeSpec extends Specification { 6 | "segment-tree" should { 7 | "match naive implementation" in { 8 | val data = RandomData.seq() 9 | val functions: Seq[(Int, Int) => Int] = Seq(_ + _, _ min _) 10 | 11 | examplesBlock { 12 | for { 13 | f <- functions 14 | s1 = SegmentTree.apply(data)(f) 15 | s2 = SegmentTree.naive(data)(f) 16 | i <- data.indices 17 | j <- i + 1 until data.length 18 | } s1.fold(Int.MaxValue)(i, j) must be equalTo s2.fold(Int.MaxValue)(i, j) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/games/CardSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos.games 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | import Card._ 6 | import PokerHandType._ 7 | 8 | class CardSpec extends Specification { 9 | 10 | "hand evaluation" should { 11 | 12 | implicit def toCardSeq(s: String): Seq[Card] = (s split " " map fromStr).toSeq 13 | 14 | "work" in { 15 | def test(cards: String, expectedHand: PokerHandType.Value, kickers: Seq[Card]) = { 16 | val (hand, sorted) = classify(toCardSeq(cards).toSet) 17 | hand must be equalTo expectedHand 18 | sorted zip kickers foreach {p => p._1.rank must be equalTo p._2.rank} 19 | } 20 | 21 | // todo: use datatables 22 | examplesBlock { 23 | test("qc kc tc ac jc", StraightFlush , "Ac kc qc jc tc") 24 | test("5c 4c 3c ac 2c", StraightFlush , "5c 4c 3c 2c ac") 25 | test("5c 4c 3c 6c 2c", StraightFlush , "6c 5c 4c 3c 2c") 26 | test("5d As 5c 5h 5s", FourOfAKind , "5s 5c 5d 5h As") 27 | test("Td KH tc Th ks", FullHouse , "Td Th Tc Kh Ks") 28 | test("qc kc 9c ac jc", Flush , "Ac kc qc jc 9c") 29 | test("2c 5H 4S 3D ad", Straight , "5h 4s 3d 2c ac") 30 | test("kc qH aS jD td", Straight , "as kc QH JD TD") 31 | test("9c AH AS AD 8d", ThreeOfAKind , "AH AS AD 9c 8d") 32 | test("ac 2s 2d 3s 3d", TwoPair , "3s 3d 2s 2d ac") 33 | test("3d 3s ah td js", OnePair , "3s 3d ah js td") 34 | test("2d 3s ah td js", HighCard , "ah js td 3s 2d") 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/scala/com/github/pathikrit/scalgos/games/SudokuSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.pathikrit.scalgos.games 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class SudokuSpec extends Specification { 6 | 7 | "solve" should { 8 | "fail on boards that are not 9x9" in todo 9 | "fail on boards that contain anything other than 0-9" in todo 10 | "false on invalid solved boards" in todo 11 | "false and input not modified on insolvable boards" in todo 12 | 13 | "solve a normal solution" in { 14 | import scala.collection.{IndexedSeq => $} 15 | Sudoku( 16 | $( 17 | $(1, 0, 0, 0, 0, 7, 0, 9, 0), 18 | $(0, 3, 0, 0, 2, 0, 0, 0, 8), 19 | $(0, 0, 9, 6, 0, 0, 5, 0, 0), 20 | $(0, 0, 5, 3, 0, 0, 9, 0, 0), 21 | $(0, 1, 0, 0, 8, 0, 0, 0, 2), 22 | $(6, 0, 0, 0, 0, 4, 0, 0, 0), 23 | $(3, 0, 0, 0, 0, 0, 0, 1, 0), 24 | $(0, 4, 0, 0, 0, 0, 0, 0, 7), 25 | $(0, 0, 7, 0, 0, 0, 3, 0, 0) 26 | ) 27 | ).solve.map(_.toString) must beSome(""" 28 | |1 6 2 | 8 5 7 | 4 9 3 29 | |5 3 4 | 1 2 9 | 6 7 8 30 | |7 8 9 | 6 4 3 | 5 2 1 31 | |- - - - - - - - - - - 32 | |4 7 5 | 3 1 2 | 9 8 6 33 | |9 1 3 | 5 8 6 | 7 4 2 34 | |6 2 8 | 7 9 4 | 1 3 5 35 | |- - - - - - - - - - - 36 | |3 5 6 | 4 7 8 | 2 1 9 37 | |2 4 1 | 9 3 5 | 8 6 7 38 | |8 9 7 | 2 6 1 | 3 5 4 """.stripMargin.trim) 39 | } 40 | 41 | "find multiple solutions" in todo 42 | "work on the fully empty board" in todo 43 | } 44 | } 45 | --------------------------------------------------------------------------------