diff --git a/build.sbt b/build.sbt index 0f4ad5c..89896e6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,33 +1,27 @@ +import com.typesafe.sbt.SbtScalariform._ name := "ascii-graphs" organization := "com.github.mdr" -version := "0.0.6" +version := "0.0.7" -scalaVersion := "2.12.1" +scalaVersion := "2.10.6" -crossScalaVersions := Seq("2.9.1", "2.9.2", "2.10.1") +crossScalaVersions := Seq("2.10.6") -scalacOptions ++= Seq("-deprecation") +scalacOptions ++= Seq("-deprecation", "-Xlint") -javacOptions ++= Seq("-source", "1.6", "-target", "1.6") +javacOptions ++= Seq("-source", "1.8", "-target", "1.8") libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test" libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.13.4" % "test" -// Screen-sized dependency graph: -// libraryDependencies += "org.vert-x" % "vertx-core" % "1.3.1.final" - EclipseKeys.withSource := true EclipseKeys.eclipseOutput := Some("bin") -//net.virtualvoid.sbt.graph.Plugin.graphSettings - -import com.typesafe.sbt.SbtScalariform.ScalariformKeys - -ScalariformKeys.preferences <<= baseDirectory.apply { dir => +ScalariformKeys.preferences <<= baseDirectory.apply { dir => scalariform.formatter.preferences.PreferencesImporterExporter.loadPreferences((dir / "formatterPreferences.properties").getPath) } @@ -36,7 +30,7 @@ publishMavenStyle := true publishArtifact in Test := false publishTo <<= isSnapshot( - if (_) Some("snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/") + if (_) Some("snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/") else Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/")) pomExtra := { diff --git a/src/main/scala/com/github/mdr/ascii/common/Point.scala b/src/main/scala/com/github/mdr/ascii/common/Point.scala index c203748..8f3671f 100644 --- a/src/main/scala/com/github/mdr/ascii/common/Point.scala +++ b/src/main/scala/com/github/mdr/ascii/common/Point.scala @@ -2,25 +2,33 @@ package com.github.mdr.ascii.common object Point { - private def sameColumn(p1: Point, p2: Point, p3: Point) = p1.column == p2.column && p2.column == p3.column - private def sameRow(p1: Point, p2: Point, p3: Point) = p1.row == p2.row && p2.row == p3.row - private def colinear(p1: Point, p2: Point, p3: Point) = sameColumn(p1, p2, p3) || sameRow(p1, p2, p3) + private def sameColumn(p1: Point, p2: Point, p3: Point) = + p1.column == p2.column && p2.column == p3.column + private def sameRow(p1: Point, p2: Point, p3: Point) = + p1.row == p2.row && p2.row == p3.row + private def colinear(p1: Point, p2: Point, p3: Point) = + sameColumn(p1, p2, p3) || sameRow(p1, p2, p3) def removeRedundantPoints(points: List[Point]): List[Point] = points match { - case List() | List(_) | List(_, _) ⇒ points - case p1 :: p2 :: p3 :: remainder if colinear(p1, p2, p3) ⇒ removeRedundantPoints(p1 :: p3 :: remainder) - case p :: ps ⇒ p :: removeRedundantPoints(ps) + case List() | List(_) | List(_, _) ⇒ points + case List(p1, p2, p3, remainder @ _*) if colinear(p1, p2, p3) ⇒ + removeRedundantPoints(List(p1, p3) ::: remainder.toList) + case List(p, ps @ _*) ⇒ List(p) ::: removeRedundantPoints(ps.toList) } } -case class Point(row: Int, column: Int) extends Translatable[Point] with Transposable[Point] { +case class Point(row: Int, column: Int) + extends Translatable[Point] + with Transposable[Point] { - def maxRowCol(that: Point): Point = Point(math.max(this.row, that.row), math.max(this.column, that.column)) + def maxRowCol(that: Point): Point = + Point(math.max(this.row, that.row), math.max(this.column, that.column)) type Self = Point - def translate(down: Int = 0, right: Int = 0): Point = Point(row + down, column + right) + def translate(down: Int = 0, right: Int = 0): Point = + Point(row + down, column + right) def transpose: Point = Point(column, row) diff --git a/src/main/scala/com/github/mdr/ascii/layout/drawing/KinkRemover.scala b/src/main/scala/com/github/mdr/ascii/layout/drawing/KinkRemover.scala index 3435ca5..295a947 100644 --- a/src/main/scala/com/github/mdr/ascii/layout/drawing/KinkRemover.scala +++ b/src/main/scala/com/github/mdr/ascii/layout/drawing/KinkRemover.scala @@ -1,148 +1,198 @@ -package com.github.mdr.ascii.layout.drawing - -import scala.annotation.tailrec -import com.github.mdr.ascii.common.Direction._ -import com.github.mdr.ascii.common.Point -import com.github.mdr.ascii.util.Utils._ -import com.github.mdr.ascii.common.Direction - -/** - * Remove kinks in edges where this can be achieved by removing an edge segment. For example: - * - * ╭───────────╮ ╭───────────╮ - * │Aberystwyth│ │Aberystwyth│ - * ╰─────┬─────╯ ╰─┬─────────╯ - * │ ==> │ - * ╭───╯ │ - * │ │ - * v v - * ╭───╮ ╭───╮ - * │ X │ │ X │ - * ╰───╯ ╰───╯ - */ -object KinkRemover { - - def removeKinks(drawing: Drawing): Drawing = { - val edgeTracker = new EdgeTracker(drawing) - var currentDrawing = drawing - var continue = true - while (continue) - removeKink(currentDrawing, edgeTracker) match { - case None ⇒ - continue = false - case Some((oldEdge, updatedEdge)) ⇒ - currentDrawing = currentDrawing.replaceElement(oldEdge, updatedEdge) - } - return currentDrawing - } - - private def removeKink(drawing: Drawing, edgeTracker: EdgeTracker): Option[(EdgeDrawingElement, EdgeDrawingElement)] = { - for { - edgeElement ← drawing.edgeElements - newEdgeElement ← removeKink(edgeElement, drawing, edgeTracker) - } return Some(edgeElement → newEdgeElement) - None - } - - private def removeKink(edge: EdgeDrawingElement, drawing: Drawing, edgeTracker: EdgeTracker): Option[EdgeDrawingElement] = { - val segments: List[EdgeSegment] = edge.segments - adjacentPairsWithPreviousAndNext(segments) collect { - - // segment1 - // ...──start─╮.............. alternativeMiddle - // │ . - // segment2 │ . - // │ . - // middle ╰─────────────╮ end - // segment3 │ - // │ segment4 - // │ - // . - // . - case (segment1Opt, segment2 @ EdgeSegment(start, Down, middle), segment3 @ EdgeSegment(_, Left | Right, end), segment4Opt) ⇒ - val alternativeMiddle = Point(start.row, end.column) - - segment1Opt.foreach(edgeTracker.removeHorizontalSegment) - edgeTracker.removeVerticalSegment(segment2) - edgeTracker.removeHorizontalSegment(segment3) - segment4Opt.foreach(edgeTracker.removeVerticalSegment) - - val newSegment1Opt = segment1Opt.map { segment1 ⇒ - EdgeSegment(segment1.start, segment1.direction, alternativeMiddle) - } - val newSegment4Opt = segment4Opt.map { segment4 ⇒ - EdgeSegment(alternativeMiddle, segment4.direction, segment4.finish) - } - val collision = newSegment1Opt.exists(edgeTracker.collidesHorizontal) || newSegment4Opt.exists(edgeTracker.collidesVertical) - if (!collision && checkVertexConnection(drawing, start, alternativeMiddle, Direction.Up)) { - segment1Opt.foreach(edgeTracker.addHorizontalSegment) - segment4Opt.foreach(edgeTracker.addVerticalSegment) - return Some(removeKink(edge, start, alternativeMiddle)) - } else { - segment1Opt.foreach(edgeTracker.addHorizontalSegment) - edgeTracker.addVerticalSegment(segment2) - edgeTracker.addHorizontalSegment(segment3) - segment4Opt.foreach(edgeTracker.addVerticalSegment) - } - - // . - // . - // │ - // segment1 │ - // │ segment2 - // start ╰────────────╮ middle - // . │ - // . │ segment3 - // . │ - // alternativeMiddle .............╰─end───────────... - // segment4 - // - case (segment1Opt, segment2 @ EdgeSegment(start, Left | Right, middle), segment3 @ EdgeSegment(_, Down, end), segment4Opt) ⇒ - val alternativeMiddle = Point(end.row, start.column) - segment1Opt.foreach(edgeTracker.removeVerticalSegment) - edgeTracker.removeHorizontalSegment(segment2) - edgeTracker.removeVerticalSegment(segment3) - segment4Opt.foreach(edgeTracker.removeHorizontalSegment) - - val newSegment1Opt = segment1Opt.map { segment1 ⇒ - EdgeSegment(segment1.start, segment1.direction, alternativeMiddle) - } - val newSegment4Opt = segment4Opt.map { segment4 ⇒ - EdgeSegment(alternativeMiddle, segment4.direction, segment4.finish) - } - val collision = newSegment1Opt.exists(edgeTracker.collidesVertical) || newSegment4Opt.exists(edgeTracker.collidesHorizontal) - if (!collision && checkVertexConnection(drawing, end, alternativeMiddle, Direction.Down)) { - segment1Opt.foreach(edgeTracker.addVerticalSegment) - segment4Opt.foreach(edgeTracker.addHorizontalSegment) - return Some(removeKink(edge, start, alternativeMiddle)) - } else { - segment1Opt.foreach(edgeTracker.addVerticalSegment) - edgeTracker.addHorizontalSegment(segment2) - edgeTracker.addVerticalSegment(segment3) - segment4Opt.foreach(edgeTracker.addHorizontalSegment) - } - } - None - } - - /** - * Check that the vertex connection won't be changed, and that we don't want to connect to the extreme - * left or right of the vertex. - */ - private def checkVertexConnection(drawing: Drawing, end: Point, alternativeMiddle: Point, direction: Direction): Boolean = { - drawing.vertexElementAt(end.go(direction)).forall { vertex ⇒ - val connectedToSameVertex = drawing.vertexElementAt(alternativeMiddle.go(direction)) == Some(vertex) - val extremeLeftOfVertex = alternativeMiddle.column == vertex.region.leftColumn - val extremeRightOfVertex = alternativeMiddle.column == vertex.region.rightColumn - connectedToSameVertex && !extremeLeftOfVertex && !extremeRightOfVertex - } - } - - private def removeKink(edge: EdgeDrawingElement, start: Point, alternativeMiddle: Point): EdgeDrawingElement = { - val oldBendPoints = edge.bendPoints - val oldIndex = oldBendPoints.indexOf(start) - val newBendPoints = Point.removeRedundantPoints(oldBendPoints.patch(oldIndex, List(alternativeMiddle), 3).distinct) - return edge.copy(bendPoints = newBendPoints) - } - -} +package com.github.mdr.ascii.layout.drawing + +import com.github.mdr.ascii.common.Direction +import com.github.mdr.ascii.common.Point +import com.github.mdr.ascii.util.Utils._ +import scala.annotation.tailrec +import com.github.mdr.ascii.common.Direction._ + +/** + * Remove kinks in edges where this can be achieved by removing an edge segment. For example: + * + * ╭───────────╮ ╭───────────╮ + * │Aberystwyth│ │Aberystwyth│ + * ╰─────┬─────╯ ╰─┬─────────╯ + * │ ==> │ + * ╭───╯ │ + * │ │ + * v v + * ╭───╮ ╭───╮ + * │ X │ │ X │ + * ╰───╯ ╰───╯ + */ +object KinkRemover { + + def removeKinks(drawing: Drawing): Drawing = { + val edgeTracker = new EdgeTracker(drawing) + var currentDrawing = drawing + var continue = true + while (continue) removeKink(currentDrawing, edgeTracker) match { + case None ⇒ + continue = false + case Some((oldEdge, updatedEdge)) ⇒ + currentDrawing = currentDrawing.replaceElement(oldEdge, updatedEdge) + } + return currentDrawing + } + + private def removeKink(drawing: Drawing, edgeTracker: EdgeTracker): Option[(EdgeDrawingElement, EdgeDrawingElement)] = { + for { + edgeElement ← drawing.edgeElements + newEdgeElement ← removeKink(edgeElement, drawing, edgeTracker) + } return Some(edgeElement → newEdgeElement) + None + } + + private def removeKink( + edge: EdgeDrawingElement, + drawing: Drawing, + edgeTracker: EdgeTracker + ): Option[EdgeDrawingElement] = { + val segments: List[EdgeSegment] = edge.segments + val test = adjacentPairsWithPreviousAndNext(segments) + test.collect { + + // segment1 + // ...──start─╮.............. alternativeMiddle + // │ . + // segment2 │ . + // │ . + // middle ╰─────────────╮ end + // segment3 │ + // │ segment4 + // │ + // . + // . + case (segment1Opt, + segment2 @ EdgeSegment(start, Down, middle), + segment3 @ EdgeSegment(_, Left | Right, end), + segment4Opt) ⇒ funcMethodExtraction1(segment1Opt, segment2, segment3, segment4Opt, start, middle, end, edge, drawing, edgeTracker) + + // . + // . + // │ + // segment1 │ + // │ segment2 + // start ╰────────────╮ middle + // . │ + // . │ segment3 + // . │ + // alternativeMiddle .............╰─end───────────... + // segment4 + // + case (segment1Opt, + segment2 @ EdgeSegment(start, Left | Right, middle), + segment3 @ EdgeSegment(_, Down, end), + segment4Opt) ⇒ + val alternativeMiddle = Point(end.row, start.column) + segment1Opt.foreach(edgeTracker.removeVerticalSegment) + edgeTracker.removeHorizontalSegment(segment2) + edgeTracker.removeVerticalSegment(segment3) + segment4Opt.foreach(edgeTracker.removeHorizontalSegment) + + val newSegment1Opt = segment1Opt.map { segment1 ⇒ + EdgeSegment(segment1.start, segment1.direction, alternativeMiddle) + } + val newSegment4Opt = segment4Opt.map { segment4 ⇒ + EdgeSegment(alternativeMiddle, segment4.direction, segment4.finish) + } + val collision = newSegment1Opt.exists(edgeTracker.collidesVertical) || newSegment4Opt + .exists(edgeTracker.collidesHorizontal) + if (!collision && checkVertexConnection( + drawing, + end, + alternativeMiddle, + Direction.Down + )) { + segment1Opt.foreach(edgeTracker.addVerticalSegment) + segment4Opt.foreach(edgeTracker.addHorizontalSegment) + return Some(removeKink(edge, start, alternativeMiddle)) + } else { + segment1Opt.foreach(edgeTracker.addVerticalSegment) + edgeTracker.addHorizontalSegment(segment2) + edgeTracker.addVerticalSegment(segment3) + segment4Opt.foreach(edgeTracker.addHorizontalSegment) + } + } + None + } + + private def funcMethodExtraction1( + segment1Opt: Option[EdgeSegment], + segment2: EdgeSegment, + segment3: EdgeSegment, + segment4Opt: Option[EdgeSegment], + start: Point, + middle: Point, + end: Point, + edge: EdgeDrawingElement, + drawing: Drawing, + edgeTracker: EdgeTracker + ): Option[EdgeDrawingElement] = { + val alternativeMiddle = Point(start.row, end.column) + + segment1Opt.foreach(edgeTracker.removeHorizontalSegment) + edgeTracker.removeVerticalSegment(segment2) + edgeTracker.removeHorizontalSegment(segment3) + segment4Opt.foreach(edgeTracker.removeVerticalSegment) + + val newSegment1Opt = segment1Opt.map { segment1 ⇒ + EdgeSegment(segment1.start, segment1.direction, alternativeMiddle) + } + val newSegment4Opt = segment4Opt.map { segment4 ⇒ + EdgeSegment(alternativeMiddle, segment4.direction, segment4.finish) + } + val collision = newSegment1Opt.exists(edgeTracker.collidesHorizontal) || newSegment4Opt + .exists(edgeTracker.collidesVertical) + if (!collision && checkVertexConnection( + drawing, + start, + alternativeMiddle, + Direction.Up + )) { + segment1Opt.foreach(edgeTracker.addHorizontalSegment) + segment4Opt.foreach(edgeTracker.addVerticalSegment) + return Some(removeKink(edge, start, alternativeMiddle)) + } else { + segment1Opt.foreach(edgeTracker.addHorizontalSegment) + edgeTracker.addVerticalSegment(segment2) + edgeTracker.addHorizontalSegment(segment3) + segment4Opt.foreach(edgeTracker.addVerticalSegment) + return None + } + } + + /** + * Check that the vertex connection won't be changed, and that we don't want to connect to the extreme + * left or right of the vertex. + */ + private def checkVertexConnection( + drawing: Drawing, + end: Point, + alternativeMiddle: Point, + direction: Direction + ): Boolean = { + drawing.vertexElementAt(end.go(direction)).forall { vertex ⇒ + val connectedToSameVertex = drawing.vertexElementAt( + alternativeMiddle.go(direction) + ) == Some(vertex) + val extremeLeftOfVertex = alternativeMiddle.column == vertex.region.leftColumn + val extremeRightOfVertex = alternativeMiddle.column == vertex.region.rightColumn + connectedToSameVertex && !extremeLeftOfVertex && !extremeRightOfVertex + } + } + + private def removeKink( + edge: EdgeDrawingElement, + start: Point, + alternativeMiddle: Point + ): EdgeDrawingElement = { + val oldBendPoints = edge.bendPoints + val oldIndex = oldBendPoints.indexOf(start) + val newBendPoints = Point.removeRedundantPoints( + oldBendPoints.patch(oldIndex, List(alternativeMiddle), 3).distinct + ) + return edge.copy(bendPoints = newBendPoints) + } + +} diff --git a/src/main/scala/com/github/mdr/ascii/layout/drawing/Renderer.scala b/src/main/scala/com/github/mdr/ascii/layout/drawing/Renderer.scala index a0882b9..bd8f389 100644 --- a/src/main/scala/com/github/mdr/ascii/layout/drawing/Renderer.scala +++ b/src/main/scala/com/github/mdr/ascii/layout/drawing/Renderer.scala @@ -9,7 +9,8 @@ import com.github.mdr.ascii.layout.prefs.RendererPrefs object Renderer { - def render(drawing: Drawing, rendererPrefs: RendererPrefs) = new Renderer(rendererPrefs).render(drawing) + def render(drawing: Drawing, rendererPrefs: RendererPrefs) = + new Renderer(rendererPrefs).render(drawing) } @@ -28,25 +29,43 @@ class Renderer(rendererPrefs: RendererPrefs) { } @tailrec - private def drawLine(grid: Grid, point1: Point, direction: Direction, point2: Point) { + private def drawLine( + grid: Grid, + point1: Point, + direction: Direction, + point2: Point + ) { val lineChar = direction match { case Up | Down ⇒ lineHorizontalChar case Right | Left ⇒ lineVerticalChar } - grid(point1) = if (grid(point1) == backgroundChar) lineChar else intersectionCharOpt.getOrElse(lineChar) + grid(point1) = + if (grid(point1) == backgroundChar) lineChar + else intersectionCharOpt.getOrElse(lineChar) if (point1 != point2) drawLine(grid, point1.go(direction), direction, point2) } - private def render(grid: Grid, element: EdgeDrawingElement, drawing: Drawing) { - for ((previousSegmentOpt, segment @ EdgeSegment(point1, direction, point2)) ← Utils.withPrevious(element.segments)) { - val startPoint = point1 - val endPoint = if (point2 == element.bendPoints.last) point2 else point2.go(direction.opposite) - - try - drawLine(grid, startPoint, direction, endPoint) + private def render( + grid: Grid, + element: EdgeDrawingElement, + drawing: Drawing + ) { + for ( + (previousSegmentOpt, segment @ EdgeSegment(point1, direction, point2)) ← Utils + .withPrevious(element.segments) + ) { + val endPoint = + if (point2 == element.bendPoints.last) point2 + else point2.go(direction.opposite) + + try drawLine(grid, point1, direction, endPoint) catch { - case e: Throwable ⇒ throw new RuntimeException("Problem drawing segment " + segment + " in edge " + element, e) + case e: Throwable ⇒ + throw new RuntimeException( + "Problem drawing segment " + segment + " in edge " + element, + e + ) } condOpt(previousSegmentOpt.map(_.direction), direction) { @@ -58,7 +77,9 @@ class Renderer(rendererPrefs: RendererPrefs) { } def drawBoxIntersection(intersectionPoint: Point, direction: Direction) = - if (unicode && drawing.vertexElementAt(intersectionPoint).isDefined && grid.contains(intersectionPoint)) + if (unicode && drawing + .vertexElementAt(intersectionPoint) + .isDefined && grid.contains(intersectionPoint)) grid(intersectionPoint) = direction match { case Up ⇒ joinChar1 case Down ⇒ joinChar2 @@ -117,20 +138,43 @@ class Renderer(rendererPrefs: RendererPrefs) { private def lineHorizontalChar = if (unicode) '│' else '|' private def lineVerticalChar = if (unicode) '─' else '-' - private def bendChar1 = if (unicode) (if (rounded) '╭' else '┌') else if (explicitAsciiBends) '/' else '-' - private def bendChar2 = if (unicode) (if (rounded) '╮' else '┐') else if (explicitAsciiBends) '\\' else '-' - private def bendChar3 = if (unicode) (if (rounded) '╰' else '└') else if (explicitAsciiBends) '\\' else '-' - private def bendChar4 = if (unicode) (if (rounded) '╯' else '┘') else if (explicitAsciiBends) '/' else '-' - - private def intersectionCharOpt = if (unicode) Some('┼') else Some('-' /* '+' */ ) // '+' is problematic because it can generate accidental boxes - - private def topLeftChar = if (unicode) (if (doubleVertices) '╔' else if (rounded) '╭' else '┌') else '+' - private def topRightChar = if (unicode) (if (doubleVertices) '╗' else if (rounded) '╮' else '┐') else '+' - private def bottomLeftChar = if (unicode) (if (doubleVertices) '╚' else if (rounded) '╰' else '└') else '+' - private def bottomRightChar = if (unicode) (if (doubleVertices) '╝' else if (rounded) '╯' else '┘') else '+' - - private def boxHorizontalChar = if (unicode) (if (doubleVertices) '═' else '─') else '-' - private def boxVerticalChar = if (unicode) (if (doubleVertices) '║' else '│') else '|' + private def bendChar1 = + if (unicode) (if (rounded) '╭' else '┌') + else if (explicitAsciiBends) '/' + else '-' + private def bendChar2 = + if (unicode) (if (rounded) '╮' else '┐') + else if (explicitAsciiBends) '\\' + else '-' + private def bendChar3 = + if (unicode) (if (rounded) '╰' else '└') + else if (explicitAsciiBends) '\\' + else '-' + private def bendChar4 = + if (unicode) (if (rounded) '╯' else '┘') + else if (explicitAsciiBends) '/' + else '-' + + private def intersectionCharOpt = + if (unicode) Some('┼') else Some('-' /* '+' */ ) // '+' is problematic because it can generate accidental boxes + + private def topLeftChar = + if (unicode) (if (doubleVertices) '╔' else if (rounded) '╭' else '┌') + else '+' + private def topRightChar = + if (unicode) (if (doubleVertices) '╗' else if (rounded) '╮' else '┐') + else '+' + private def bottomLeftChar = + if (unicode) (if (doubleVertices) '╚' else if (rounded) '╰' else '└') + else '+' + private def bottomRightChar = + if (unicode) (if (doubleVertices) '╝' else if (rounded) '╯' else '┘') + else '+' + + private def boxHorizontalChar = + if (unicode) (if (doubleVertices) '═' else '─') else '-' + private def boxVerticalChar = + if (unicode) (if (doubleVertices) '║' else '│') else '|' private def joinChar1 = if (doubleVertices) '╤' else '┬' private def joinChar2 = if (doubleVertices) '╧' else '┴' @@ -147,4 +191,3 @@ class Renderer(rendererPrefs: RendererPrefs) { } } - diff --git a/src/main/scala/com/github/mdr/ascii/util/Utils.scala b/src/main/scala/com/github/mdr/ascii/util/Utils.scala index afef559..a9bb656 100644 --- a/src/main/scala/com/github/mdr/ascii/util/Utils.scala +++ b/src/main/scala/com/github/mdr/ascii/util/Utils.scala @@ -5,12 +5,15 @@ import scala.io.Source object Utils { - def transformValues[K, V, V2](map: Map[K, V])(f: V ⇒ V2): Map[K, V2] = map.map { case (k, v) ⇒ (k, f(v)) } + def transformValues[K, V, V2](map: Map[K, V])(f: V ⇒ V2): Map[K, V2] = + map.map { case (k, v) ⇒ (k, f(v)) } def withPrevious[T](iterable: Iterable[T]): List[(Option[T], T)] = withPreviousAndNext(iterable).map { case (a, b, c) ⇒ (a, b) } - def withPreviousAndNext[T](iterable: Iterable[T]): List[(Option[T], T, Option[T])] = { + def withPreviousAndNext[T]( + iterable: Iterable[T] + ): List[(Option[T], T, Option[T])] = { if (iterable.isEmpty) Nil else { @@ -21,11 +24,19 @@ object Utils { } def adjacentPairs[T](xs: List[T]): List[(T, T)] = xs zip xs.drop(1) - def adjacentTriples[T](xs: List[T]): List[(T, T, T)] = xs zip xs.drop(1) zip xs.drop(2) map { case ((x, y), z) ⇒ (x, y, z) } - def adjacentPairsWithPreviousAndNext[T](xs: List[T]): List[(Option[T], T, T, Option[T])] = - (None :: xs.init.map(Some(_))) zip xs zip xs.drop(1) zip (xs.drop(2).map(Some(_)) :+ None) map { - case (((x, y), z), u) ⇒ (x, y, z, u) - } + def adjacentTriples[T](xs: List[T]): List[(T, T, T)] = + xs zip xs.drop(1) zip xs.drop(2) map { case ((x, y), z) ⇒ (x, y, z) } + + def adjacentPairsWithPreviousAndNext[T]( + xs: List[T] + ): List[(Option[T], T, T, Option[T])] = + (None :: xs.init.map(Some(_))) + .zip(xs) + .zip(xs.drop(1)) + .zip(xs.drop(2).map(Some(_)) :+ None) + .map({ + case (((x, y), z), u) ⇒ (x, y, z, u) + }) @tailrec def iterate[T](t: T, f: T ⇒ Option[T]): T = f(t) match { @@ -36,7 +47,8 @@ object Utils { def multisetCompare[T](set1: List[T], set2: List[T]): Boolean = mkMultiset(set1) == mkMultiset(set2) - def mkMultiset[T](set1: List[T]): Map[T, Int] = set1.groupBy(identity).mapValues(_.size).toMap + def mkMultiset[T](set1: List[T]): Map[T, Int] = + set1.groupBy(identity).mapValues(_.size).toMap def removeFirst[T](xs: List[T], x: T): List[T] = xs match { case Nil ⇒ Nil @@ -61,9 +73,11 @@ object Utils { * Map over a list, optionally transforming some of the elements */ def conditionallyMap[T](xs: List[T])(fn: PartialFunction[T, T]): List[T] = - xs.map { t ⇒ if (fn isDefinedAt t) fn(t) else t } + xs.map { t ⇒ + if (fn isDefinedAt t) fn(t) else t + } def addToMultimap[K, V](m: Map[K, List[V]], k: K, v: V): Map[K, List[V]] = m + (k → (v :: m.getOrElse(k, Nil))) -} \ No newline at end of file +}