I am fairly lost with this script - I don't get it - why does it leave duplicate entries?
private static float GenerateMedian(IEnumerable<Collider> items, KDAxis axis)
{
float[] allValues = items.SelectMany(AxisSelector(axis)).ToArray();
Debug.LogFormat("{0} all values for {1} items: {2}.", allValues.Length, items.Count(), string.Join(", ", allValues.Select(v => v.ToString("F10")).ToArray()));
#if BASIC_DISTINCT
float[] values = allValues.Distinct().OrderBy(f => f).ToArray();
#else
float[] values = allValues.Distinct(new KDFloatComparer(0.0001f)).OrderBy(f => f).ToArray();
#endif
Debug.LogFormat("{0} distinct values for {1} items: {2}.", values.Length, items.Count(), string.Join(", ", values.Select(v => v.ToString("F10")).ToArray()));
int medianIndex = Mathf.CeilToInt(values.Length / 2f) - 1;
float medianValue = values[medianIndex];
Debug.LogFormat("Median index: {0} (left: {1}; right: {2}) value: {3}", medianIndex, medianIndex + 1, values.Length - 1 - medianIndex, medianValue);
return medianValue;
}
private static Func<Collider, IEnumerable<float>> AxisSelector(KDAxis axis)
{
switch (axis)
{
case KDAxis.X:
return XAxisSelector;
case KDAxis.Y:
return YAxisSelector;
case KDAxis.Z:
return ZAxisSelector;
}
return XAxisSelector;
}
private static IEnumerable<float> XAxisSelector(Collider collider)
{
yield return collider.bounds.max.x;
yield return collider.bounds.min.x;
}
private static IEnumerable<float> YAxisSelector(Collider collider)
{
yield return collider.bounds.max.y;
yield return collider.bounds.min.y;
}
private static IEnumerable<float> ZAxisSelector(Collider collider)
{
yield return collider.bounds.max.z;
yield return collider.bounds.min.z;
}
Provides this output:
28 all values for 14 items: 3.0000000000, 2.0000000000, 11.0000000000, -11.0000000000, -5.0000010000, -10.0000000000, 3.0000000000, 2.0000000000, 3.0000000000, 2.0000000000, 11.0000000000, -11.0000000000, -10.0000000000, -11.0000400000, 3.0000000000, 2.0000000000, 7.0000000000, 6.0000000000, -7.0000000000, -10.0000000000, 10.0000000000, -10.0000000000, 11.0000000000, 9.9999550000, -8.0000000000, -9.9999980000, 3.0000000000, 2.0000000000.
20 distinct values for 14 items: -11.0000400000, -11.0000000000, -10.0000000000, -10.0000000000, -9.9999980000, -8.0000000000, -7.0000000000, -5.0000010000, 2.0000000000, 2.0000000000, 2.0000000000, 3.0000000000, 3.0000000000, 3.0000000000, 6.0000000000, 7.0000000000, 9.9999550000, 10.0000000000, 11.0000000000, 11.0000000000.
And it clearly contains duplicates - for instance the 3 x 2.0
and 3 x 3.0
.
Even if I were to implement a custom float comparer, and feed it into Distinct()
with new KDFloatComparer(0.0001f)
:
public class KDFloatComparer : EqualityComparer<float>
{
public readonly float InternalEpsilon = 0.001f;
public KDFloatComparer(float epsilon) : base()
{
InternalEpsilon = epsilon;
}
// http://stackoverflow.com/a/31587700/393406
public override bool Equals(float a, float b)
{
float absoluteA = Math.Abs(a);
float absoluteB = Math.Abs(b);
float absoluteDifference = Math.Abs(a - b);
if (a == b)
{
return true;
}
else if (a == 0 || b == 0 || absoluteDifference < float.Epsilon)
{
// a or b is zero or both are extremely close to it.
// Relative error is less meaningful here.
return absoluteDifference < InternalEpsilon;
}
else
{
// Use relative error.
return absoluteDifference / (absoluteA + absoluteB) < InternalEpsilon;
}
return true;
}
public override int GetHashCode(float value)
{
return value.GetHashCode();
}
}
The result is exactly the same.
I did try to replicate the scenario over on csharppad.com
- it didn't leave duplicates. Though, I didn't use the SelectMany
approach, I made raw arrays with the reported ToString("F10")
values, which makes me think that the problem is with floating point precision, however, no matter how I have implemented the EqualityComparer
(had some custom variations before attempting to use the SO one), I cannot seem to nail it.
How could I fix this?