【问题标题】:lower_bound == upper_bound下界 == 上界
【发布时间】:2023-03-23 16:28:01
【问题描述】:

lower_bound 是什么意思。如果我不得不猜测,我会回答这个函数在最后一个小于请求值的元素处返回迭代器。但我看到lower_bound 几乎和upper_bound 一样。唯一的区别是在 upper_bound 的情况下严格不等式。 stl中是否有真正的下限选择函数与下限的正常定义一致。

编辑:文档中有太多的否定让我感到困惑。问题是我得到了相同的迭代器。我通过从 lower_bound 返回值中减去 1 来解决它。我用它来插值:

    float operator()(double f)
        {
        SpectrumPoint* l=std::lower_bound(beginGet(),endGet(),(SpectrumPoint){float(f),0.0f}
            ,SpectrumPoint::CompareFreqLessThan);
        if(l>beginGet())
            {--l;}

        SpectrumPoint* u=std::lower_bound(beginGet(),endGet(),(SpectrumPoint){float(f),0.0f}
            ,SpectrumPoint::CompareFreqLessThan);

        if(u==endGet())
            {u=beginGet();}

        if(l==u)
            {
            if(u==endGet())
                {return u->amp;}
            return l->amp;
            }

        double f_min=l->freq;
        double A_min=l->amp;
        double f_max=u->freq;
        double A_max=u->amp;

        double delta_f=f_max-f_min;
        double delta_A=A_max-A_min;

        return A_min + delta_A*(f-f_min)/delta_f;
        }

我很抱歉造成这种混乱:-(

【问题讨论】:

  • 你在问为什么它返回迭代器 after 边界(保证存在,由于使用半开范围的 C++ 约定),而不是迭代器before 边界(不保证存在)?或者你对界限应该在哪里有其他想法?
  • 我不太明白这不是一个真正的问题。同意没有问号,但文本中似乎存在一些有效的担忧。
  • 是的,名称不正确。一个更好的名字是least_upper_bound,但这可能会让大多数没有数学头脑的人感到困惑。数学上正确的lower_bound 函数模板必须返回一个反向迭代器。这将远不如当前将前向迭代器返回到最小上限的方案有用。该函数的名称在技术上不正确,但仍然有意义。
  • 请为这个问题付出一些努力:给它一个meaningful title,并编辑正文以将问题表达为正确的英语(包括问号!)。这个问题当然有道理,但糟糕的表述可能会让人们望而却步。
  • 我觉得这个标题很有用。我非常困惑并在脑海中重复标题(但带有问号)。

标签: c++ math stl naming-conventions


【解决方案1】:
  • 下限:第一个大于或等于的元素。

  • 上限:严格大于的第一个元素。

示例:

+- lb(2) == ub(2)       +- lb(6)        +- lb(8)
|        == begin()     |  == ub(6)     |   +- ub(8) == end()
V                       V               V   V
+---+---+---+---+---+---+---+---+---+---+---+
| 3 | 4 | 4 | 4 | 4 | 5 | 7 | 7 | 7 | 7 | 8 |
+---+---+---+---+---+---+---+---+---+---+---+
    ^               ^                       ^
    |               |                       |
    +- lb(4)        +- ub(4)                +- lb(9) == ub(9) == end()

    |- eq-range(4) -|

如您所见,n 的半开等距是 [lb(n), ub(n)) .

请注意,这两个边界都为您提供了所需值的元素的有意义的插入位置,以便保持顺序,但lower_bound 具有如果元素已经存在的显着特征,那么您获得一个实际指向该元素的迭代器。因此,您可以在有序范围内使用lower_bound 来实现您自己的唯一成员身份多成员身份容器。

void insert(Container & c, T const & t)
{
    auto it = std::lower_bound(c.begin(), c.end(), t);

    // if unique container:
    if (it != c.end() && *it == t) { /* error, element exists! */ return; }

    c.insert(it, t);
}

【讨论】:

  • 我突然意识到lower_bound 使用了顺序比较运算符(如<),因此存在条件应该重写为!(t < *it),以便不需要不必要的约束。
  • 没有“中间”指针/迭代器。图表中的箭头应指向实际元素,而不是它们之间。
  • @AmokHuginnsson:箭头指向相应迭代器将取消引用的元素的开头。这样我就不需要发明一个虚构的过去的元素:-)
  • @KerrekSB:嗯,IMO,添加过去的结束元素比丢失重要信息更好,lower_boundupper_bound 返回指向某些 实际 的迭代器元素。此外,对于简单的ints 的集合,“元素的开头”是什么意思? - 给定int 的一些开头? int 的第一位?或者整个int?对于任何复杂类型的集合,比如std::string,lb 或 ub 返回的迭代器指向字符串的第一个字符还是指向整个字符串?我要说的是 - 你的图表可能会产生误导。
  • 将迭代器视为插入新元素的有效位置,将迭代器指向元素之间或更好地指向所描绘的元素前面是很有意义的。
【解决方案2】:

它在最后一个小于请求值的元素之后返回迭代器。这作为插入位置很有用(这就是函数返回该迭代器的原因)。半开范围first, lower_bound(first, last, value) 指定所有小于value 的值也很有用。

upper_bound 返回最后一个元素之后的迭代器[小于或等于/不大于]所要求的值。或者严格来说:值不小于的最后一个元素,因为这两种算法都专门处理小于比较器。

如果您希望迭代器在lower_bound 返回的迭代器之前,您可以减去 1(对于随机访问迭代器)、递减(对于双向迭代器)或进行线性搜索,而不是使用 lower_bound(对于一个前向迭代器,它们都不是)。

当心没有元素小于所要求的值的边缘情况,在这种情况下你不能得到你想要的,因为它不存在。 lower_bound 在这种情况下当然会返回范围的开头,因此不需要特殊情况的返回值。

【讨论】:

  • "lower_bound 在这种情况下当然返回范围的开头" std::lower_bound 返回 end 如果元素不存在,则为范围。 (std::upper_bound 也一样)。
  • @Nawaz 我认为他的意思是lower_bound == begin()
【解决方案3】:

由于已重新打开,我将尝试将我的评论作为答案。

名称lower_bound 在数学上不正确。一个更好的名字可能是least_upper_bound,但这可能会让大多数没有数学头脑的人感到困惑。 (然后你怎么称呼upper_boundalmost_least_upper_bound?耶!)

我的建议:克服名称lower_boundupper_bound 在技术上不正确的事实。定义的两个函数非常有用。将这些函数视为对符号的有用滥用。

要生成符合 C++ 迭代器概念的数学上正确的 lower_bound 函数,该函数必须返回反向迭代器而不是正向迭代器。返回一个反向迭代器不如lower_boundupper_bound 所采用的方法有用,而且返回一个反向迭代器的概念违背了并非所有容器都是可逆的事实。

为什么是反向迭代器?正如不能保证容器中存在上限一样,同样不能保证会存在下限。现有的lower_boundupper_bound 返回end() 以指示搜索到的值超出范围。真正的下限需要返回 rend() 以指示搜索到的值是超出范围的低值。

有一种方法可以以前向迭代器的形式实现真正的下限,但其代价是滥用end() 的含义来表示“没有下限”。这种滥用符号的问题在于,该函数的某些用户可能会执行与true_lower_bound(off_scale_low_search_value)-1 等效的操作,瞧! one 有一个指向集合中最大元素的指针。

也就是说,这是如何做到的。如果容器为空或搜索的值小于容器中的第一个值,则让真正的下限函数返回 end()。否则返回upper_bound()-1

【讨论】:

  • 我不明白为什么名称 lower_boundupper_bound 一定不正确。对我来说含义很清楚:对于任何值 x,它们给出了可以插入 x 的范围的下限和上限,并且不会破坏给定范围的顺序。或者,[begin, lower_bound) 中的所有值都小于 x,[lower_bound, upper_bound) 中的值都等于 x,并且 [upper_bound, end) 中的值都大于 x。
  • 全套好名字是用 Java 实现的 - {ceiling,floor,lower,higher}Entry() 和另一个,但仍然很好,在 C# 的 C5 库中 - [Weak]{Predecessor,接班人}。但他们致力于实际的地图搜索本身,而不是围绕抽象序列的抽象数学迭代概念。
  • @musiphil 我通过回答解释了原因。 stackoverflow.com/a/56770542/893406
【解决方案4】:

lower_boundupper_boundequal_range 是按排序顺序执行二进制搜索的函数。对三个函数的需求来自于元素可能在序列中重复的事实:

1, 2, 3, 4, 4, 4, 5, 6, 7

在这种情况下,当搜索值 4 时,lower_bound 将返回一个迭代器,该迭代器指向三个值为 4 的元素中的第一个,upper_bound 将返回一个指向值为 5 的元素的迭代器,而@ 987654327@ 将返回一个包含这两个迭代器的对。

【讨论】:

    【解决方案5】:

    根据 David Hammen 的回答,我试图解释为什么我们经常觉得 lower_bound/upper_bound 的名称不正确,或者至少不直观。

    这是因为我们正在寻找一个低于查询的元素。 我做了一张图和一个用例:

    代码:

    template< typename T, typename U >
    auto infimum(std::map<T,U> const& ctr, T query)
    {
        auto it = ctr.upper_bound(query);
        return it == ctr.begin() ? ctr.cend() : --it;
    }
    
    template< typename T, typename U >
    bool is_in_interval(std::map<T,U> const& ctr, T query)
    {
        auto inf = infimum(ctr, query);
        return inf == ctr.end() ? false : query <= inf->second;
    }
    

    https://ideone.com/jM8pt3

    基本上要获得“灰色箭头”的行为,我们需要upper_bound - 1,这就是它奇怪的原因。

    让我稍微改一下: 从名称lower_bound 我们本能地期望returns-first-immediately-inferior-element(如灰色箭头)。但是我们得到returns-first-immediately-superior-element for lower_bound;和first-immediately-strictly-superior-element 为上限。这就是令人惊讶的地方。

    您使用 稀疏序列 的假设令人惊讶,就像我在上图中的思想实验一样。但是,当您以密集序列中的“equal_range 的边界”来考虑它时,它会非常有意义,其中充满了高原,就像美丽的 Kerrek SB 描绘的那样。

    测试代码:

    #include <map>
    #include <cassert>
    #include <iostream>
    
    // .. paste infimum and is_in_interval here
    
    int main()
    {
        using std::cout;
        using Map = std::map<int,int>;
        Map intervals{{2,5}, {8,9}};
    
        auto red = infimum(intervals, 4);
        assert(red->first == 2);
        cout << "red->first " << red->first << "\n";
    
        auto green = infimum(intervals, 6);
        assert(green->first == 2);
        cout << "green->first " << green->first << "\n";
    
        auto pink = infimum(intervals, 8);
        assert(pink->first == 8);
        cout << "pink->first " << pink->first << "\n";
    
        auto yellow = infimum(intervals, 1);
        assert(yellow == intervals.cend());
    
        auto larger_than_all = infimum(intervals, 15);
        assert(larger_than_all->first == 8);
    
        bool red_is = is_in_interval(intervals, 4);
        cout << "red is in " << red_is << "\n";
    
        bool green_is = is_in_interval(intervals, 6);
        cout << "green is in " << green_is << "\n";
    
        bool pink_is = is_in_interval(intervals, 8);
        cout << "pink is in " << pink_is << "\n";
    
        bool yellow_is = is_in_interval(intervals, 1);
        cout << "yellow is in " << yellow_is << "\n";
    }
    

    结果

    red->first 2
    green->first 2
    pink->first 8
    red is in 1
    green is in 0
    pink is in 1
    yellow is in 0
    

    看起来不错。

    所以当然实用函数不是很好,它们应该使用范围 API 设计,因此我们可以使用任何集合或子范围,或反向迭代器,或过滤视图等等。当我们拥有 C++20 时,我们可以做到这一点。与此同时,我刚刚制作了一个简单的教育性地图绘制 API。

    玩它:
    https://ideone.com/jM8pt3

    【讨论】:

    • 我不确定我是否理解。首先,为什么要使用std::map&lt;int, int&gt; 来表示一组间隔(我认为应该类似于std::set&lt;std::pair&lt;int, int&gt;&gt;)? intervals中的上端点5和9有什么意义吗? infimum(c, q) 应该是什么意思?我了解数学中的集合的下确界,但是对于集合的下确界和查询点是否有一个被广泛接受的定义?
    • @musiphil 所有听起来都很棒的问题; pair 的东西提供了我在那个特定示例中不需要的更多灵活性,没有充分的理由。是的,5 和 9 很重要,我正在测试“0,1,inf”。这是一种重复证明,5 离 2 足够远,可以测试“inf”,而 9 离 8 仅 1,所以它测试“1”。所以确实函数infimum 是基于数学定义,但松散。我认为将其与“lower_bound”术语分开就足以解决 Hammem 指出的问题。
    【解决方案6】:

    lower_boundupper_bound 的另一种用法是在容器中查找一系列相等的元素,例如

    std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };
    
    auto lower = std::lower_bound(data.begin(), data.end(), 4);
    auto upper = std::upper_bound(lower, data.end(), 4);
    
    std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));
    

    【讨论】:

    • equal_range 也这样做,至少不应该比同时调用lower_boundupper_bound 慢。
    • 在你的例子中,从lower而不是data.begin()开始搜索上限不是更有意义吗?
    【解决方案7】:

    哎呀!

    您是否更改了原始代码,或者从第一天起就出现了复制粘贴错误?

    float operator()(double f)
    {
        SpectrumPoint* l=std::lower_bound//...
    ...
        SpectrumPoint* u=std::lower_bound//...
    ...
    }
    

    在我今天阅读的代码中,您将 lower_bound 分配给“l”和“u”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-16
      • 1970-01-01
      • 2017-02-01
      • 2018-03-19
      相关资源
      最近更新 更多