簡體   English   中英

STL 算法的可組合性

[英]Composability of STL algorithms

STL 算法在 C++ 中非常有用。 但讓我感到厭煩的一件事是它們似乎缺乏可組合性。

例如,假設我有一個vector<pair<int, int>>並希望將其轉換為僅包含該對的second成員的vector<int> 這很簡單:

std::vector<std::pair<int, int>> values = GetValues();
std::vector<int> result;

std::transform(values.begin(), values.end(), std::back_inserter(result),
    [] (std::pair<int, int> p) { return p.second; });

或者,也許我只想過濾first成員為偶數的那些對的vector 也很簡單:

std::vector<std::pair<int, int>> values = GetValues();
std::vector<std::pair<int, int>> result;

std::copy_if(values.begin(), values.end(), std::back_inserter(result),
    [] (std::pair<int, int> p) { return (p.first % 2) == 0; });

但是如果我想兩者都做呢? 沒有transform_if算法,同時使用transformcopy_if似乎需要分配一個臨時vector來保存中間結果:

std::vector<std::pair<int, int>> values = GetValues();
std::vector<std::pair<int, int>> temp;
std::vector<int> result;

std::copy_if(values.begin(), values.end(), std::back_inserter(temp),
    [] (std::pair<int, int> p) { return (p.first % 2) == 0; });

std::transform(values.begin(), values.end(), std::back_inserter(result),
    [] (std::pair<int, int> p) { return p.second; });

這對我來說似乎相當浪費。 我能想到的避免臨時向量的唯一方法是放棄transformcopy_if並簡單地使用for_each (或常規的 for 循環,以適合您的方式):

std::vector<std::pair<int, int>> values = GetValues();
std::vector<int> result;

std::for_each(values.begin(), values.end(),
    [&result] (std::pair<int, int> p) 
        { if( (p.first % 2) == 0 ) result.push_back(p.second); });

我在這里錯過了什么嗎? 有沒有一種好方法可以將兩個現有的 STL 算法組合成一個新算法而不需要臨時存儲?

你是對的。 您可以使用Boost.Range 適配器來實現組合。

我認為這個問題不幸是結構性的

  1. C++ 使用兩個迭代器來表示一個序列
  2. C++ 函數是單值的

所以你不能鏈接它們,因為 function 不能返回“序列”。

一種選擇是使用單對象序列代替( 例如 boost 的 range 方法)。 這樣,您可以將一個處理的結果組合為另一個處理的輸入......(一個 object -> 一個對象)。

在標准 C++ 庫中,處理是(兩個對象 -> 一個對象),很明顯,如果不命名臨時 object,就無法將其鏈接起來。

早在 2000 年,這個問題就已經被注意到了。 Gary Powell 和 Martin Weiser 提出了“視圖”概念,並創造了“視圖模板庫”這個名稱。 它當時並沒有起飛,但這個想法是有道理的。 “視圖”適配器本質上是應用動態變換。 例如,它可以調整value_type

現在我們有了 C++0x,這個概念可能應該重新解決。 自 2000 年以來,我們在泛型編程方面取得了相當大的進展。

例如,讓我們使用vector<pair<int, int>>vector<int>的例子。 這可能很簡單:

std::vector<std::pair<int, int>> values = GetValues();
vtl2::view v (values, [](std::pair<int, int> p) { return p.first }); 
std::vector<int> result(view.begin(), view.end());

或者,使用boost::bind技術,更簡單:

std::vector<std::pair<int, int>> values = GetValues();
vtl2::view v (values, &std::pair<int, int>::first); 
std::vector<int> result(view.begin(), view.end());

C++20開始,您可以將std::ranges::copyRanges 庫中的范圍適配器std::views::filterstd::views::values一起使用,如下所示:

int main() {
    std::vector<std::pair<int, int>> values = { {1,2}, {4,5}, {6,7}, {9,10} };
    std::vector<int> result;

    auto even = [](const auto& p) { return (p.first % 2) == 0; };
    std::ranges::copy(values | std::views::filter(even) | std::views::values,
                      std::back_inserter(result));

    for (int i : result)
        std::cout << i << std::endl;

    return 0;
}

Output:

5
7

在上面的解決方案中,沒有為中間結果創建臨時向量,因為視圖適配器創建不包含元素的范圍。 這些范圍只是輸入向量的視圖,但具有自定義的迭代行為。

魔杖盒上的代碼

不知道這是否仍然有效,但是......一個新的燈等待 header 只有你描述的庫。 Doc 討論了惰性評估和 com 可組合生成器。

文檔片段:

  • 從文件“test.txt”中讀取最多 10 個整數。
  • 過濾偶數,將它們平方並求和它們的值。
    int total = lz::read<int>(ifstream("test.txt")) | lz::limit(10) |
                lz::filter([](int i) { return i % 2 == 0; }) |
                lz::map([](int i) { return i *  i; }) | lz::sum();

您可以將該行拆分為多個表達式。

    auto numbers = lz::read<int>(ifstream("test.txt")) | lz::limit(10);
    auto evenFilter = numbers | lz::filter([](int i) { return i % 2 == 0; });
    auto squares = evenFilter | lz::map([](int i) { return i *  i; });
    int total = squares | lz::sum();
  • 盡管這個表達式被拆分為多個變量賦值,但它的效率並沒有降低。
  • 每個中間變量簡單地描述了一個要執行的代碼單元。 全部存放在堆棧中。

https://github.com/SaadAttieh/lazyCode

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM