【问题标题】:How to find median of `std::set` [duplicate]如何找到`std :: set`的中位数[重复]
【发布时间】:2020-12-31 20:09:25
【问题描述】:

我正在尝试找到std::set 的中位数。由于std::set 已经对所有内容进行了排序,我只需要选择中间元素。我的想法是推进到一半:std::advance(e, rtspUrls.size() / 2);,但我不确定它会如何表现。像1.5 这样的数字呢?它会前进吗?

我正在使用 try catch 来尝试不进入未定义的内容。这安全吗?

根据http://www.cplusplus.com/reference/algorithm/min_element/?kw=min_element,如果迭代器抛出,std::advance 就会抛出。我不确定当我们尝试++ 时,std::set 的迭代器是否会抛出(https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator 什么也没说)。

std::set<RTSPUrl, decltype(compare_rtsp_url)*> rtspUrls(compare_rtsp_url);
std::set<RTSPUrl, decltype(compare_rtsp_url)*>::iterator e = rtspUrls.begin();
for (const RTSPUrl &rtspUrl : stream.rtsp_urls())
{
    if (rtspUrl.has_resolution())
    {
        rtspUrls.push_back(rtspUrl);
    }
}
try
{
    std::advance(e, rtspUrls.size() / 2);
    return *e;
}
catch (std::exception &e)
{
    return std::nullopt;
}

【问题讨论】:

  • 您需要检查元素的数量是偶数还是奇数,因为这将决定 median 是多少 Median - wikipedia

标签: c++


【解决方案1】:

我只需要选择中间元素。我的想法是推进到一半:std::advance(e, rtspUrls.size() / 2);,但我不确定它会如何表现。像 1.5 这样的数字呢?它会前进吗?

std::set 索引使用无符号整数值 (size_t),因此 double 1.5 将转换为 size_t 1

我不确定当我们尝试 ++ 时,std::set 的迭代器是否会抛出

不,它不会,但超越end() 是未定义的。

具有偶数元素的集合的真正中位数将取两个中间元素的平均值 - 但这要求您存储在std::set 中的类型都支持+/。示例:

std::set<double> foo{1., 2., 3., 10.};

if(foo.empty()) throw std::runtime_error("no elements in set");

double median;

if(foo.size() % 2 == 0) {                 // even number of elements 
    auto lo = std::next(foo.begin(), foo.size() / 2 - 1);
    auto hi = std::next(lo);
    median = (*lo + *hi) / 2.;
} else {                                  // odd number of elements
    median = *std::next(foo.begin(), foo.size() / 2);
}

std::cout << median << '\n'; // prints 2.5

在您的情况下,集合中的类型看起来不支持 +/ 以创建平均两个 RTSPUrls,以防您有偶数个元素,所以您可能应该只是如果您有偶数数量,请选择两个中间元素之一。通过返回一个迭代器(这样用户就可以检查它是否是rtspUrls.end()):

return std::next(rtspUrls.begin(), rtspUrls.size() / 2);

或者通过返回对元素的引用或副本:

if(rtspUrls.empty()) throw std::runtime_error("no elements in set");
return *std::next(rtspUrls.begin(), rtspUrls.size() / 2);

【讨论】:

    【解决方案2】:

    使用std::set,您只能使用迭代器迭代到中间元素(如果您的集合中有奇数个条目)或迭代到 middle-1 和中间并取平均值(在 a 的情况下)偶数个条目)来确定中位数。

    一个简单的循环和一个计数器几乎是直截了当的。一个简短的例子是:

    #include <iostream>
    #include <set>
    
    int main (void) {
        
    #ifdef ODD
        std::set<std::pair<char,int>> s {{'a',1}, {'b',2}, {'c',3}, {'d',4}, {'e',5}};
    #else
        std::set<std::pair<char,int>> s {{'a',1}, {'b',2}, {'c',3}, {'d',4}, {'e',5}, {'f',6}};
    #endif
        double median = 0.;
        size_t n = 0;
        
        for (auto iter = s.begin(); iter != s.end(); iter++, n++) {
            if (n == s.size() / 2 - 1 && s.size() % 2 == 0) {
                median += iter->second;
                std::cout << iter->first << "  " << iter->second << '\n';
            }
            if (n == s.size() / 2) {
                median += iter->second;
                if (s.size() % 2 == 0)
                    median /= 2.;
                std::cout << iter->first << "  " << iter->second
                        << "\n\nmedian " << median << '\n';
                break;
            }
        }
    }
    

    (当然,您必须调整类型以满足您的数据)

    使用/输出示例

    编译时使用ODD定义:

    $ ./bin/set_median
    c  3
    

    中位数 3

    在没有额外定义的情况下编译 EVEN 案例:

    $ ./bin/set_median
    c  3
    d  4
    
    median 3.5
    

    std::next

    您可以使用std::next 前进到当前迭代器之后的第 nth 个迭代器。您必须分配结果:

        median = 0.;
        auto iter = s.begin();
        
        if (s.size() % 2 == 0) {
            iter = std::next(iter, s.size() / 2 - 1);
            median += iter->second;
            iter = std::next(iter);
            median += iter->second;
            median /= 2.;
        }
        else {
            iter = std::next(iter, s.size() / 2);
            median += iter->second;
        }
        std::cout << "\nmedian " << median << '\n';
    

    std::advance

    std::advance 将作为参数提供的迭代器推进到当前迭代器之后的第 nth 个迭代器:

        median = 0.;
        iter = s.begin();
        if (s.size() % 2 == 0) {
            std::advance(iter, s.size() / 2 - 1);
            median += iter->second;
            std::advance(iter, 1);
            median += iter->second;
            median /= 2.;
        }
        else {
            std::advance(iter, s.size() / 2);
            median += iter->second;
        }
        std::cout << "\nmedian " << median << '\n';
    

    median 的输出与上面的循环相同)

    查看一下,如果您还有其他问题,请告诉我。

    【讨论】:

    • std::nextstd::advance 中的任一个
    • 谢谢,这正是我要找的。​​span>
    • if 不需要打断?
    • 不,因为这是偶数个元素的两部分中位数计算的第一部分。注意n == s.size() / 2 - 1,然后是下面的n == s.size() / 2
    【解决方案3】:

    我只需要选择中间元素

    仅当集合包含奇数个元素时。否则,当大小为偶数时,中值定义为两个中间值的平均值,有时称为上中值和下中值。

    像 1.5 这样的数字呢?

    你永远不会得到这个,因为rtspUrls.size() / 2 是一个整数除法,它会截断任何小数位。

    我认为,传递 floatdouble 作为第二个参数,如 std::advance(e, 1.5) 不应该编译。 据我所见,reference 没有指定第二个参数的类型。然而,“可能的实现”部分总是使用特定于第一个参数的差异类型,这通常是一个整数类型,看起来很合理。

    我正在使用 try catch 来尝试不进入未定义的内容。这安全吗?

    不,取消引用或递增无效的迭代器是未定义的行为,不需要引发任何异常。尽管许多实现在调试版本中提供了广泛的错误检查,并且很好地抛出异常 UB 发生。但是推进到集合大小的一半不会成为问题。

    【讨论】:

      猜你喜欢
      • 2012-05-15
      • 2013-11-23
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      • 2012-11-07
      • 2015-05-21
      • 2012-06-10
      • 1970-01-01
      相关资源
      最近更新 更多