Google OR-Toolsで「もっと金送れ」

古典的な覆面算「もっと金送れ（SEND MORE MONEY）」を解きたいです。

```
 SEND
+MORE
-----
MONEY
```

これは、同じアルファベットには同じ数字を、異なるアルファベットには異なる数字を当てはめて、この筆算を成立させましょうというパズルです。たとえば「S=1 E=2 N=3 D=4 M=5 O=6 R=7 Y=8」とすると、

```
 1234
+5672
-----
56328
```

となって、これは成立しません。お金は送ってもらえませんでした。

# 解かせ方

さて、Scala から Google OR-Tools を呼びだして、このパズルを解かせてみましょう。まずアルファベットに対応する変数を用意します。

```scala
val dict = "SENDMOREMONEY".toCharArray.toSet[Char]
             .map(c => (c, solver.makeIntVar(0, 9, c.toString))).toMap
```

アルファベットは全て異なる数字であるという制約をかけます。

```scala
solver.addConstraint(solver.makeAllDifferent(dict.values.toArray))
```

そして「SEND + MORE = MONEY」の制約をかけます。

```scala
  val send = dict('S') * 1000 + dict('E') * 100 + dict('N') * 10 + dict('D')
  val more = dict('M') * 1000 + dict('O') * 100 + dict('R') * 10 + dict('E')
  val money = dict('M') * 10000 + dict('O') * 1000 + dict('N') * 100 + dict('E') * 10 + dict('Y')
  solver.addConstraint(send + more === money)
```

さてさて Google OR-Tools が出す答は？？？

```
6851 + 0738 = 07589
6853 + 0728 = 07581
5849 + 0638 = 06487
3821 + 0468 = 04289
3829 + 0458 = 04287
2819 + 0368 = 03187
2817 + 0368 = 03185
5732 + 0647 = 06379
5731 + 0647 = 06378
3712 + 0467 = 04179
3719 + 0457 = 04176
7643 + 0826 = 08469
7649 + 0816 = 08465
9567 + 1085 = 10652
8542 + 0915 = 09457
7534 + 0825 = 08359
7531 + 0825 = 08356
7539 + 0815 = 08354
6524 + 0735 = 07259
8432 + 0914 = 09346
7429 + 0814 = 08243
6415 + 0734 = 07149
6419 + 0724 = 07143
8324 + 0913 = 09237
7316 + 0823 = 08139
```

おやおや、いっぱい答が出ててしまいました。~~いっぱいお金も送ってもらえますね！~~

これは一番上の桁には 0 を入れてはならないというルールを忘れていたからでした。気を取りなおして、

```scala
solver.addConstraint(dict('S') !== 0)
solver.addConstraint(dict('M') !== 0)
```

さてさて？

```
9567 + 1085 = 10652
```

はい、上手に解けました。

# コード一式

Google OR-Tools の環境構築手順は『[巨大お絵かきロジックを最適化ソルバーで瞬殺（ネタバレあり）](https://qiita.com/piyo7/items/0990a9c75a24407a0a04#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E6%89%8B%E9%A0%86)』を参照してください。

```src/main/scala/SendMoreMoney.scala
object SendMoreMoney extends App {
  System.loadLibrary("jniortools")

  implicit val solver: Solver = new Solver("")

  val dict = "SENDMOREMONEY".toCharArray.toSet[Char]
               .map(c => (c, solver.makeIntVar(0, 9, c.toString))).toMap

  solver.addConstraint(solver.makeAllDifferent(dict.values.toArray))
  solver.addConstraint(dict('S') !== 0)
  solver.addConstraint(dict('M') !== 0)
  val send = dict('S') * 1000 + dict('E') * 100 + dict('N') * 10 + dict('D')
  val more = dict('M') * 1000 + dict('O') * 100 + dict('R') * 10 + dict('E')
  val money = dict('M') * 10000 + dict('O') * 1000 + dict('N') * 100 + dict('E') * 10 + dict('Y')
  solver.addConstraint(send + more === money)
  solver.newSearch(solver.makePhase(dict.values.toArray,
    Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE))

  while (solver.nextSolution()) {
    println(
      "SEND".toCharArray.map(dict).map(_.value()).mkString("") + " + " +
        "MORE".toCharArray.map(dict).map(_.value()).mkString("") + " = " +
        "MONEY".toCharArray.map(dict).map(_.value()).mkString(""))
  }

  implicit class IntExprHelper(i: IntExpr)(implicit solver: Solver) {
    def +(j: IntExpr): IntExpr = solver.makeSum(i, j)
    def *(j: Long): IntExpr = solver.makeProd(i, j)
    def ===(j: IntExpr): Constraint = solver.makeEquality(i, j)
    def !==(j: Long): Constraint = solver.makeNonEquality(i, j)
  }
}
```

```build.sbt
name := "send-more-money"
version := "0.1.0"
javacOptions ++= Seq("-source", "11", "-target", "11")
scalaVersion := "2.13.0"

fork := true
javaOptions += "-Djava.library.path=lib"

```