Advertisement
Guest User

kva

a guest
Dec 11th, 2017
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scala 9.41 KB | None | 0 0
  1. // Scala Implementierung in Anlehnung an die Haskell Version von
  2. // https://github.com/almost/regex-crossword-solver/blob/master/recross.hs
  3.  
  4. import scala.util.matching.Regex
  5.  
  6. object HexRegexCrossword {
  7.  
  8.   // Aufgabe 2
  9.   // 2.1
  10.   type Coords = (Int, Int)
  11.   type Possibilities = List[Char]
  12.   type Hex = List[(Coords, Possibilities)]
  13.   type GameState = (Int, Hex)
  14.  
  15.   // 2.2
  16.   def validCoords(x: Int, y: Int, size: Int): Boolean = {
  17.     // Eine Koordinate ist gültig, wenn sie im Intervall [-size, size] liegt.
  18.     def ok(i: Int): Boolean = i.abs <= size
  19.  
  20.     // In dem gegebenen achsenbasierten Koordinatensystem gilt die Bedingung, dass immer x + y + z = 0 gelten muss.
  21.     // Dadurch kann aus x und y die Koordinate z berechnet werden.
  22.     val z = -(x + y)
  23.  
  24.     // Alle Koordinaten müssen gültig sein.
  25.     ok(x) && ok(y) && ok(z)
  26.   }
  27.  
  28.   // 2.3
  29.   def allPossibilites(): Possibilities =
  30.     '!' :: '$' :: '?' :: '\\' :: ('A' to 'Z').toList ::: ('0' to '9').toList
  31.  
  32.   // 2.4
  33.   def startGameState(size: Int): GameState = {
  34.     def startHex(): Hex =
  35.     // Alle Koordinaten-Kombinationen durchprobieren und für die gültigen Koordinaten ein Hex-Feld anlegen.
  36.       (for {
  37.         x <- -size to size
  38.         y <- -size to size
  39.         if validCoords(x, y, size)
  40.       } yield ((x, y), allPossibilites())).toList
  41.  
  42.     // Rückgabewert muss ein GameState-Tuple sein.
  43.     (size, startHex())
  44.   }
  45.  
  46.   // Aufgabe 3
  47.   // 3.1
  48.   // Eine Variation der in der Aufgabenstellung geforderten simplePrinter-Funktion.
  49.   def simplePrinter(x: Int, y: Int, z: Int, p: Possibilities): String = p.length match {
  50.     case 1 => p.head.toString + " " // das letzte verbleibende Element ausgeben
  51.     case l if l == allPossibilites().length => "* " // noch keine Einschränkung vorgenommen
  52.     case l if l < allPossibilites().length => "+ " // Möglichkeiten konnten schon eingeschränkt werden
  53.     case _ => "- " // falls keine Möglichkeiten mehr vorhanden
  54.   }
  55.  
  56.   // 3.2
  57.   def printGameState(gs: GameState, printer: ((Int, Int, Int, Possibilities) => String)): String = {
  58.     // Hilfsfunktion, die die z-Koordinate berechnet, die verbleibenden Möglichkeiten abruft und die printer-Funktion aufruft.
  59.     def printCell(x: Int, y: Int): String = {
  60.       val z = -(y + x)
  61.       getPossibilities(x, y, gs) match {
  62.         case None => "" // Wenn das Ergebnis None ist, wurde ein ungültiges Hex-Feld angefragt. Wir ignorieren dies mit einem leeren String.
  63.         case Some(p) => printer(x, y, z, p) // Bei einem gültigen Feld übernimmt die printer-Funktion die Bestimmung des Ergebnis-Strings.
  64.       }
  65.     }
  66.  
  67.     // Hilfsfunktion, die eine Zeile des Hex-Feldes erzeugt:
  68.     // - List.fill(n)(c) erzeugt eine Liste der Länge n mit Elementen c. Da die "mittlerste" Zeile die längste ist und die Koordinaten 0 hat,
  69.     //   können wir für jede Zeile darüber oder darunter den absoluten Wert der Zeilen-Koordinate nehmen um die Zeilen entsprechen einzurücken.
  70.     // - Nach den Einrückungsleerzeichen verwenden wir flatMap um für alle möglichen x-Werte der Zeile, den Inhalt über die Hilfsfunktion ausgeben zu lassen.
  71.     //   flatMap sorgt hier dafür, dass das Ergebnis schon ein einzelner String ist, den wir über toList zu einer List[Char] wandeln und mit der
  72.     //   Einrückungsliste verbinden können.
  73.     // - Zum Schluss wird die List[Char] durch mkString(s) zu einem String zusammengesetzt. Dabei wird zwischen jedem Listenelement der Seperator s
  74.     //   (hier ein Leerzeichen) eingefügt.
  75.     def printRow(y: Int): String = (List.fill(y.abs)(' ') ::: (-gs._1 to gs._1).flatMap(printCell(_, y)).toList).mkString(" ")
  76.  
  77.     // Für jede Zeile wird die Zeilen-Hilfsfunktion aufgerufen und die Ergebnis-Liste durch Zeilenumbrüche zu einem einzigen String zusammengebaut
  78.     (-gs._1 to gs._1).map(printRow).mkString("\n")
  79.   }
  80.  
  81.   // Rekursive Funktion, die alle Hex-Felder nach den gegebenen Koordinaten durchsucht, und bei Erfolg die mit dem Hex-Feld verbundenen Möglichkeiten zurückgibt
  82.   // Wenn das gesuchte Feld nicht in dem GameState vorhanden ist, wird None zurückgegeben
  83.   def getPossibilities(x: Int, y: Int, gs: GameState): Option[Possibilities] = gs._2 match {
  84.     case ((a, b), p) :: gss if a == x && b == y => Some(p)
  85.     case (_, _) :: gss => getPossibilities(x, y, (gs._1, gss))
  86.     case _ => None
  87.   }
  88.  
  89.   // Aufgabe 4
  90.   // 4.1
  91.   def generator[A](el: List[Possibilities]): List[String] = {
  92.     // Hilfsfunktion zum zusammensetzen aller möglichen Strings
  93.     // In jedem Schritt der Rekursion wird jedes Element aus der ersten Liste (dem letztendlichen Ergebnis)
  94.     // mit jedem Zeichen des Kopfes der verbleibenden Möglichkeiten kombiniert.
  95.     def h(ergebnis: List[String], rest: List[Possibilities]): List[String] = rest match {
  96.       case x :: xs => h(for {str <- ergebnis; chr <- x} yield str + chr.toString, xs)
  97.       case _ => ergebnis
  98.     }
  99.  
  100.     // Initial wir die Funktion mit den Möglichkeiten für die erste Position (umgewandelt in Strings) und den folgenden Positionen aufgerufen
  101.     h(el.head.map(_.toString), el.tail)
  102.   }
  103.  
  104.   // 4.2
  105.   // Hier muss nur das Listenfunktional filter mit der gegebenen Funktion matches angewendet werden
  106.   def reducePossibilities(r: Regex, l: List[String]): List[String] = l.filter(matches(r, _))
  107.  
  108.   // Testet ob ein regulärer Ausdruck einen String vollständig erfasst
  109.   def matches(r: Regex, s: String): Boolean = r.findAllMatchIn(s).exists(_.toString == s)
  110.  
  111.   // 4.3
  112.   // Aus der Darstellung für Achsen- bzw. Würfelkoordinaten https://www.redblobgames.com/grids/hexagons/#coordinates-cube
  113.   // kann die Reihenfolge des Durchlaufens der Koordinaten ermittelt werden.
  114.   // Zum Zusammensetzten kann jeweils eine geschachtelte Listenkomprehension verwendet werden.
  115.   def regexCoordinates(gs: GameState): List[List[Coords]] = {
  116.     val xAxis = for {x <- -gs._1 to gs._1} yield (for {y <- (-gs._1 to gs._1).reverse if validCoords(x, y, gs._1)} yield (x, y)).toList
  117.     val yAxis = for {z <- -gs._1 to gs._1} yield (for {x <- (-gs._1 to gs._1).reverse; y = -(x + z) if validCoords(x, y, gs._1)} yield (x, y)).toList
  118.     val zAxis = for {y <- -gs._1 to gs._1} yield (for {x <- -gs._1 to gs._1; z = -(y + x) if validCoords(x, y, gs._1)} yield (x, y)).toList
  119.     xAxis.toList ::: yAxis.toList ::: zAxis.toList
  120.   }
  121.  
  122.   // 4.4
  123.   def runIteration(rl: List[Regex], gs: GameState): GameState = {
  124.     def update(x: (List[Coords], Regex), h: Hex): Hex = {
  125.       // Erst werden alle möglichen Kombinationen der Zellen zu dem regulären Ausdruck erstellt
  126.       val set: List[String] = generator(x._1.map((c: Coords) => getPossibilities(c._1, c._2, (gs._1, h)).getOrElse(List())))
  127.       // Das Set aller Möglichkeiten wird dann durch matchen mit dem regulären Ausdruck reduziert
  128.       val rSet: List[String] = reducePossibilities(x._2, set)
  129.  
  130.       // Rückwandeln der Strings in List[Char] für die Möglichkeiten der einzelnen Zellen.
  131.       // Zunächst wird eine leere Liste, die für jede Position eine leere Liste an möglichen Chars enthält, angelegt.
  132.       val t: List[List[Char]] = List.fill(x._1.length)(List())
  133.       // Ausgehend von dieser Liste ohne Einträge für die jeweiligen Möglichkeiten, wird mit fold durch die reduzierte Ergebnisliste gegangen.
  134.       val u = rSet.foldRight(t)((m: String, l: List[List[Char]]) =>
  135.         // Dabei wird der aktuelle String, der für jedes beteiligte Hex-Feld ein mögliches Zeichen enthält, durch zip mit der Liste der Möglichkeiten nach Hex-Feldern verknüpft.
  136.         // Das Resultat des zip-Aufrufs ist eine Liste von Tupeln (Char, Possibilites)
  137.         m.zip(l).map(ct =>
  138.           // Damit es keine Duplikate für die einzelnen Positionen gibt, wird beim Zusammenfassen der Tupeleinträge überprüft, ob der Char schon in der Liste der Möglichkeiten existiert.
  139.           if (ct._2.contains(ct._1)) ct._2 else ct._1 :: ct._2).toList)
  140.  
  141.       // Zum Schluss muss das alte Hex ohne die Hex-Felder, die aktualisiert wurden, kopiert werden.
  142.       // Die aktualisierten Felder werden dann angehängt um ein vollständiges aktualisiertes Hex zu bekommen.
  143.       h.filter(_ match { case (c, _) => !x._1.contains(c); case _ => false }) ::: x._1.zip(u)
  144.     }
  145.  
  146.     // Die zunächst werden die Koordinatenlisten mit den regulären Ausdrücken zu einer Liste von Tupeln (Koordinatenliste, regulärer Ausdruck) zusammen gezipped.
  147.     // Das aktuelle Hex wird dann kontinuierlich durch den fold mit der update-Funktion und allen regulären Ausdrücken aktualisiert.
  148.     // Damit das Ergebnis wieder ein GameState ist, muss das Ergebnis zu einem entsprechenden Tupel zusammengesetzt werden.
  149.     (gs._1, regexCoordinates(gs).zip(rl).foldRight(gs._2)(update))
  150.   }
  151.  
  152.   def main(args: Array[String]): Unit = {
  153.     val g1RegX: List[Regex] = List(
  154.       """[^XRAY]?[CROWN]*$""".r,
  155.       """^[D-F\?][^CAMPED]+""".r,
  156.       """[KIP?\!]*(BU|L|B){1,2}""".r
  157.     )
  158.     val g1RegY: List[Regex] = List(
  159.       """[78NGUA\!E]{2}""".r,
  160.       """(F|^L|Y)?[POWER!]*""".r,
  161.       """[^ZENI$](TA|NG|LE|R)""".r
  162.     )
  163.     val g1RegZ: List[Regex] = List(
  164.       """[FIND]?\$?[LAKME]""".r,
  165.       """[^PESCE]?[POS3]*.""".r,
  166.       """[^OPRES!]+[^STICKO]*""".r
  167.     )
  168.     val g1r = g1RegX ::: g1RegY ::: g1RegZ
  169.  
  170.     val g1s = startGameState(1)
  171.     println(printGameState(g1s, simplePrinter))
  172.     println(printGameState(runIteration(g1r, g1s), simplePrinter))
  173.   }
  174.  
  175. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement