N个Boost间隔集合的组合

4
我有一个服务,在4个不同的位置出现故障。我将每个位置的故障建模为Boost ICL interval_set。我想知道至少有N个位置有活动故障的时间。
因此,遵循这个答案,我实现了一种组合算法,以便通过interval_set交集创建元素之间的组合。
当这个过程结束时,我应该有一定数量的interval_set,每个interval_set都同时定义N个位置的故障,并且最终步骤将是将它们连接起来以获得所需的完整图像。
问题是我目前正在调试代码,当打印每个交集的时间到达时,输出文本变得混乱(即使我使用gdb逐步调试),我无法看到它们,导致CPU使用率很高。
我猜想我在某种程度上发送了比应该更大的内存部分到输出,但我无法看到问题所在。
以下是SSCCE:
#include <boost/icl/interval_set.hpp>
#include <algorithm>
#include <iostream>
#include <vector>


int main() {
    // Initializing data for test
    std::vector<boost::icl::interval_set<unsigned int> > outagesPerLocation;
    for(unsigned int j=0; j<4; j++){
        boost::icl::interval_set<unsigned int> outages;
        for(unsigned int i=0; i<5; i++){
            outages += boost::icl::discrete_interval<unsigned int>::closed(
                (i*10), ((i*10) + 5 - j));
        }
        std::cout << "[Location " << (j+1) << "] " << outages << std::endl;
        outagesPerLocation.push_back(outages);
    }

    // So now we have a vector of interval_sets, one per location. We will combine
    // them so we get an interval_set defined for those periods where at least
    // 2 locations have an outage (N)
    unsigned int simultaneusOutagesRequired = 2;  // (N)

    // Create a bool vector in order to filter permutations, and only get
    // the sorted permutations (which equals the combinations)
    std::vector<bool> auxVector(outagesPerLocation.size());
    std::fill(auxVector.begin() + simultaneusOutagesRequired, auxVector.end(), true);

    // Create a vector where combinations will be stored
    std::vector<boost::icl::interval_set<unsigned int> > combinations;

    // Get all the combinations of N elements
    unsigned int numCombinations = 0;
    do{
        bool firstElementSet = false;
        for(unsigned int i=0; i<auxVector.size(); i++){
            if(!auxVector[i]){
                if(!firstElementSet){
                    // First location, insert to combinations vector
                    combinations.push_back(outagesPerLocation[i]);
                    firstElementSet = true;
                }
                else{
                    // Intersect with the other locations
                    combinations[numCombinations] -= outagesPerLocation[i];
                }
            }
        }
        numCombinations++;
        std::cout << "[-INTERSEC-] " << combinations[numCombinations] << std::endl;  // The problem appears here
    }
    while(std::next_permutation(auxVector.begin(), auxVector.end()));

    // Get the union of the intersections and see the results
    boost::icl::interval_set<unsigned int> finalOutages;
    for(std::vector<boost::icl::interval_set<unsigned int> >::iterator
        it = combinations.begin(); it != combinations.end(); it++){
        finalOutages += *it;
    }

    std::cout << finalOutages << std::endl;
    return 0;
}

有需要帮助的地方吗?
2个回答

11

正如我猜测的那样,这里有一种“高级”方法。

Boost ICL容器不仅仅是“带有区间起始/结束点的赞扬对”,它们旨在以通用优化的方式实现组合、搜索等操作。

所以不必自己实现。

如果您让库完成它应该做的事情:

using TimePoint = unsigned;
using DownTimes = boost::icl::interval_set<TimePoint>;
using Interval  = DownTimes::interval_type;
using Records   = std::vector<DownTimes>;

使用函数域typedefs可以提高代码的抽象层次。现在,让我们问一个假想的“业务问题”:

我们实际上想用我们每个位置停机记录做什么?

好吧,我们基本上想要:

  1. 对所有可辨别的时间段进行统计,并
  2. 筛选那些统计至少为2的时间段,并
  3. 最后,我们想展示剩下的“合并”时间段。

好的,工程师:去实现它吧!


  1. Hmm. Tallying. How hard could it be?

    ❕ The key to elegant solutions is the choice of the right datastructure

    using Tally     = unsigned; // or: bit mask representing affected locations?
    using DownMap   = boost::icl::interval_map<TimePoint, Tally>;
    

    Now it's just bulk insertion:

    // We will do a tally of affected locations per time slot
    DownMap tallied;
    for (auto& location : records)
        for (auto& incident : location)
            tallied.add({incident, 1u});
    
  2. Ok, let's filter. We just need the predicate that works on our DownMap, right

    // define threshold where at least 2 locations have an outage
    auto exceeds_threshold = [](DownMap::value_type const& slot) {
        return slot.second >= 2;
    };
    
  3. Merge the time slots!

    Actually. We just create another DownTimes set, right. Just, not per location this time.

    The choice of data structure wins the day again:

    // just printing the union of any criticals:
    DownTimes merged;
    for (auto&& slot : tallied | filtered(exceeds_threshold) | map_keys)
        merged.insert(slot);
    

报告!

std::cout << "Criticals: " << merged << "\n";

请注意,我们从未接近过操作数组索引、重叠或非重叠区间、闭合或开放边界。也没有使用暴力排列集合元素的方法。我们只是说明了我们的目标,让库来完成工作。
完整演示: Live On Coliru
#include <boost/icl/interval_set.hpp>
#include <boost/icl/interval_map.hpp>
#include <boost/range.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/numeric.hpp>
#include <boost/range/irange.hpp>
#include <algorithm>
#include <iostream>
#include <vector>

using TimePoint = unsigned;
using DownTimes = boost::icl::interval_set<TimePoint>;
using Interval  = DownTimes::interval_type;
using Records   = std::vector<DownTimes>;

using Tally     = unsigned; // or: bit mask representing affected locations?
using DownMap   = boost::icl::interval_map<TimePoint, Tally>;

// Just for fun, removed the explicit loops from the generation too. Obviously,
// this is bit gratuitous :)
static DownTimes generate_downtime(int j) {
    return boost::accumulate(
            boost::irange(0, 5),
            DownTimes{},
            [j](DownTimes accum, int i) { return accum + Interval::closed((i*10), ((i*10) + 5 - j)); }
        );
}

int main() {
    // Initializing data for test
    using namespace boost::adaptors;
    auto const records = boost::copy_range<Records>(boost::irange(0,4) | transformed(generate_downtime));

    for (auto location : records | indexed()) {
        std::cout << "Location " << (location.index()+1) << " " << location.value() << std::endl;
    }

    // We will do a tally of affected locations per time slot
    DownMap tallied;
    for (auto& location : records)
        for (auto& incident : location)
            tallied.add({incident, 1u});

    // We will combine them so we get an interval_set defined for those periods
    // where at least 2 locations have an outage
    auto exceeds_threshold = [](DownMap::value_type const& slot) {
        return slot.second >= 2;
    };

    // just printing the union of any criticals:
    DownTimes merged;
    for (auto&& slot : tallied | filtered(exceeds_threshold) | map_keys)
        merged.insert(slot);

    std::cout << "Criticals: " << merged << "\n";
}

打印哪个

Location 1 {[0,5][10,15][20,25][30,35][40,45]}
Location 2 {[0,4][10,14][20,24][30,34][40,44]}
Location 3 {[0,3][10,13][20,23][30,33][40,43]}
Location 4 {[0,2][10,12][20,22][30,32][40,42]}
Criticals: {[0,4][10,14][20,24][30,34][40,44]}

3
在排列循环结束时,您编写如下内容:
numCombinations++;
std::cout << "[-INTERSEC-] " << combinations[numCombinations] << std::endl;  // The problem appears here

我的调试器告诉我,在第一次迭代中,numCombinations 在增量之前是0。但是增加它使得它超出了combinations容器的范围(因为它只有一个元素,所以索引为0)。
你是不是想在使用后递增它?有没有特别的原因不使用?
std::cout << "[-INTERSEC-] " << combinations.back() << "\n";

或者,对于C++03
std::cout << "[-INTERSEC-] " << combinations[combinations.size()-1] << "\n";

甚至只是:

std::cout << "[-INTERSEC-] " << combinations.at(numCombinations) << "\n";

哪一个会抛出std::out_of_range


顺便提一下,我认为Boost ICL有更加高效的方法来得到你想要的答案。让我考虑一下。如果我看到了,会发布另一个答案。

更新:发布了另一个答案,展示了使用Boost ICL进行高级编程的示例(其他答案)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接