如何使用C#搜索一系列范围值

时间:2009-01-20 14:13:10

标签: c# algorithm

我有一个像这样的值列表

1000, 20400
22200, 24444

范围不重叠。

我想要做的是拥有可以存储的ac#函数(从db加载值然后在本地缓存它)这些值的相对较大的列表然后有一个方法来查找提供的值是否在任何范围内?

这有意义吗?

需要最快的解决方案

8 个答案:

答案 0 :(得分:11)

您已指定了值,但随后讨论了范围。

对于正好值,我会使用HashSet<int>。对于范围来说它变得更加复杂......让我们知道这实际上是你所追求的,我会更多地考虑它。如果他们范围,您是否有任何关于它们的额外信息?你知道他们是否会重叠吗?您是否只对范围的存在感兴趣,还是需要找到值所属的所有范围?

编辑:通过对问题的编辑,巴里的答案是完全正确的。只需在初始化时排序(通过下限足够好)然后进行二分查找以找到包含该值或缺少该值的范围。

编辑:我最近在my answer to a similar question找到了以下代码。

范围需要事先进行排序 - 假设您没有重叠,List<Range>.Sort将正常工作。

public class Range : IComparable<Range>
{
      private readonly int bottom; // Add properties for these if you want
      private readonly int top;

      public Range(int bottom, int top)
      {
             this.bottom = bottom;
             this.top = top;
      }

      public int CompareTo(Range other)
      {
             if (bottom < other.bottom && top < other.top)
             {
                   return -1;
             }
             if (bottom > other.bottom && top > other.top)
             {
                   return 1;
             }
             if (bottom == other.bottom && top == other.top)
             {
                   return 0;
             }
             throw new ArgumentException("Incomparable values (overlapping)");
      }

      /// <summary>
      /// Returns 0 if value is in the specified range;
      /// less than 0 if value is above the range;
      /// greater than 0 if value is below the range.
      /// </summary>
      public int CompareTo(int value)
      {
             if (value < bottom)
             {
                   return 1;
             }
             if (value > top)
             {
                   return -1;
             }
             return 0;
      }
}

// Just an existence search
public static bool BinarySearch(IList<Range> ranges, int value)
{
    int min = 0;
    int max = ranges.Count-1;

    while (min <= max)
    {
        int mid = (min + max) / 2;
        int comparison = ranges[mid].CompareTo(value);
        if (comparison == 0)
        {
            return true;
        }
        if (comparison < 0)
        {
            min = mid+1;
        }
        else if (comparison > 0)
        {
            max = mid-1;
        }
    }
    return false;
}

答案 1 :(得分:5)

二进制搜索会很好。按排序顺序保留范围列表,确保它们都不相交(如果相同,则合并它们)。然后编写一个二进制搜索,而不是针对单个值进行测试,在选择上方或下方时,针对范围的任一端进行测试。

答案 2 :(得分:4)

我首先尝试最简单的选项,如果不能满足您的需求,请进行优化。

class Range {
   int Lower { get; set; }
   int Upper { get; set; }
}

List<Range>.FirstOrDefault(r => i >= r.Lower && i <= r.Upper);

答案 3 :(得分:2)

如前所述,如果范围集很大且不重叠,则最好进行二分搜索。一种方法是使用SortedDictionary,它实现了一个红黑树,以提供O(log(n))搜索时间。我们可以使用范围作为键,并通过将我们想要匹配的单个值转换为单个点的范围来执行字典查找。如果我们实现CompareTo方法,以便重叠的范围被视为相等/匹配,则字典查找将找到匹配的范围以供使用。

public struct Range : IComparable<Range>
{
    public int From;
    public int To;

    public Range(int point)
    {
        From = point;
        To = point;
    }

    public Range(int from, int to)
    {
        From = from;
        To = to;
    }

    public int CompareTo(Range other)
    {
        // If the ranges are overlapping, they are considered equal/matching
        if (From <= other.To && To >= other.From)
        {
            return 0;
        }

        // Since the ranges are not overlapping, we can compare either end
        return From.CompareTo(other.From);
    }
}

public class RangeDictionary
{
    private static SortedDictionary<Range, string> _ranges = new SortedDictionary<Range, string>();

    public RangeDictionary()
    {
        _ranges.Add(new Range(1, 1000), "Alice");
        _ranges.Add(new Range(1001, 2000), "Bob");
        _ranges.Add(new Range(2001, 3000), "Carol");
    }

    public string Lookup(int key)
    {
        /* We convert the value we want to lookup into a range,
         * so it can be compared with the other ranges */
        var keyAsRange = new Range(key);
        string value;
        if (_ranges.TryGetValue(keyAsRange, out value))
        {
            return value;
        }
        return null;
    }
}

例如,运行以下代码

var ranges = new RangeDictionary();
var value = ranges.Lookup(1356);
在这种情况下,

value将包含字符串"Bob",因为1356匹配范围1001-2000。

在您的情况下,如果您有兴趣获取范围本身,则可以将范围用作字典中的键和值。示例代码可以很容易地扩展为保存通用值。

作为旁注,这个技巧也可以使用SortedList使用几乎相同的代码来完成,它使用更少的内存(数组而不是树),但是对于未排序的数据,插入/删除时间较慢。它们都使用密钥类型(或指定的)的默认比较器来比较值。另一方面,正常的C#Dictionary使用GetHashCodeEquals来比较值。

答案 4 :(得分:1)

class Ranges
{
    int[] starts = new[] { 1000, 22200 };
    int[] ends = new[] { 20400, 24444 };

    public int RangeIndex(int test)
    {
        int index = -1;

        if (test >= starts[0] && test <= ends[ends.Length - 1])
        {
            index = Array.BinarySearch(ends, test);

            if (index <= 0)
            {
                index = ~index;
                if (starts[index] > test) index = -1;
            }
        }

        return index;
    }
}

显然,你如何实例化这个课取决于你。也许传入DataTable并从中构造数组。

答案 5 :(得分:0)

假设您的范围不重叠:

  

- &GT;将所有范围编号放在一个数组中。

     

- &GT;对数组进行排序。

     

- &GT;还要为起始值保留一个HashSet。

     

- &GT;现在对你的号码进行二元搜索。两种可能性:

     

- &GT;数组范围左(小于)你的数字是一个起始值:你的   数字在范围内。

     

- &GT;数组范围左(小于)您的数字不是起始值:您的   号码不在范围内。

答案 6 :(得分:0)

这在功能上是你追求的吗?如果是这样,你只是希望它更高效,而不是将ValueRangeCollection中的foreach更改为二进制搜索..

    public struct ValueRange 
    { 
       public int LowVal; 
       public int HiVal; 
       public bool Contains (int CandidateValue) 
       { return CandidateValue >= LowVal && CandidateValue <= HiVal; } 
       public ValueRange(int loVal, int hiVal)
       {
          LowVal = loVal;
          HiVal = hiVal;
       }
   }

    public class ValueRangeCollection: SortedList<int, ValueRange> 
    { 
        public bool Contains(int candValue) 
        {  
            foreach ( ValueRange valRng in Values)
                if (valRng.Contains(candValue)) return true;
            return false; 
        }
        public void Add(int loValue, int hiValue)
        {
            Add(loValue, new ValueRange(loValue, hiValue));
        }
    }

答案 7 :(得分:0)

class Range
{
   public int Start { get; set; }
   public int End { get; set; }

   static Dictionary<int, Range> values;
   static int[] arrToBinarySearchIn;
   public static void BuildRanges(IEnumerable<Range> ranges) { 
        values = new Dictionary<int, Range>();
        foreach (var item in ranges)
            values[item.Start] = item;
        arrToBinarySearchIn = values.Keys.ToArray();
        Array.Sort(arrToBinarySearchIn);
   }
   public static Range GetRange(int value)
   {
       int searchIndex = Array.BinarySearch(arrToBinarySearchIn, value);
       if (searchIndex < 0)
           searchIndex = ~searchIndex - 1;
       if (searchIndex < 0)
           return null;
       Range proposedRange = values[arrToBinarySearchIn[searchIndex]];
       if (proposedRange.End >= value)
           return proposedRange;
       return null;
   }
}