24 game/Solve: Difference between revisions

Content added Content deleted
(Added C#)
Line 1,122: Line 1,122:
(( 5 + 3 ) * 3 ) / 1
(( 5 + 3 ) * 3 ) / 1
</pre>
</pre>

=={{header|C sharp}}==
Generate binary trees -> generate permutations -> create expression -> evaluate expression<br/>
This works with other targets and more numbers but it will of course become slower.<br/>
Redundant expressions are filtered out (based on https://www.4nums.com/theory/) but I'm not sure I caught them all.
{{works with|C sharp|8}}
<lang csharp>using System;
using System.Collections.Generic;
using static System.Linq.Enumerable;

public static class Solve24Game
{
public static void Main2() {
var testCases = new [] {
new [] { 1,1,2,7 },
new [] { 1,2,3,4 },
new [] { 1,2,4,5 },
new [] { 1,2,7,7 },
new [] { 1,4,5,6 },
new [] { 3,3,8,8 },
new [] { 4,4,5,9 },
new [] { 5,5,5,5 },
new [] { 5,6,7,8 },
new [] { 6,6,6,6 },
new [] { 6,7,8,9 },
};
foreach (var t in testCases) Test(24, t);
Test(100, 9,9,9,9,9,9);

static void Test(int target, params int[] numbers) {
foreach (var eq in GenerateEquations(target, numbers)) Console.WriteLine(eq);
Console.WriteLine();
}
}

static readonly char[] ops = { '*', '/', '+', '-' };
public static IEnumerable<string> GenerateEquations(int target, params int[] numbers) {
var operators = Repeat(ops, numbers.Length - 1).CartesianProduct().Select(e => e.ToArray()).ToList();
return (
from pattern in Patterns(numbers.Length)
let expression = CreateExpression(pattern)
from ops in operators
where expression.WithOperators(ops).HasPreferredTree()
from permutation in Permutations(numbers)
let expr = expression.WithValues(permutation)
where expr.Value == target && expr.HasPreferredValues()
select $"{expr.ToString()} = {target}")
.Distinct()
.DefaultIfEmpty($"Cannot make {target} with {string.Join(", ", numbers)}");
}

///<summary>Generates postfix expression trees where 1's represent operators and 0's represent numbers.</summary>
static IEnumerable<int> Patterns(int length) {
if (length == 1) yield return 0; //0
if (length == 2) yield return 1; //001
if (length < 3) yield break;
//Of each tree, the first 2 bits must always be 0 and the last bit must be 1. Generate the bits in between.
length -= 2;
int len = length * 2 + 3;
foreach (int permutation in BinaryPatterns(length, length * 2)) {
(int p, int l) = ((permutation << 1) + 1, len);
if (IsValidPattern(ref p, ref l)) yield return (permutation << 1) + 1;
}
}

///<summary>Generates all numbers with the given number of 1's and total length.</summary>
static IEnumerable<int> BinaryPatterns(int ones, int length) {
int initial = (1 << ones) - 1;
int blockMask = (1 << length) - 1;
for (int v = initial; v >= initial; ) {
yield return v;
int w = (v | (v - 1)) + 1;
w |= (((w & -w) / (v & -v)) >> 1) - 1;
v = w & blockMask;
}
}

static bool IsValidPattern(ref int pattern, ref int len) {
bool isNumber = (pattern & 1) == 0;
pattern >>= 1;
len--;
if (isNumber) return true;
IsValidPattern(ref pattern, ref len);
IsValidPattern(ref pattern, ref len);
return len == 0;
}

static Expr CreateExpression(int pattern) {
return Create();

Expr Create() {
bool isNumber = (pattern & 1) == 0;
pattern >>= 1;
if (isNumber) return new Const(0);
Expr right = Create();
Expr left = Create();
return new Binary('*', left, right);
}
}

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) {
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from acc in accumulator
from item in sequence
select acc.Concat(new [] { item }));
}

private static List<int> helper = new List<int>();
public static IEnumerable<T[]> Permutations<T>(params T[] input) {
if (input == null || input.Length == 0) yield break;
helper.Clear();
for (int i = 0; i < input.Length; i++) helper.Add(i);
while (true) {
yield return input;
int cursor = helper.Count - 2;
while (cursor >= 0 && helper[cursor] > helper[cursor + 1]) cursor--;
if (cursor < 0) break;
int i = helper.Count - 1;
while (i > cursor && helper[i] < helper[cursor]) i--;
(helper[cursor], helper[i]) = (helper[i], helper[cursor]);
(input[cursor], input[i]) = (input[i], input[cursor]);
int firstIndex = cursor + 1;
for (int lastIndex = helper.Count - 1; lastIndex > firstIndex; ++firstIndex, --lastIndex) {
(helper[firstIndex], helper[lastIndex]) = (helper[lastIndex], helper[firstIndex]);
(input[firstIndex], input[lastIndex]) = (input[lastIndex], input[firstIndex]);
}
}
}

static Expr WithOperators(this Expr expr, char[] operators) {
int i = 0;
SetOperators(expr, operators, ref i);
return expr;

static void SetOperators(Expr expr, char[] operators, ref int i) {
if (expr is Binary b) {
b.Symbol = operators[i++];
SetOperators(b.Right, operators, ref i);
SetOperators(b.Left, operators, ref i);
}
}
}

static Expr WithValues(this Expr expr, int[] values) {
int i = 0;
SetValues(expr, values, ref i);
return expr;

static void SetValues(Expr expr, int[] values, ref int i) {
if (expr is Binary b) {
SetValues(b.Left, values, ref i);
SetValues(b.Right, values, ref i);
} else {
expr.Value = values[i++];
}
}
}

static bool HasPreferredTree(this Expr expr) => expr switch {
Const _ => true,
// a / b * c => a * c / b
((_, '/' ,_), '*', _) => false,
// c + a * b => a * b + c
(var l, '+', (_, '*' ,_) r) when l.Depth < r.Depth => false,
// c + a / b => a / b + c
(var l, '+', (_, '/' ,_) r) when l.Depth < r.Depth => false,
// a * (b + c) => (b + c) * a
(var l, '*', (_, '+' ,_) r) when l.Depth < r.Depth => false,
// a * (b - c) => (b - c) * a
(var l, '*', (_, '-' ,_) r) when l.Depth < r.Depth => false,
// (a +- b) + (c */ d) => ((c */ d) + a) +- b
((_, var p, _), '+', (_, var q, _)) when "+-".Contains(p) && "*/".Contains(q) => false,
// a + (b + c) => (a + b) + c
(var l, '+', (_, '+' ,_) r) => false,
// a + (b - c) => (a + b) - c
(var l, '+', (_, '-' ,_) r) => false,
// a - (b + c) => (a - b) + c
(_, '-', (_, '+', _)) => false,
// a * (b * c) => (a * b) * c
(var l, '*', (_, '*' ,_) r) => false,
// a * (b / c) => (a * b) / c
(var l, '*', (_, '/' ,_) r) => false,
// a / (b / c) => (a * c) / b
(var l, '/', (_, '/' ,_) r) => false,
// a - (b - c) * d => (c - b) * d + a
(_, '-', ((_, '-' ,_), '*', _)) => false,
// a - (b - c) / d => (c - b) / d + a
(_, '-', ((_, '-' ,_), '/', _)) => false,
// a - (b - c) => a + c - b
(_, '-', (_, '-', _)) => false,
// (a - b) + c => (a + c) - b
((_, '-', var b), '+', var c) => false,

(var l, _, var r) => l.HasPreferredTree() && r.HasPreferredTree()
};

static bool HasPreferredValues(this Expr expr) => expr switch {
Const _ => true,

// -a + b => b - a
(var l, '+', var r) when l.Value < 0 && r.Value >= 0 => false,
// b * a => a * b
(var l, '*', var r) when l.Depth == r.Depth && l.Value > r.Value => false,
// b + a => a + b
(var l, '+', var r) when l.Depth == r.Depth && l.Value > r.Value => false,
// (b o c) * (a o d) => (a o d) * (b o c)
((var a, _, _) l, '*', (var c, _, _) r) when l.Value == r.Value && l.Depth == r.Depth && a.Value < c.Value => false,
// (b o c) + (a o d) => (a o d) + (b o c)
((var a, var p, _) l, '+', (var c, var q, _) r) when l.Value == r.Value && l.Depth == r.Depth && a.Value < c.Value => false,
// (a * c) * b => (a * b) * c
((_, '*', var c), '*', var b) when b.Value < c.Value => false,
// (a + c) + b => (a + b) + c
((_, '+', var c), '+', var b) when b.Value < c.Value => false,
// (a - b) - c => (a - c) - b
((_, '-', var b), '-', var c) when b.Value < c.Value => false,
// a / 1 => a * 1
(_, '/', var b) when b.Value == 1 => false,
// a * b / b => a + b - b
((_, '*', var b), '/', var c) when b.Value == c.Value => false,
// a * 1 * 1 => a + 1 - 1
((_, '*', var b), '*', var c) when b.Value == 1 && c.Value == 1 => false,

(var l, _, var r) => l.HasPreferredValues() && r.HasPreferredValues()
};

private struct Fraction : IEquatable<Fraction>, IComparable<Fraction>
{
public readonly int Numerator, Denominator;

public Fraction(int numerator, int denominator)
=> (Numerator, Denominator) = (numerator, denominator) switch
{
(_, 0) => (Math.Sign(numerator), 0),
(0, _) => (0, 1),
(_, var d) when d < 0 => (-numerator, -denominator),
_ => (numerator, denominator)
};

public static implicit operator Fraction(int i) => new Fraction(i, 1);
public static Fraction operator +(Fraction a, Fraction b) =>
new Fraction(a.Numerator * b.Denominator + a.Denominator * b.Numerator, a.Denominator * b.Denominator);
public static Fraction operator -(Fraction a, Fraction b) =>
new Fraction(a.Numerator * b.Denominator + a.Denominator * -b.Numerator, a.Denominator * b.Denominator);
public static Fraction operator *(Fraction a, Fraction b) =>
new Fraction(a.Numerator * b.Numerator, a.Denominator * b.Denominator);
public static Fraction operator /(Fraction a, Fraction b) =>
new Fraction(a.Numerator * b.Denominator, a.Denominator * b.Numerator);

public static bool operator ==(Fraction a, Fraction b) => a.CompareTo(b) == 0;
public static bool operator !=(Fraction a, Fraction b) => a.CompareTo(b) != 0;
public static bool operator <(Fraction a, Fraction b) => a.CompareTo(b) < 0;
public static bool operator >(Fraction a, Fraction b) => a.CompareTo(b) > 0;
public static bool operator <=(Fraction a, Fraction b) => a.CompareTo(b) <= 0;
public static bool operator >=(Fraction a, Fraction b) => a.CompareTo(b) >= 0;

public bool Equals(Fraction other) => Numerator == other.Numerator && Denominator == other.Denominator;
public override string ToString() => Denominator == 1 ? Numerator.ToString() : $"[{Numerator}/{Denominator}]";

public int CompareTo(Fraction other) => (Numerator, Denominator, other.Numerator, other.Denominator) switch {
var ( n1, d1, n2, d2) when n1 == n2 && d1 == d2 => 0,
( 0, 0, _, _) => (-1),
( _, _, 0, 0) => 1,
var ( n1, d1, n2, d2) when d1 == d2 => n1.CompareTo(n2),
(var n1, 0, _, _) => Math.Sign(n1),
( _, _, var n2, 0) => Math.Sign(n2),
var ( n1, d1, n2, d2) => (n1 * d2).CompareTo(n2 * d1)
};
}

private abstract class Expr
{
protected Expr(char symbol) => Symbol = symbol;
public char Symbol { get; set; }
public abstract Fraction Value { get; set; }
public abstract int Depth { get; }
public abstract void Deconstruct(out Expr left, out char symbol, out Expr right);
}

private sealed class Const : Expr
{
public Const(Fraction value) : base('c') => Value = value;
public override Fraction Value { get; set; }
public override int Depth => 0;
public override void Deconstruct(out Expr left, out char symbol, out Expr right) => (left, symbol, right) = (this, Symbol, this);
public override string ToString() => Value.ToString();
}

private sealed class Binary : Expr
{
public Binary(char symbol, Expr left, Expr right) : base(symbol) => (Left, Right) = (left, right);
public Expr Left { get; }
public Expr Right { get; }
public override int Depth => Math.Max(Left.Depth, Right.Depth) + 1;
public override void Deconstruct(out Expr left, out char symbol, out Expr right) => (left, symbol, right) = (Left, Symbol, Right);

public override Fraction Value {
get => Symbol switch {
'*' => Left.Value * Right.Value,
'/' => Left.Value / Right.Value,
'+' => Left.Value + Right.Value,
'-' => Left.Value - Right.Value,
_ => throw new InvalidOperationException() };
set {}
}

public override string ToString() => Symbol switch {
'*' => ToString("+-".Contains(Left.Symbol), "+-".Contains(Right.Symbol)),
'/' => ToString("+-".Contains(Left.Symbol), "*/+-".Contains(Right.Symbol)),
'+' => ToString(false, false),
'-' => ToString(false, "+-".Contains(Right.Symbol)),
_ => $"[{Value}]"
};

private string ToString(bool wrapLeft, bool wrapRight) =>
$"{(wrapLeft ? $"({Left})" : $"{Left}")} {Symbol} {(wrapRight ? $"({Right})" : $"{Right}")}";
}
}</lang>
{{out}}
<pre>
(1 + 2) * (1 + 7) = 24

(1 + 3) * (2 + 4) = 24
1 * 2 * 3 * 4 = 24
(1 + 2 + 3) * 4 = 24

(5 - 1) * (2 + 4) = 24
(2 + 5 - 1) * 4 = 24

(7 * 7 - 1) / 2 = 24

4 / (1 - 5 / 6) = 24
6 / (5 / 4 - 1) = 24

8 / (3 - 8 / 3) = 24

Cannot make 24 with 4, 4, 5, 9

5 * 5 - 5 / 5 = 24

(8 - 6) * (5 + 7) = 24
6 * 8 / (7 - 5) = 24
(5 + 7 - 8) * 6 = 24

6 + 6 + 6 + 6 = 24
6 * 6 - 6 - 6 = 24

6 * 8 / (9 - 7) = 24

(9 / 9 + 9) * (9 / 9 + 9) = 100</pre>


=={{header|Ceylon}}==
=={{header|Ceylon}}==