Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions nmcb/src/main/scala/examples/FriedmanNumber.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package examples

import nmcb.predef.*

import java.lang.System.*
import scala.Option.when
import scala.collection.*
import scala.util.*


object FriedmanNumber:

trait Operation derives CanEqual:
def run(l: Int, r: Int): Option[Int]

case object Addition extends Operation:
def run(l: Int, r: Int): Option[Int] =
Some(l + r)

case object Subtraction extends Operation:
def run(l: Int, r: Int): Option[Int] =
Some(l - r)

case object Multiplication extends Operation:
def run(l: Int, r: Int): Option[Int] =
Some(l * r)

case object Division extends Operation:
def run(l: Int, r: Int): Option[Int] =
when(r != 0 && l % r == 0)(l / r)

case object Exponentiation extends Operation:
def run(l: Int, r: Int): Option[Int] =
Some(math.pow(l, r).toInt)

case object Concatenation extends Operation:
def run(l: Int, r: Int): Option[Int] =
sys.error(s"unimplemented, special handling in computation")

object Operation:
val all: Vector[Operation] =
Vector(
Addition,
Subtraction,
Multiplication,
Division,
Exponentiation,
Concatenation
)


sealed trait ComputationOrder derives CanEqual:

def isConcat: Boolean =
this match
case BinaryOperation(l, r, Some(Concatenation)) => l.isConcat && r.isConcat
case Value(v) => true
case _ => false

def compute: Option[Int] =
this.runtimeChecked match
case Value(Some(v)) => Some(v)
case BinaryOperation(l, r, Some(Concatenation)) => when(l.isConcat && r.isConcat)(s"${l.compute.get}${r.compute.get}".toInt)
case BinaryOperation(l, r, Some(o)) => l.compute.flatMap(lr => r.compute.flatMap(rr => o.run(lr, rr)))

def bind(operations: Vector[Operation], arguments: Vector[Int]): ComputationOrder =
val (result, _, _) = bindRecursively(operations, arguments)
result

private def bindRecursively(operations: Vector[Operation], arguments: Vector[Int]): (ComputationOrder, Vector[Operation], Vector[Int]) =
this match
case Value(v) =>
(Value(Some(arguments.head)), operations, arguments.tail)
case BinaryOperation(l, r, v) =>
val (fl, ol, al) = l.bindRecursively(operations.tail, arguments)
val (fr, or, ar) = r.bindRecursively(ol, al)
(BinaryOperation(fl, fr, Some(operations.head)), or, ar)

case class Value(v: Option[Int] = None) extends ComputationOrder
case class BinaryOperation(l: ComputationOrder, r: ComputationOrder, o: Option[Operation] = None) extends ComputationOrder


private val possibleOperationsMemo = Memo.empty[Int, Vector[Vector[Operation]]]
def possibleOperations(size: Int): Vector[Vector[Operation]] =
possibleOperationsMemo.memoize(size):
Vector
.fill(size)(Operation.all)
.flatten
.combinations(size).flatMap(_.permutations)
.filter(_.count(_ == Concatenation) != size)
.distinct
.toVector

private val possibleDigitSequencesMemo = Memo.empty[Vector[Int], Vector[Vector[Int]]]
def possibleDigitSequences(digits: Vector[Int]): Vector[Vector[Int]] =
possibleDigitSequencesMemo.memoize(digits):
digits
.combinations(digits.length)
.flatMap(_.permutations)
.distinct
.toVector

private val possibleComputationOrdersMemo = Memo.empty[Int, Vector[ComputationOrder]]
def possibleComputationOrders(size: Int): Vector[ComputationOrder] =
possibleComputationOrdersMemo.memoize(size):
size match
case 0 => Vector.empty
case 1 => Vector(Value())
case n =>
(0 until size).toVector.flatMap: leftSize =>
for
t1 <- possibleComputationOrders(leftSize)
t2 <- possibleComputationOrders(size - leftSize - 1)
yield
BinaryOperation(t1, t2)


def isFriedmanNumber(number: Int): Boolean =
val digits: Vector[Int] = number.toString.toVector.map(_.asDigit)
possibleOperations(digits.length - 1).exists: operations =>
possibleDigitSequences(digits).exists: sequence =>
possibleComputationOrders(operations.size + sequence.size).exists: order =>
order.bind(operations, sequence).compute.contains(number)

def sieve(max: Int): Seq[Int] =
(0 to max).filter(isFriedmanNumber)


def main(args: Array[String]): Unit =
val max: Int = Try(args(0).toInt).getOrElse(10000)
val start: Long = currentTimeMillis
val result: Seq[Int] = sieve(max)

println(s"friedman numbers up to $max:")
println(result.mkString(", "))
println(s"took ${currentTimeMillis - start} ms")
14 changes: 14 additions & 0 deletions nmcb/src/test/scala/examples/TestFriedmanNumber.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package examples

import org.scalatest.funsuite.AnyFunSuite

class TestFriedmanNumber extends AnyFunSuite:

/** [OEIS-A036057](https://oeis.org/A036057) */
test("FriedmanNumber") {
assertResult(
Vector(
25, 121, 125, 126, 127, 128, 153, 216, 289, 343, 347, 625, 688, 736, 1022, 1024, 1206, 1255, 1260, 1285, 1296, 1395, 1435, 1503, 1530, 1792, 1827, 2048, 2187, 2349, 2500, 2501, 2502, 2503, 2504, 2505, 2506, 2507, 2508, 2509, 2592, 2737, 2916, 3125, 3159, 3281, 3375, 3378, 3685, 3784, 3864, 3972, 4088, 4096, 4106, 4167, 4536, 4624, 4628, 5120, 5776, 5832, 6144, 6145, 6455, 6880, 7928, 8092, 8192, 9025, 9216, 9261
)
)(FriedmanNumber.sieve(10000))
}
1 change: 0 additions & 1 deletion nmcb/src/test/scala/examples/TestVampireNumber.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class TestVampireNumber extends AnyFunSuite:

/** [OEIS-A014575](https://oeis.org/A014575) */
test("VampireNumber") {
println(VampireNumber.sieve(135837))
assertResult(
Vector(
1260, 1395, 1435, 1530, 1827, 2187, 6880, 102510, 104260, 105210, 105264, 105750, 108135,
Expand Down