Packages

A library for computing integer complexity.

Current section

Files

Jump to
integer_complexity src integer_complexity.gleam
Raw

src/integer_complexity.gleam

//// This module contains functions for computing the complexity of an integer,
//// and a corrisponding mathematical expression that uses only ones, addition, and multiplication to reach that integer.
//// testing the products
import gleam/bool
import gleam/float
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam_community/maths/arithmetics
import gleam_community/maths/piecewise
import integer_complexity/expression.{type Expression}
import integer_complexity/internal/array
import integer_complexity/internal/memo
/// The complexity and a valid expression of an Integer.
@internal
pub opaque type ComplexityData {
ComplexityData(complexity: Int, from: DerivedExpression)
}
/// An expression that can point to other integer complexity expressions.
type DerivedExpression {
DerivedAdd(lhs: DerivedExpression, rhs: DerivedExpression)
DerivedMultiply(lhs: DerivedExpression, rhs: DerivedExpression)
Derived(from_complexity: Int)
DerivedOne
}
/// A big numner.
const integer_limit = 2_147_483_647
const default_data = ComplexityData(complexity: 0, from: DerivedOne)
/// Create a new cache used to store computed integer complexities.
pub const new_cache = memo.create
/// Returns a list of integer complexities from 1 up to the specified integer.
/// Returns `Error(Nil)` if the specified integer is less than 1.
pub fn get_complexities_up_to(cache, integer: Int) -> Result(List(Int), Nil) {
use <- bool.guard(integer <= 0, Error(Nil))
list.map(get_complexity_data_up_to(cache, integer), fn(x) { x.complexity })
|> Ok()
}
/// Returns a list of integer complexity expressins (one per integer) from 1 up to the specified integer.
/// Returns `Error(Nil)` if the specified integer is less than 1.
pub fn get_expressions_up_to(
cache,
integer: Int,
) -> Result(List(Expression), Nil) {
use <- bool.guard(integer <= 0, Error(Nil))
list.map(get_complexity_data_up_to(cache, integer), fn(x) {
construct_expression(cache, x.from)
})
|> Ok()
}
/// Returns the complexity data from 1 up to the specified integer.
fn get_complexity_data_up_to(cache, integer: Int) -> List(ComplexityData) {
// case integer <= cache.highest_computed {
// True -> {
// let list =
// array.to_list(cache.array)
// |> list.drop(1)
// |> list.take(integer)
// #(cache, list)
// }
// False -> {
// let assert Ok(new_cache) = extend_complexity_list(integer, cache)
// get_complexity_data_up_to(ComplexitiesCache(new_cache, integer), integer)
// }
// }
list.range(1, integer)
|> list.map(complexity_rec(cache, _))
}
/// Returns the integer complexity of the (absoulte value of the) specified integer.
pub fn get_complexity(cache cache, of integer: Int) -> Int {
use <- bool.guard(integer == 0, 0)
get_complexity_data(cache, int.absolute_value(integer)).complexity
}
/// Returns a valid expression following the rules of integer complexity of the (absoulte value of the) specified integer.
/// Note that there can be multiple valid expressions for an integer, but this function only
/// generates a single expression.
/// Returns `Error(Nil)` if the specified integer is `0`.
pub fn get_expression(cache cache, of integer: Int) -> Result(Expression, Nil) {
use <- bool.guard(integer == 0, Error(Nil))
get_complexity_data(cache, int.absolute_value(integer)).from
|> construct_expression(cache, _)
|> Ok()
}
/// Return the complexity data of the specified integer.
fn get_complexity_data(cache cache, of integer: Int) -> ComplexityData {
complexity_rec(cache, integer)
}
/// Recursively calculate the complexity of an integer
fn complexity_rec(cache, n: Int) -> ComplexityData {
use <- memo.memoize(cache, n)
use <- bool.guard(n == 1, ComplexityData(n, DerivedOne))
//usual best value
//1 + complexity of [n - 1]
let base_complexity =
ComplexityData(
complexity_rec(cache, n - 1).complexity + 1,
DerivedAdd(Derived(n - 1), DerivedOne),
)
let assert target = complexity_rec(cache, n - 1).complexity
let t = calc_t(target / 2, target, n)
let k_max = a000792(t)
let sum_test_result =
sums(cache, n, k_max)
|> option.unwrap(base_complexity)
let divisor_test_result =
divisors(cache, n)
|> option.unwrap(base_complexity)
let assert Ok(result) =
piecewise.list_minimum(
[base_complexity, sum_test_result, divisor_test_result],
fn(a, b) { int.compare(a.complexity, b.complexity) },
)
result
}
fn sums(cache, n: Int, max: Int) -> Option(ComplexityData) {
use <- bool.guard(max < 6, None)
list.range(6, max)
|> list.fold(None, fn(acc, m) {
let complexity_m = complexity_rec(cache, m).complexity
let complexity_n_m = complexity_rec(cache, n - m).complexity
let sum_value = complexity_m + complexity_n_m
case acc {
Some(ComplexityData(complexity, _)) if sum_value < complexity ->
Some(ComplexityData(sum_value, DerivedAdd(Derived(m), Derived(n - m))))
None ->
Some(ComplexityData(sum_value, DerivedAdd(Derived(m), Derived(n - m))))
_ -> acc
}
})
}
fn divisors(cache, n: Int) -> Option(ComplexityData) {
let divisors = arithmetics.divisors(n)
let smaller_divisors =
list.take(divisors, float.round(int.to_float(list.length(divisors)) /. 2.0))
|> list.drop(1)
let sum_complexity =
list.fold(smaller_divisors, None, fn(acc, a) {
let complexity_a = complexity_rec(cache, a).complexity
let complexity_b = complexity_rec(cache, n / a).complexity
let prod_complexity = complexity_a + complexity_b
case acc {
None ->
Some(ComplexityData(
prod_complexity,
DerivedMultiply(Derived(a), Derived(n / a)),
))
Some(ComplexityData(complexity, _)) if prod_complexity < complexity ->
Some(ComplexityData(
prod_complexity,
DerivedMultiply(Derived(a), Derived(n / a)),
))
_ -> acc
}
})
sum_complexity
}
fn products(
k: Int,
max: Int,
n: Int,
complexity: array.Array(ComplexityData),
) -> array.Array(ComplexityData) {
use <- bool.guard(k > max, complexity)
let complexity_k =
array.get(complexity, k)
|> result.map(fn(x) { x.complexity })
|> result.unwrap(integer_limit)
let complexity_n =
array.get(complexity, n)
|> result.map(fn(x) { x.complexity })
|> result.unwrap(integer_limit)
let complexity_k_n = array.get(complexity, k * n)
let prod_value = complexity_k + complexity_n
let assert Ok(updated_complexity) = case complexity_k_n {
Error(_) ->
array.set(
complexity,
k * n,
ComplexityData(prod_value, DerivedMultiply(Derived(k), Derived(n))),
)
Ok(ComplexityData(k_n_value, _)) if prod_value < k_n_value ->
array.set(
complexity,
k * n,
ComplexityData(prod_value, DerivedMultiply(Derived(k), Derived(n))),
)
_ -> Ok(complexity)
}
products(k + 1, max, n, updated_complexity)
}
fn calc_t(t: Int, target: Int, index: Int) -> Int {
case a000792(t) + a000792(target - t) < index {
True -> calc_t(t - 1, target, index)
False -> t
}
}
@internal
pub fn a000792(n: Int) -> Int {
a000792_rec(n, 1)
}
fn a000792_rec(n: Int, result: Int) -> Int {
case n >= 5 || n == 3 {
True -> a000792_rec(n - 3, result * 3)
False -> int.bitwise_shift_left(result, n / 2)
}
}
fn construct_expression(
cache,
derived_expression: DerivedExpression,
) -> Expression {
case derived_expression {
DerivedOne -> expression.One
DerivedAdd(lhs, rhs) ->
expression.Add(
construct_expression(cache, lhs),
construct_expression(cache, rhs),
)
DerivedMultiply(lhs, rhs) ->
expression.Multiply(
construct_expression(cache, lhs),
construct_expression(cache, rhs),
)
Derived(n) -> {
let data = complexity_rec(cache, n)
construct_expression(cache, data.from)
}
}
}