Блог пользователя Hohol

Автор Hohol, 12 лет назад, По-русски

Нужен аналог std::set::lower_bound/upper_bound в C#. Например, для решения чего-нибудь такого: timus 1414

Погуглив, не нашел аналога, работающего за логарифм. SortedSet — ближайший родственник std::set — ничего подобного не умеет. Как будто бы остается только самому дерево писать.

Но на всякий случай спрошу еще здесь: кто-нибудь знает, можно ли это сделать?

  • Проголосовать: нравится
  • +5
  • Проголосовать: не нравится

»
12 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

А можно развернуть, что требуется? Чем вам List.BinarySearch не подойдёт?

  • »
    »
    12 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +1 Проголосовать: не нравится

    Нужно поддерживать еще и добавление в множество новых элементов онлайн.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Ну строго говоря, можно костыльно учитывать добавление-удаление :)

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        Ну а надо не костыльно, а за логарифм.

»
12 лет назад, # |
  Проголосовать: нравится +14 Проголосовать: не нравится

У SortedSet есть метод GetViewBetween. Это именно то, что нужно.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Проверил — что-то очень быстро при увеличении N время работы растет. Оно точно за логарифм работает?

    • »
      »
      »
      12 лет назад, # ^ |
      Rev. 9   Проголосовать: нравится +10 Проголосовать: не нравится

      Я его декомпилировал Рефлектором в свое время. Насколько я вижу — вроде логарифмический.

      internal SortedSet<T>.Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive)
      {
        SortedSet<T>.Node node = this.root;
        while (node != null)
        {
          if (lowerBoundActive && this.comparer.Compare(from, node.Item) > 0)
          {
            node = node.Right;
          }
          else
          {
            if (!upperBoundActive || this.comparer.Compare(to, node.Item) >= 0)
              return node;
            node = node.Left;
          }
        }
        return (SortedSet<T>.Node) null;
      }
      

      Правда, там еще потом какой-то VersionCheckImpl() делается...


      UPD1: В общем, вот весь остальной код.

      public virtual SortedSet<T> GetViewBetween(T lowerValue, T upperValue)
      {
        if (this.Comparer.Compare(lowerValue, upperValue) > 0)
          throw new ArgumentException("lowerBound is greater than upperBound");
        else
          return (SortedSet<T>) new SortedSet<T>.TreeSubSet(this, lowerValue, upperValue, true, true);
      }
      
      public TreeSubSet(SortedSet<T> Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive)
          : base(Underlying.Comparer)
      {
          this.underlying = Underlying;
          this.min = Min;
          this.max = Max;
          this.lBoundActive = lowerBoundActive;
          this.uBoundActive = upperBoundActive;
          this.root = this.underlying.FindRange(this.min, this.max, this.lBoundActive, this.uBoundActive);
          this.count = 0;
          this.version = -1;
          this.VersionCheckImpl();
      }
      
      private void VersionCheckImpl()
      {
          if (this.version == this.underlying.version)
            return;
          this.root = this.underlying.FindRange(this.min, this.max, this.lBoundActive, this.uBoundActive);
          this.version = this.underlying.version;
          this.count = 0;
          base.InOrderTreeWalk((TreeWalkPredicate<T>) (n =>
          {
            SortedSet<T>.TreeSubSet temp_31 = this;
            int temp_34 = temp_31.count + 1;
            temp_31.count = temp_34;
            return true;
          }));
      }
      

      UPD2: Судя по тому, что он зачем-то так по-дурацки считает количество вершин в получившемся поддереве — таки линейный... Брр, неужели?


      UPD3: Вот этот код работает за 32 секунды. Отвратительно.

      var s = new SortedSet<int>();
      int n = 100000;
      var rand = new Random(13051991);
      int sum = 0;
      for (int i = 0; i < n; ++i) {
          s.Add(rand.Next());
          if (rand.Next() % 2 == 0) {
              int l = rand.Next(int.MaxValue / 2 - 10);
              int r = l + rand.Next(int.MaxValue / 2 - 10);
              var t = s.GetViewBetween(l, r);
              sum += t.Min;
          }
      }
      Console.WriteLine(sum);
      
»
12 лет назад, # |
  Проголосовать: нравится -22 Проголосовать: не нравится

Можно написать сжатый бор и сдать 1414. Не гарантирую, конечно, что сжатый бор пишется сильно быстрее собственного set'a, но и такой метод имеется. :)

»
12 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Скорее всего, можно так сделать, правда это долго пишется. Затем создаем SortedSet, указав в качестве параметра экземпляр класса MyComparer, теперь чтобы найти lowerbound x, делаем Reset(), затем SortedSet.Contains(x), и нужно значение лежит в поле int? LowerBound.

»
12 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

Понимаю, что тема старая, но раз уж её подняли... Майкрософт об этом просят ещё с 2008-го года: http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/6776d660-ffa2-4174-8b91-a725c5265d2c/

Но чёт не видно подвижек пока что. Сам об этой особенности знаю, поэтому видя задачки, требующие этих вот bound-ов матерясь и кряхтя пересаживаюсь на c++, например из недавнего в задаче про антисоциальных пассажиров поезда из отборочного Russian Code Cup пришлось.