【问题标题】:Compare 3D Coordinates with Tolerance将 3D 坐标与公差进行比较
【发布时间】:2021-11-14 06:24:08
【问题描述】:

给定两个坐标列表(作为双三元组)

var reference = 
  new List<(double x, double y, double z)()
    {(10,10,10),
     (15,15,15)};

还有一组真实世界的坐标,比如

var coords =
  new List<(double x, double y, double z)()
    {(9.97,10.02,10),
     (15.01,14.98,15),
     (12.65,18.69,0)};

我需要coords的项目,其中值的偏差在+/-0.1以内,所以预期的结果是:

res = {coords[0], coords[1]} //resp. the items of course.

两个列表都可以包含 1000 个条目,因此 Where/Contains 似乎不是一个好的选择。

【问题讨论】:

  • 范围内... -0.1 到 +0.1 :-)

标签: c# .net


【解决方案1】:

首先,您需要一个比较距离的函数。我将使用Vector3 而不是值元组,因为它更容易编写:

public bool IsAlmostEqual(Vector3 a, Vector3 b, float epsilon){
    return DistanceSquared(a, b) < (epsilon * epsilon)
}
public double DistanceSquared(Vector3 a, Vector3 b){
    var c = a - b;
    return c.x * c.x + c.y * c.y + c.z * c.z ;
}

如果您只想检查每个坐标的相应索引,您只需编写一个循环:

for(var i = 0; i < coords.Count; i++){
    if(IsAlmostEqual(coords[i], reference[i], 0.1){
        ...
    }
    else{
        ...
    }

如果您想检查坐标和参考位置的每个组合,您只需添加另一个循环。这不会很好地扩展,但是 1000 个项目只会导致内部循环的大约 500000 次迭代,我预计这需要大约一毫秒。

如果您需要处理更大的坐标集,您应该研究某种搜索结构,例如 KD 树。

【讨论】:

  • 可以只使用Math.Abs(c.x - r.x) &lt; 0.1 来提高性能,因为似乎不需要准确度。
  • @Creyke 是的,您可以使用该比较来代替盒子形而不是球形公差体积。但首选方法将取决于用例,我预计不会有巨大的性能差异。
  • @JAlex 我可能会遗漏一些东西,但是您提到的 2 的 squaredLength 似乎与我相同,我不需要匹配向量,只需将它们的 (sqrd) 长度与 (sqrd ) 阈值独立于向量的方向
  • @dba - 对不起,我误读了答案。继续。
  • 谢谢!我担心嵌套循环的性能,但事实证明这并不是真正的问题:-)
【解决方案2】:

我认为您正在寻找与目标点匹配的点列表中的匹配项。或者,您可以匹配列表中的所有点。

首先我创建了一个通用比较类ApproxEqual 来比较集合和元组。

接下来我迭代列表以在 ApproxFind() 中查找匹配项

参见下面的示例代码:

class Program
{
    static void Main(string[] args)
    {
        var actual_points = new List<(float, float, float)>()
        {
            (9.97f,10.02f,10),
            (15.01f,14.98f,15),
            (12.65f,18.69f,0),
            (10.03f,9.98f,10),
        };

        var target = (10f, 10f, 10f);
        var match = ApproxFind(actual_points, target, 0.1f);
        foreach (var item in match)
        {
            Console.WriteLine(item);
            // (9.97, 10.02, 10)
            // (10.03, 9.98, 10)
        }

        var targetAll = new[] { (10f, 10f, 10f), (15f, 15f, 15f) };
        var matchAll = ApproxFind(actual_points, targetAll , 0.1f);
        foreach (var item in matchAll)
        {
            Console.WriteLine(item);
            // (9.97, 10.02, 10)
            // (10.03, 9.98, 10)
            // (15.01, 14.98, 15)
        }
    }

    /// <summary>
    /// Find the items in <paramref name="list"/> that are near the <paramref name="target"/> by a tolerance <paramref name="tolerance"/>
    /// </summary>
    public static IEnumerable<(float, float, float)> ApproxFind(IEnumerable<(float, float, float)> list, (float, float, float) target, float tolerance)
    {
        var comp = new ApproxEqual(tolerance);
        foreach (var item in list)
        {
            if (comp.Compare(item, target) == 0)
            {
                yield return item;
            }
        }
    }
    /// <summary>
    /// Find the items in <paramref name="list"/> that are near any of the items in the <paramref name="targets"/> by a tolerance <paramref name="tolerance"/>
    /// </summary>
    public static IEnumerable<(float, float, float)> ApproxFind(IEnumerable<(float, float, float)> list, IEnumerable<(float, float, float)> targets, float tolerance)
    {
        var comp = new ApproxEqual(tolerance);
        foreach (var other in targets)
        {
            foreach (var item in list)
            {
                if (comp.Compare(item, other) == 0)
                {
                    yield return item;
                }
            }
        }
    }
}

/// <summary>
/// Implementation of approximate comparison for tuples and collections
/// </summary>
public class ApproxEqual : 
    IComparer<ICollection<float>>, 
    IComparer<ValueTuple<float,float,float>>,
    System.Collections.IComparer
{
    public ApproxEqual(float tolerance)
    {
        Tolerance = tolerance;
    }

    public float Tolerance { get; }

    int System.Collections.IComparer.Compare(object x, object y)
    {
        if (x is ICollection<float> x_arr && y is ICollection<float> y_arr)
        {
            return Compare(x_arr, y_arr);
        }
        if (x is ValueTuple<float, float, float> x_tuple && y is ValueTuple<float, float, float> y_tuple)
        {
            return Compare(x_tuple, y_tuple);
        }
        return -1;
    }

    public int Compare(ICollection<float> x, ICollection<float> y)
    {
        if (x.Count == y.Count)
        {
            foreach (var delta in x.Zip(y, (xi,yi)=>Math.Abs(xi-yi)))
            {
                if (delta > Tolerance) return -1;
            }
            return 0;
        }
        return -1;
    }

    public int Compare((float, float, float) x, (float, float, float) y)
    {
        if (Math.Abs(x.Item1 - y.Item1) > Tolerance) return -1;
        if (Math.Abs(x.Item2 - y.Item2) > Tolerance) return -1;
        if (Math.Abs(x.Item3 - y.Item3) > Tolerance) return -1;
        return 0;
    }
}

如果你想在ApproxEqual中实现IEqualityComparer那么添加以下接口

    IEqualityComparer<ICollection<float>>,
    IEqualityComparer<ValueTuple<float,float,float>>,
    System.Collections.IEqualityComparer

并用

实现它们
    bool System.Collections.IEqualityComparer.Equals(object x, object y)
    {
        if (x is ICollection<float> x_arr && y is ICollection<float> y_arr)
        {
            return Equals(x_arr, y_arr);
        }
        if (x is ValueTuple<float, float, float> x_tuple && y is ValueTuple<float, float, float> y_tuple)
        {
            return Equals(x_tuple, y_tuple);
        }
        return false;
    }
    public bool Equals(ICollection<float> x, ICollection<float> y)
    {
        if (x.Count == y.Count)
        {
            return !x.Zip(y, (xi, yi) => Math.Abs(xi - yi)).Any((delta) => delta > Tolerance);
        }
        return false;
    }


    public bool Equals((float, float, float) x, (float, float, float) y)
    {
        return Math.Abs(x.Item1 - y.Item1)<=Tolerance
            && Math.Abs(x.Item2 - y.Item2)<=Tolerance
            && Math.Abs(x.Item3 - y.Item3)<=Tolerance;
    }

    int System.Collections.IEqualityComparer.GetHashCode(object obj)
    {
        if (obj is ValueTuple<float, float, float> x_tuple)
        {
            return GetHashCode(x_tuple);
        }
        if (obj is ICollection<float> x_arr)
        {
            GetHashCode(x_arr);
        }
        return obj.GetHashCode();
    }
    public int GetHashCode(ICollection<float> obj)
    {
        var array = obj.ToArray();
        unchecked
        {
            int hc = 17;
            for (int i = 0; i < array.Length; i++)
            {
                hc = 23 * hc + array[i].GetHashCode();
            }
            return hc;
        }
    }
    public int GetHashCode((float, float, float) obj)
    {
        return obj.GetHashCode();
    }

最后,将ApproxFind() 方法更改为使用.Equals() 而不是.Compare()

    /// <summary>
    /// Find the items in <paramref name="list"/> that are near the <paramref name="target"/> by a tolerance <paramref name="tolerance"/>
    /// </summary>
    public static IEnumerable<(float, float, float)> ApproxFind(IEnumerable<(float, float, float)> list, (float, float, float) target, float tolerance)
    {
        var comp = new ApproxEqual(tolerance);
        foreach (var item in list)
        {
            if (comp.Equals(item, target))
            {
                yield return item;
            }
        }
    }
    /// <summary>
    /// Find the items in <paramref name="list"/> that are near any of the items in the <paramref name="targets"/> by a tolerance <paramref name="tolerance"/>
    /// </summary>
    public static IEnumerable<(float, float, float)> ApproxFind(IEnumerable<(float, float, float)> list, IEnumerable<(float, float, float)> targets, float tolerance)
    {
        var comp = new ApproxEqual(tolerance);
        foreach (var other in targets)
        {
            foreach (var item in list)
            {
                if (comp.Equals(item, other) )
                {
                    yield return item;
                }
            }
        }
    }

PS。我正在使用浮点数,因为我想使用 System.Numerics 并坚持使用它。也很容易将上述转换为支持double

【讨论】:

  • 我建议使用IEqualityComparer 接口而不是IComparerComparison sort 需要传递性,而在使用这样的容差时会违反这一点。即如果a = b &amp;&amp; b = c 那么它应该遵循a = c
  • @JonasH - 问题在于两个大致相等的对象将具有不同的哈希码,因此就 CLR 而言假定不相等。
  • 好点。也许最好跳过接口并使用Func&lt;(float, float, float), (float, float, float), bool&gt;,或者只是一个常规的静态方法。
  • @JonasH - 我选择IComparer 的原因是,在单元测试期间,CollectionAssert.AreEqual() 方法需要一个IComparer 参数来进行相等性检查。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-21
  • 1970-01-01
  • 2016-09-11
  • 1970-01-01
相关资源
最近更新 更多