Move MathUtil methods to extensions (#432)

* Begin moving MathUtil methods to Extensions

* Add missing extension
This commit is contained in:
Jesse
2025-06-28 17:15:11 +02:00
committed by GitHub
parent 065ee32438
commit d0af6acbe6
9 changed files with 106 additions and 113 deletions
@@ -494,10 +494,7 @@ public class InsuranceController(
);
// Create prob array and add all attachments with rouble price as the weight
var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(
_mathUtil,
_cloner
);
var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(_cloner);
foreach (var (itemTpl, price) in weightedAttachmentByPrice)
{
attachmentsProbabilityArray.Add(
@@ -507,7 +504,7 @@ public class InsuranceController(
// Draw x attachments from weighted array to remove from parent, remove from pool after being picked
var attachmentIdsToRemove = attachmentsProbabilityArray.Draw(
(int)countOfAttachmentsToRemove,
(int) countOfAttachmentsToRemove,
false
);
foreach (var attachmentId in attachmentIdsToRemove)
@@ -881,7 +881,6 @@ public class RepeatableQuestController(
repeatableConfig
);
var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(
_mathUtil,
_cloner,
eliminationConfig.Targets
);
@@ -0,0 +1,59 @@
namespace SPTarkov.Server.Core.Extensions
{
public static class MathExtensions
{
/// <summary>
/// Helper to create the cumulative sum of all enumerable elements
/// [1, 2, 3, 4].CumulativeSum() = [1, 3, 6, 10]
/// </summary>
/// <param name="values">The enumerable with numbers of which to calculate the cumulative sum</param>
/// <returns>cumulative sum of values</returns>
public static IEnumerable<double> CumulativeSum(this IEnumerable<double> values)
{
double sum = 0;
foreach (var value in values)
{
sum += value;
yield return sum;
}
}
/// <summary>
/// Helper to create the cumulative sum of all enumerable elements
/// [1, 2, 3, 4].CumulativeSum() = [1, 3, 6, 10]
/// </summary>
/// <param name="values">The enumerable with numbers of which to calculate the cumulative sum</param>
/// <returns>cumulative sum of values</returns>
public static IEnumerable<float> CumulativeSum(this IEnumerable<float> values)
{
float sum = 0;
foreach (var value in values)
{
sum += value;
yield return sum;
}
}
/// <summary>
/// Helper to create the product of each element times factor
/// </summary>
/// <param name="values">The enumerable of numbers which shall be multiplied by the factor</param>
/// <param name="factor">Number to multiply each element by</param>
/// <returns>An enumerable of elements all multiplied by the factor</returns>
public static IEnumerable<double> Product(this IEnumerable<double> values, double factor)
{
return values.Select(v => v * factor);
}
/// <summary>
/// Helper to create the product of each element times factor
/// </summary>
/// <param name="values">The enumerable of numbers which shall be multiplied by the factor</param>
/// <param name="factor">Number to multiply each element by</param>
/// <returns>An enumerable of elements all multiplied by the factor</returns>
public static IEnumerable<float> Product(this IEnumerable<float> values, float factor)
{
return values.Select(v => v * factor);
}
}
}
@@ -401,14 +401,14 @@ public class LocationLootGenerator(
}
// Create probability array with all possible container ids in this group and their relative probability of spawning
var containerDistribution = new ProbabilityObjectArray<string, double>(_mathUtil, _cloner);
var containerDistribution = new ProbabilityObjectArray<string, double>(_cloner);
foreach (var x in containerIds)
{
var value = containerData.ContainerIdsWithProbability[x];
containerDistribution.Add(new ProbabilityObject<string, double>(x, value, value));
}
chosenContainerIds.AddRange(containerDistribution.Draw((int)containerData.ChosenCount));
chosenContainerIds.AddRange(containerDistribution.Draw((int) containerData.ChosenCount));
return chosenContainerIds;
}
@@ -653,7 +653,7 @@ public class LocationLootGenerator(
)
{
// Create probability array to calculate the total count of lootable items inside container
var itemCountArray = new ProbabilityObjectArray<int, float?>(_mathUtil, _cloner);
var itemCountArray = new ProbabilityObjectArray<int, float?>(_cloner);
var countDistribution = staticLootDist[containerTypeId]?.ItemCountDistribution;
if (countDistribution is null)
{
@@ -698,7 +698,7 @@ public class LocationLootGenerator(
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
var itemDistribution = new ProbabilityObjectArray<string, float?>(_mathUtil, _cloner);
var itemDistribution = new ProbabilityObjectArray<string, float?>(_cloner);
var itemContainerDistribution = staticLootDist[containerTypeId]?.ItemDistribution;
if (itemContainerDistribution is null)
@@ -804,7 +804,7 @@ public class LocationLootGenerator(
);
// Init empty array to hold spawn points, letting us pick them pseudo-randomly
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_cloner);
// Positions not in forced but have 100% chance to spawn
List<Spawnpoint> guaranteedLoosePoints = [];
@@ -855,7 +855,7 @@ public class LocationLootGenerator(
if (randomSpawnPointCount > 0 && spawnPointArray.Count > 0)
// Add randomly chosen spawn points
{
foreach (var si in spawnPointArray.Draw((int)randomSpawnPointCount, false))
foreach (var si in spawnPointArray.Draw((int) randomSpawnPointCount, false))
{
chosenSpawnPoints.Add(spawnPointArray.Data(si));
}
@@ -940,7 +940,7 @@ public class LocationLootGenerator(
var validItemIds = spawnPoint.Template.Items.Select(item => item.Id).ToHashSet();
// Construct container to hold above filtered items, letting us pick an item for the spot
var itemArray = new ProbabilityObjectArray<string, double?>(_mathUtil, _cloner);
var itemArray = new ProbabilityObjectArray<string, double?>(_cloner);
foreach (var itemDist in spawnPoint.ItemDistribution)
{
if (!validItemIds.Contains(itemDist.ComposedKey.Key))
@@ -333,22 +333,18 @@ public class EliminationQuestGenerator(
var locationsConfig = repeatableConfig.Locations;
var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(
mathUtil,
cloner,
eliminationConfig.Targets
);
var bodyPartsConfig = new ProbabilityObjectArray<string, List<string>>(
mathUtil,
cloner,
eliminationConfig.BodyParts
);
var weaponCategoryRequirementConfig = new ProbabilityObjectArray<string, List<string>>(
mathUtil,
cloner,
eliminationConfig.WeaponCategoryRequirements
);
var weaponRequirementConfig = new ProbabilityObjectArray<string, List<string>>(
mathUtil,
cloner,
eliminationConfig.WeaponRequirements
);
@@ -609,9 +605,9 @@ public class EliminationQuestGenerator(
+ generationData.EliminationConfig.MinDistance
);
distance = (int)Math.Ceiling((decimal)(distance / 5d)) * 5;
distance = (int) Math.Ceiling((decimal) (distance / 5d)) * 5;
var distanceDifficulty = (int)(
var distanceDifficulty = (int) (
MaxDistDifficulty * distance / generationData.EliminationConfig.MaxDistance
);
@@ -633,26 +629,26 @@ public class EliminationQuestGenerator(
{
// Filter out close range weapons from far distance requirement
case > 50:
{
List<string> weaponTypeBlacklist = ["Shotgun", "Pistol"];
{
List<string> weaponTypeBlacklist = ["Shotgun", "Pistol"];
// Filter out close range weapons from long distance requirement
generationData.WeaponCategoryRequirementConfig.RemoveAll(category =>
weaponTypeBlacklist.Contains(category.Key)
);
break;
}
// Filter out close range weapons from long distance requirement
generationData.WeaponCategoryRequirementConfig.RemoveAll(category =>
weaponTypeBlacklist.Contains(category.Key)
);
break;
}
// Filter out long range weapons from close distance requirement
case < 20:
{
List<string> weaponTypeBlacklist = ["MarksmanRifle", "DMR"];
{
List<string> weaponTypeBlacklist = ["MarksmanRifle", "DMR"];
// Filter out far range weapons from close distance requirement
generationData.WeaponCategoryRequirementConfig.RemoveAll(category =>
weaponTypeBlacklist.Contains(category.Key)
);
break;
}
// Filter out far range weapons from close distance requirement
generationData.WeaponCategoryRequirementConfig.RemoveAll(category =>
weaponTypeBlacklist.Contains(category.Key)
);
break;
}
}
// Pick a weighted weapon category
@@ -433,7 +433,7 @@ public class ItemHelper(
// Run getItemPrice for each tpl in tpls array, return sum
return tpls.Aggregate(
0,
(total, tpl) => total + (int)GetItemPrice(tpl).GetValueOrDefault(0)
(total, tpl) => total + (int) GetItemPrice(tpl).GetValueOrDefault(0)
);
}
@@ -902,8 +902,8 @@ public class ItemHelper(
// Find required items to take after buying (handles multiple items)
var desiredBarterIds =
desiredBarterItemIds.GetType() == typeof(string)
? [(string)desiredBarterItemIds]
: (List<string>)desiredBarterItemIds;
? [(string) desiredBarterItemIds]
: (List<string>) desiredBarterItemIds;
List<Item> matchingItems = [];
foreach (var barterId in desiredBarterIds)
@@ -1456,7 +1456,7 @@ public class ItemHelper(
var cartridgeItemToAdd = CreateCartridges(
ammoBox[0].Id,
cartridgeTpl,
(int)cartridgeCountToAdd,
(int) cartridgeCountToAdd,
location
);
@@ -1485,7 +1485,7 @@ public class ItemHelper(
0
].Filter?.FirstOrDefault();
ammoBox.Add(
CreateCartridges(ammoBox[0].Id, cartridgeTpl, (int)ammoBoxMaxCartridgeCount, 0)
CreateCartridges(ammoBox[0].Id, cartridgeTpl, (int) ammoBoxMaxCartridgeCount, 0)
);
}
@@ -1593,8 +1593,8 @@ public class ItemHelper(
}
var desiredStackCount = _randomUtil.GetInt(
(int)Math.Round(minSizeMultiplier * magazineCartridgeMaxCount ?? 0),
(int)magazineCartridgeMaxCount
(int) Math.Round(minSizeMultiplier * magazineCartridgeMaxCount ?? 0),
(int) magazineCartridgeMaxCount
);
if (magazineWithChildCartridges.Count > 1)
@@ -1693,7 +1693,7 @@ public class ItemHelper(
return null;
}
var ammoArray = new ProbabilityObjectArray<string, float?>(_mathUtil, _cloner);
var ammoArray = new ProbabilityObjectArray<string, float?>(_cloner);
foreach (var icd in ammos)
{
// Whitelist exists and tpl not inside it, skip
@@ -1706,7 +1706,7 @@ public class ItemHelper(
ammoArray.Add(
new ProbabilityObject<string, float?>(
icd.Tpl,
(double)icd.RelativeProbability,
(double) icd.RelativeProbability,
null
)
);
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Utils.Collections;
@@ -23,16 +24,13 @@ namespace SPTarkov.Server.Core.Utils.Collections;
public class ProbabilityObjectArray<K, V> : List<ProbabilityObject<K, V>>
{
private readonly ICloner _cloner;
private readonly MathUtil _mathUtil;
public ProbabilityObjectArray(
MathUtil mathUtil,
ICloner cloner,
ICollection<ProbabilityObject<K, V>>? items = null
)
: base(items ?? [])
{
_mathUtil = mathUtil;
_cloner = cloner;
}
@@ -43,11 +41,11 @@ public class ProbabilityObjectArray<K, V> : List<ProbabilityObject<K, V>>
/// <returns>Cumulative Sum normalized to 1</returns>
public List<double> CumulativeProbability(List<double> probValues)
{
var sum = _mathUtil.ListSum(probValues);
var probCumsum = _mathUtil.ListCumSum(probValues);
probCumsum = _mathUtil.ListProduct(probCumsum, 1D / sum);
var sum = probValues.Sum();
var probCumsum = probValues.CumulativeSum();
probCumsum = probCumsum.Product(1D / sum);
return probCumsum;
return probCumsum.ToList();
}
/// <summary>
@@ -57,11 +55,7 @@ public class ProbabilityObjectArray<K, V> : List<ProbabilityObject<K, V>>
/// <returns>Filtered results</returns>
public ProbabilityObjectArray<K, V> Filter(Predicate<ProbabilityObject<K, V>> predicate)
{
var result = new ProbabilityObjectArray<K, V>(
_mathUtil,
_cloner,
new List<ProbabilityObject<K, V>>()
);
var result = new ProbabilityObjectArray<K, V>(_cloner, new List<ProbabilityObject<K, V>>());
foreach (var probabilityObject in this)
{
if (predicate.Invoke(probabilityObject))
@@ -81,7 +75,6 @@ public class ProbabilityObjectArray<K, V> : List<ProbabilityObject<K, V>>
{
var clone = _cloner.Clone(this);
var probabilityObjects = new ProbabilityObjectArray<K, V>(
_mathUtil,
_cloner,
new List<ProbabilityObject<K, V>>()
);
@@ -97,7 +90,7 @@ public class ProbabilityObjectArray<K, V> : List<ProbabilityObject<K, V>>
/// <returns>ProbabilityObjectArray without the dropped element</returns>
public ProbabilityObjectArray<K, V> Drop(K key)
{
return (ProbabilityObjectArray<K, V>)this.Where(r => !r.Key?.Equals(key) ?? false);
return (ProbabilityObjectArray<K, V>) this.Where(r => !r.Key?.Equals(key) ?? false);
}
/// <summary>
@@ -6,58 +6,6 @@ namespace SPTarkov.Server.Core.Utils;
[Injectable(InjectionType.Singleton)]
public class MathUtil
{
/// <summary>
/// Helper to create the sum of all list elements
/// </summary>
/// <param name="values">List of floats to sum</param>
/// <returns>sum of all values</returns>
public double ListSum(List<double> values)
{
// Sum the list starting with an initial value of 0
return values.Sum();
}
public float ListSum(List<float> values)
{
// Sum the list starting with an initial value of 0
return values.Sum();
}
/// <summary>
/// Helper to create the cumulative sum of all list elements
/// ListCumSum([1, 2, 3, 4]) = [1, 3, 6, 10]
/// </summary>
/// <param name="values">The list with numbers of which to calculate the cumulative sum</param>
/// <returns>cumulative sum of values</returns>
public List<double> ListCumSum(List<double> values)
{
if (values.Count == 0)
{
return [];
}
var cumSumArray = new double[values.Count];
cumSumArray[0] = values[0];
for (var i = 1; i < values.Count; i++)
{
cumSumArray[i] = cumSumArray[i - 1] + values[i];
}
return [.. cumSumArray];
}
/// <summary>
/// Helper to create the product of each element times factor
/// </summary>
/// <param name="values">The list of numbers which shall be multiplied by the factor</param>
/// <param name="factor">Number to multiply each element by</param>
/// <returns>A list of elements all multiplied by the factor</returns>
public List<double> ListProduct(List<double> values, double factor)
{
return values.Select(v => v * factor).ToList();
}
/// <summary>
/// Helper to add a constant to all list elements
/// </summary>
+4 -3
View File
@@ -1,3 +1,4 @@
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Utils;
namespace UnitTests.Tests.Utils;
@@ -19,7 +20,7 @@ public class MathUtilTests
var test = new List<float> { 1.1f, 2.1f, 3.3f };
const double expected = 6.5f;
var actual = _mathUtil.ListSum(test);
var actual = test.Sum();
Assert.AreEqual(expected, actual, $"ListSum() Expected: {expected}, Actual: {actual}");
}
@@ -30,7 +31,7 @@ public class MathUtilTests
var test = new List<double> { 1f, 2f, 3f, 4f };
var expected = new List<double> { 1f, 3f, 6f, 10f };
var actual = _mathUtil.ListCumSum(test);
var actual = test.CumulativeSum().ToList();
for (var i = 0; i < actual.Count; i++)
{
@@ -49,7 +50,7 @@ public class MathUtilTests
var test = new List<double> { 1f, 2f, 3f, 4f };
var expected = new List<double> { 2f, 4f, 6f, 8f };
var actual = _mathUtil.ListProduct(test, 2);
var actual = test.Product(2).ToList();
for (var i = 0; i < actual.Count; i++)
{