phase 2
This commit is contained in:
127
Assets/Engine/Runtime/Utilities/Backtrack.cs
Normal file
127
Assets/Engine/Runtime/Utilities/Backtrack.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
|
||||
// ReSharper disable ReturnTypeCanBeEnumerable.Global
|
||||
// ReSharper disable ParameterTypeCanBeEnumerable.Local
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
{
|
||||
public struct Backtrack
|
||||
{
|
||||
private int[] indices;
|
||||
private int[] values;
|
||||
private int target;
|
||||
|
||||
public Backtrack(int[] indices, int[] values, int target)
|
||||
{
|
||||
this.indices = indices;
|
||||
this.values = values;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find any number of elements from the given set of indices and values with the maximum sum that doesn't exceed the target sum.
|
||||
/// </summary>
|
||||
/// <remarks>This function uses a backtracking approach to find the sum.</remarks>
|
||||
public int[] BruteForceLower(int[] chosen = null, int pos = 0)
|
||||
{
|
||||
chosen ??= Array.Empty<int>();
|
||||
|
||||
if (Sum(chosen) > target)
|
||||
{
|
||||
// if the sum of the chosen values exceeds the target, return nothing
|
||||
return Array.Empty<int>();
|
||||
}
|
||||
if (pos >= indices.Length)
|
||||
{
|
||||
// if we cannot add any more elements, return all chosen
|
||||
return chosen;
|
||||
}
|
||||
|
||||
// find the best indices when skipping the one at the current position
|
||||
var leave = BruteForceLower(chosen, pos + 1);
|
||||
// find the best indices when including the one at the current position
|
||||
var next = new int[chosen.Length + 1];
|
||||
for (var i = 0; i < chosen.Length; i++)
|
||||
{
|
||||
next[i] = chosen[i];
|
||||
}
|
||||
next[^1] = indices[pos];
|
||||
var pick = BruteForceLower(next, pos + 1);
|
||||
|
||||
// return the best result
|
||||
return Sum(leave) > Sum(pick) ? leave : pick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find any number of elements from the given set of indices and values with the minimum sum that is higher than the target sum.
|
||||
/// </summary>
|
||||
/// <remarks>This function uses a backtracking approach to find the sum.</remarks>
|
||||
public int[] BruteForceHigher(int[] chosen = null, int pos = int.MaxValue)
|
||||
{
|
||||
chosen ??= indices;
|
||||
if (pos == int.MaxValue)
|
||||
{
|
||||
pos = indices.Length - 1;
|
||||
}
|
||||
|
||||
if (Sum(chosen) < target)
|
||||
{
|
||||
// if the sum of the chosen values is lower than the target, return all indices (the maximum choice)
|
||||
return indices;
|
||||
}
|
||||
if (pos < 0)
|
||||
{
|
||||
// if we cannot remove any more elements, return all chosen
|
||||
return chosen;
|
||||
}
|
||||
|
||||
// find the best indices when not removing the one at the current position
|
||||
var leave = BruteForceHigher(chosen, pos - 1);
|
||||
// find the best indices when removing the one at the current position
|
||||
var next = new int[chosen.Length - 1];
|
||||
for (var i = 0; i < chosen.Length; i++)
|
||||
{
|
||||
if (i < pos)
|
||||
{
|
||||
next[i] = chosen[i];
|
||||
} else if (i > pos)
|
||||
{
|
||||
next[i - 1] = chosen[i];
|
||||
}
|
||||
}
|
||||
var pick = BruteForceHigher(next, pos - 1);
|
||||
|
||||
// return the best result
|
||||
return Sum(leave) < Sum(pick) ? leave : pick;
|
||||
}
|
||||
|
||||
public int[] BruteForce()
|
||||
{
|
||||
var lower = BruteForceLower();
|
||||
var higher = BruteForceHigher();
|
||||
var errLower = Math.Abs(target - Sum(lower));
|
||||
var errHigher = Math.Abs(target - Sum(higher));
|
||||
|
||||
return errLower < errHigher ? lower : higher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a subset of indices where the sum of their corresponding values is closest to a target sum.
|
||||
/// </summary>
|
||||
public static int[] Closest(int[] indices, int[] values, int target) =>
|
||||
new Backtrack(indices, values, target).BruteForce();
|
||||
|
||||
private int Sum(int[] chosen)
|
||||
{
|
||||
var sum = 0;
|
||||
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var i in chosen)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Utilities/Backtrack.cs.meta
Normal file
3
Assets/Engine/Runtime/Utilities/Backtrack.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 313a79a04104480f931206ff7f57d0c5
|
||||
timeCreated: 1671126182
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MathNet.Numerics.Distributions;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
@@ -8,19 +9,21 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
[Serializable]
|
||||
public struct NormalDistribution
|
||||
{
|
||||
public double mean, σ;
|
||||
public float μ, σ;
|
||||
|
||||
public static NormalDistribution Standard => new NormalDistribution { mean = 0, σ = 1 };
|
||||
public static NormalDistribution Standard => new NormalDistribution { μ = 0, σ = 1 };
|
||||
|
||||
public NormalDistribution(double[] samples) : this()
|
||||
public NormalDistribution(float[] samples) : this()
|
||||
{
|
||||
mean = Probability.Mean(samples);
|
||||
σ = Probability.StandardDeviation(samples, mean);
|
||||
μ = Probability.Mean(samples);
|
||||
σ = Probability.StandardDeviation(samples, μ);
|
||||
}
|
||||
|
||||
public double Sample() => σ * Probability.Normal() + mean;
|
||||
public float Sample() => σ * Probability.Normal() + μ;
|
||||
|
||||
public double Cumulative(double x) => new Normal(mean, σ).CumulativeDistribution(x);
|
||||
public float Cumulative(float x) => (float)new Normal(μ, σ).CumulativeDistribution(x);
|
||||
|
||||
public float InverseCumulative(float x) => (float)new Normal(μ, σ).InverseCumulativeDistribution(x);
|
||||
}
|
||||
|
||||
public static class Probability
|
||||
@@ -32,22 +35,22 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
/// For simplicity, the result is clamped between -3 and 3. This is accurate for 99.7% of all samples, by the three-σ rule.
|
||||
/// </summary>
|
||||
/// <remarks>The calculation of the random variable is done by a Box-Muller transform.</remarks>
|
||||
public static double Normal()
|
||||
public static float Normal()
|
||||
{
|
||||
double u1, u2, square;
|
||||
float u1, u2, square;
|
||||
|
||||
// get two random points inside the unit circle
|
||||
do
|
||||
{
|
||||
u1 = 2 * _random.NextDouble() - 1;
|
||||
u2 = 2 * _random.NextDouble() - 1;
|
||||
u1 = 2 * (float)_random.NextDouble() - 1;
|
||||
u2 = 2 * (float)_random.NextDouble() - 1;
|
||||
square = u1 * u1 + u2 * u2;
|
||||
} while (square >= 1f);
|
||||
|
||||
return u1 * Math.Sqrt(-2 * Math.Log(square) / square);
|
||||
return u1 * Mathf.Sqrt(-2 * Mathf.Log(square) / square);
|
||||
}
|
||||
|
||||
public static double Mean(double[] samples)
|
||||
public static float Mean(float[] samples)
|
||||
{
|
||||
if (samples.Length == 0)
|
||||
{
|
||||
@@ -57,10 +60,10 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
return samples.Sum() / samples.Length;
|
||||
}
|
||||
|
||||
public static double StandardDeviation(double[] samples) => StandardDeviation(samples, Mean(samples));
|
||||
public static double StandardDeviation(double[] samples, double mean)
|
||||
public static float StandardDeviation(float[] samples) => StandardDeviation(samples, Mean(samples));
|
||||
public static float StandardDeviation(float[] samples, float mean)
|
||||
{
|
||||
var deviations = new double[samples.Length];
|
||||
var deviations = new float[samples.Length];
|
||||
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
@@ -68,7 +71,7 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
deviations[i] = d * d;
|
||||
}
|
||||
|
||||
return Math.Sqrt(Mean(deviations));
|
||||
return Mathf.Sqrt(Mean(deviations));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,19 @@
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public int[] ToArray(bool includeMax = false)
|
||||
{
|
||||
var count = includeMax ? Length + 1 : Length;
|
||||
var array = new int[count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
array[i] = min + i;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{{{min}, ..., {max}}}";
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,14 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
|
||||
public static int RandomInclusive(int from, int to) => Random.Range(from, to + 1);
|
||||
|
||||
public static T RandomElement<T>(this List<T> list) => list[Random.Range(0, list.Count)];
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// remove a random element from a list and return it.
|
||||
/// </summary>
|
||||
public static T PopRandomElement<T>(this List<T> list)
|
||||
{
|
||||
var index = Random.Range(0, list.Count);
|
||||
@@ -35,7 +41,20 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
return element;
|
||||
}
|
||||
|
||||
#endregion
|
||||
public static T RandomElement<T>(this List<T> list) => list[Random.Range(0, list.Count)];
|
||||
|
||||
/// <summary>
|
||||
/// Perform a Fisher-Yates shuffle on a given list, leaving it in a random order.
|
||||
/// </summary>
|
||||
public static void Shuffle<T>(this List<T> list)
|
||||
{
|
||||
for (var n = list.Count - 1; n > 0; n--)
|
||||
{
|
||||
var i = Utilities.RandomInclusive(0, n);
|
||||
var j = Utilities.RandomInclusive(0, n);
|
||||
(list[i], list[j]) = (list[j], list[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Vector2IntExtensions
|
||||
|
||||
Reference in New Issue
Block a user