【问题标题】:how can i do binary search on an array of structs w.r.t a particular field value in c#?如何在 c# 中对特定字段值的结构数组进行二进制搜索?
【发布时间】:2012-01-09 14:43:27
【问题描述】:

我有一个结构数组,其中结构有三个整数字段。它按其中一个字段排序,比如 F,我想要一种方法对该字段进行二进制搜索,即返回索引的 binarySearch(mystruct[] myarray, int val) 形式的函数字段 F = val 的结构。我知道有一个现有的 Array.BinarySearch(T[] array, T value) 函数,但它只允许搜索与数组中的类型相同的类型 T。这意味着如果我想搜索一个值,我需要创建一个新结构并将字段 F 设置为该值,以便我可以将它传递给这个函数。我认为不会有显着的性能开销,但看起来很难看。我能想到的另一种方法是自己实现该功能,但是当存在如此相似的东西时,这似乎也不优雅。有什么更好的方法或首选哪种方法的建议?

【问题讨论】:

    标签: c# arrays struct binary-search


    【解决方案1】:

    您可以为您的结构实现 IComparable<T> 以在字段 (F) 上进行比较,或者您可以为您的结构创建一个 IComparer<>,它将基于该字段进行比较并将其传递给 Array.BinarySearch()

    所以要么:

    // using IComparable<T>
    public struct MyStruct : IComparable<MyStruct>
    {
        public int F { get; set; }
    
        // other fields that should not affect "search"
        public int X { get; set; }
    
        public int CompareTo(MyStruct other)
        {
            return F.CompareTo(other.F);
        }
    }
    

    可以这样称呼:

    MyStruct target = new MyStruct { F = 13 };
    
    Array.BinarySearch(arrayOfMyStruct, target);
    

    或者单独的IComparer&lt;MyStruct&gt;:

    public struct MyStruct 
    {
        public int F { get; set; }
    
        // other non-sort/search affecting properties
        public int X { get; set; }
    }
    
    public struct MyStructComparer : IComparer<MyStruct>
    {
        public int Compare(MyStruct x, MyStruct y)
        {
            return x.F.CompareTo(y.F);
        }
    }
    

    可以这样称呼:

    MyStruct target { F = 13; }
    
    Array.BinarySearch(myArrayOfStruct, target, new MyStructComparer());
    

    第一个代码较少,但它将排序与类型强烈耦合,如果您想根据情况更改排序(即允许多个排序顺序),这并不理想。后者提供了更大的灵活性,您可以独立于结构提供多个不同的顺序,但它确实需要一个额外的类。

    更新

    如果你不想创建一个 dummy 结构来比较,你可以实现 IComparable 像:

    public struct MyStruct : IComparable
    {
        public int F { get; set; }
    
        // other non-sort/search affecting properties
        public int X { get; set; }
    
        public int CompareTo(object other)
        {
            // if the type is NOT an int, you can decide whether you'd prefer
            // to throw, but the concept of comparing the struct to something
            // unknown shouldn't return a value, should probably throw.
            return F.CompareTo((int)other);
        }
    }
    

    可以这样称呼:

    Array.BinarySearch(arrayOfMyStruct, 13);
    

    但是,这再次将您的类的实现与给定的比较类型紧密联系在一起,我认为这比使用虚拟搜索目标更难看,但这是我个人的偏好。就个人而言,尤其是在初始化语法尽可能短的情况下,我更喜欢使用虚拟目标:

    var target = new MyStruct { F = 13 };
    

    更新:您可以同时支持intMyStruct 比较,但它很快就会变得混乱,这就是为什么我个人再次建议使用虚拟结构来避免头痛:

    // implement IComparable<int> for the int search (w/o dummy), and IComparable<MyStruct> for sort
    public struct MyStruct : IComparable, IComparable<MyStruct>, IComparable<int>
    {
        public int F { get; set; }
    
        // other non-sort/search affecting properties
        public int X { get; set; }
    
        public int CompareTo(object other)
        {
            if (other is int) 
                return F.CompareTo((int)other);
    
            if (other is MyStruct)
                return F.CompareTo((MyStruct)other);
    
            throw new InvalidOperationException("Other must be int or MyStruct.");
        }
    
        public int CompareTo(MyStruct other)
        {
            return F.CompareTo(other.F);
        }
    
        public int CompareTo(int other)
        {
            return F.CompareTo(other);
        }
    }
    

    【讨论】:

    • 我几乎是这样回答的,但后来我重读了这个问题。他/她不想创建一个新的结构来比较。
    • @kendfrey:是的,OP 不想这样做,但它是一个更清洁的解决方案。为了完整起见,我加入了IComparable,但对我来说这更丑陋,因为它将数据与特定的排序概念紧密联系在一起。
    • @kendfrey,詹姆斯,乔恩。谢谢你们的回应。不是我不想做某事,而是我在问你认为最优雅的方式是什么。我猜 James 和 Jon 认为虚拟结构是最好的。 Kendfrey,您的解决方案在没有虚拟结构的情况下看起来不错 - 但您更喜欢虚拟结构吗?
    • @Sanorita:对我来说,不使用虚拟结构的主要问题是你将一个比较与类联系起来,这使得它变得脆弱。如果你也想实现IComparable&lt;MyStruct&gt;,对于排序,它开始变得很奇怪,因为IComparable&lt;MyStruct&gt;IComparable 会期望不同的目标(更新上面的例子来说明我的意思)。您可以在ComapreTo(object other) 中进行一些类型检查并切换到给定上下文的适当调用,但它开始变得非常复杂。这就是我个人说保持简单并使用虚拟结构的主要原因。
    【解决方案2】:

    如果你的结构实现了 IComparable,你可以使用:

    // myValue is an the value of the field to compare to
    Array.BinarySearch(myArray, myValue);
    

    http://msdn.microsoft.com/en-us/library/y15ef976.aspx中所述

    您可以使用 IComparable 将结构与对象进行比较,因此您可以传入新结构的值。在您的 CompareTo 实现中,您可以将任何值与字段值进行比较,从而允许您说“我的结构应该被认为大于/小于这个数字”。

    编辑:

    以下是您的结构的 CompareTo 示例:

    public int CompareTo(object obj)
    {
        if (obj is int)
        {
            return myIntField.CompareTo((int)obj);
        }
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      一种方法是创建一个自定义IComparer&lt;T&gt;,它仅根据该字段的值比较结构的实例并将其传递给BinarySearchthis overload(您还需要创建一个“虚拟”结构实例进行比较)。这可能是最纯粹的解决方案。

      但是,实际上,您可以使用 LINQ 投影到字段值的集合中并对其进行二分搜索;结果索引将与您搜索结构集合本身相同。例如:

      var structs = new MyStruct[n];
      var index = structs.Select(i => i.F).ToList().BinarySearch(42);
      

      在上面的代码中,F 是字段的名称,42 是您要搜索的值(其类型将是 F 的类型)。这不会那么快,但您不需要编写任何代码,而且速度很可能与您的情况无关。

      更新:澄清一下:显然,由于投影操作,上面的代码将是 O(n),因此在像这样投影后使用二进制搜索一次是愚蠢的(您可以简单地进行线性搜索反而)。但是,如果您打算进行多次搜索,那么它可能会开始有意义。

      我绝对不建议在您的结构中覆盖Equals,除非实例之间的比较旨在简化为比较F 成员应用程序中的所有位置

      【讨论】:

      • @downvoter:请留下评论帮助我改进这个答案。
      • 忘记 IComparer。 OP 不想制作一个虚拟结构。
      猜你喜欢
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2022-01-22
      • 2013-01-29
      • 2019-11-19
      • 1970-01-01
      • 1970-01-01
      • 2013-09-29
      相关资源
      最近更新 更多