Как бы я искать диапазон значений с использованием C # - PullRequest
5 голосов
/ 20 января 2009

У меня есть список значений, подобных этому

1000, 20400
22200, 24444

Диапазоны не перекрываются.

Что я хочу сделать, так это иметь функцию ac #, которая может хранить (загруженные значения из базы данных, затем кэшировать ее локально) относительно большой список этих значений, а затем иметь метод для определения, находится ли указанное значение в каком-либо из диапазонов?

Имеет ли это смысл?

Нужно самое быстрое решение

Ответы [ 8 ]

11 голосов
/ 20 января 2009

Вы указали значения, но затем поговорили о диапазонах.

Для просто значений я бы использовал HashSet<int>. Для диапазонов все становится сложнее ... Дайте нам знать, если это действительно то, что вам нужно, и я подумаю об этом подробнее. Если они являются диапазонами , у вас есть дополнительная информация о них? Вы знаете, будут ли они перекрываться или нет? Вы просто заинтересованы в существовании диапазона или вам нужно найти все диапазоны, которым принадлежит значение?

РЕДАКТИРОВАТЬ: С изменениями в вопросе ответ Барри является совершенно правильным. Просто отсортируйте (по нижней границе достаточно) во время инициализации, а затем выполните двоичный поиск, чтобы найти диапазон, содержащий значение или его отсутствие.

РЕДАКТИРОВАТЬ: Я нашел код ниже в мой ответ на аналогичный вопрос недавно.

Диапазоны нужно предварительно отсортировать - 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;
}
5 голосов
/ 20 января 2009

Бинарный поиск подойдет. Храните список диапазонов в отсортированном порядке, убедившись, что ни один из них не пересекается (если они это делают, объедините их). Затем запишите бинарный поиск, который, вместо того, чтобы проверять одно значение, проверяет любой конец диапазона при выборе варианта выше или ниже.

4 голосов
/ 20 января 2009

Сначала я попробую самый простой вариант и оптимизирую, если он не соответствует вашим потребностям.

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

List<Range>.FirstOrDefault(r => i >= r.Lower && i <= r.Upper);
2 голосов
/ 28 октября 2016

Как упоминалось ранее, если набор диапазонов большой и не перекрывается, лучше всего выполнить бинарный поиск. Один из способов сделать это - использовать 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, с другой стороны, использует GetHashCode и Equals для сравнения значений.

1 голос
/ 20 января 2009
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 и построить массивы из этого.

0 голосов
/ 20 января 2009
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;
   }
}
0 голосов
/ 20 января 2009

Это функционально, что вы после? Если это так, и вы просто хотите, чтобы он был более производительным, чем изменить foreach в ValueRangeCollection на бинарный поиск ..

    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));
        }
    }
0 голосов
/ 20 января 2009

Если ваши диапазоны не перекрываются:

-> Поместить все ваши диапазоны в массив.

-> Сортировать ваш массив.

-> Также сохраните HashSet для ваших начальных значений.

-> Теперь выполните бинарный поиск по вашему номеру. Две возможности:

-> Диапазон массива слева от (меньшего) вашего числа является начальным значением: ваш номер находится в диапазоне.

-> Диапазон массива слева от (меньшего) вашего номера не является начальным значением: ваш номер не находится в диапазоне.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...