泛型演算法:Tips (3) — 初始化

來源:互聯網
上載者:User

上次提到過為容器產生資料的問題,我給出的用 boost.lambda 的方法是:

  std::vector<int> vect(10);
  int i = 0;
  std::for_each( vect.begin(), vect.end(), _1 = ++var(i) );

不錯,這樣可以產生連續的數字,也還算比較簡潔,因為代碼量不會隨著容器的大小而變化,不過,如果要在容器內填入隨機數呢?其實比上面更簡單,因為 STL 的 generate 演算法就是設計來做這個的:

  std::vector<int> vect(10);
  std::generate(vect.begin(), vect.end(), rand);

rand 是我們熟悉的標準 C 庫函數,這樣我們可以產生任意數量的隨機數了,不過還是有點不好的地方:每次產生的序列都是一樣的,因為 rand 產生的是偽隨機數。這個容易解決,我們必須先 seed 一下:

  std::vector<int> vect(10);
  srand(time(NULL));
  std::generate(vect.begin(), vect.end(), rand);

好了,我們終於還是用了三行(其實是兩行,聲明 vector 總是必需的吧!),但是好歹是有了一個可用的方案。回頭看看,前面的連續整數問題也可以用 generate 來做,方法不言而喻:

  std::vector<int> vect(10);
  int i = 0;
  std::generate(vect.begin(), vect.end(), ++var(i));

或者

  std::vector<int> vect;
  int i = 0;
  std::generate_n(back_inserter(vect), 10, ++var(i));

好處是 generate 本身更能說明這句話的用途,當然這個可能因人而異。

我知道有人一定在問:一定要兩行嗎?一定要有一個初始變數嗎?答案是可以沒有,但是要用到另外的演算法,再加上 boost.lambda 的協助。看看下面:

  std::vector<int> vect(10);
  std::partial_sum(vect.begin(), vect.end(), vect.begin(), _2 = _1 + 1);

如果你現在把 vect 輸出,你會得到:

0 1 2 3 4 5 6 7 8 9

乍看起來不太好理解,我來慢慢解釋。
partial_sum 的第4個參數是一個雙參數的 functor ,在這裡,lambda 運算式 _2 = _1 + 1 充當了這個角色,它相當於

f(x, y)  {  y  =  x  +  1;  }


而 partial_sum 呢?它把一個序列的 partial sum 送到結果序列中去,例如如果輸入一個數組 v[10] ,而輸出是 r[10] ,那麼它的計算就是

r[0] = v[0]            
r[1] = f( r[0], r[1] )
r[2] = f( r[1], r[2] )
......
r[9] = f( r[8], r[9] )


而當我們把 partial_sum 作用於 vect 本身,結果就成了

vect[0] = vect[0]                            // vect[0] = 0
vect[1] = (vect[1] = vect[0] + 1)   // vect[1] = 1
vect[2] = (vect[2] = vect[1] + 1)   // vect[2] = 2
......
vect[9] = (vect[9] = vect[8] + 1)   // vect[9] = 9


你一定發現其中的問題所在了:首先,我們必須依賴於編譯器把 vect[0] 初始化為0,其次,vect[0] = vect[0] 是不可迴避的。以我當前所想到的,也只能這樣了。

推廣一下,如果把 _2 = _1 + 1 中的常數 1 換成另外的數字,我們就可以用一句話得到從 0 開始的等差數列,例如

  std::partial_sum(vect.begin(), vect.end(), vect.begin(), _2 = _1 + 3);

得到的是

0 3 6 9 12 15 18 21 24 27

如果再發揮一點想象力,你就可以構造出更複雜的 lambda 運算式,從而得到更複雜的數組(也許這裡叫數列更好吧),例如

  std::partial_sum(vect.begin(), vect.end(), vect.begin(), _2 = 2 * _1 + 1);

得到的是 2 的 n 次方 - 1 數列

0 1 3 7 15 31 63 127 255 511

在 STL 演算法中,adjacent_difference 和 partial_sum 是逆運算,因此,上面的事情也可以用 adjacent_difference 來做,只不過要把 lambda 運算式中的參數位置換一下,例如要得到 0, 3, 6... 的等差數列,只需要

  std::adjacent_difference(vect.begin(), vect.end(), vect.begin(), _1 = _2 + 3);

而 2 的 n 次方 - 1 數列也是同樣道理

  std::adjacent_difference(vect.begin(), vect.end(), vect.begin(), _1 = 2*_2 + 1);

如果你要產生倒序的數列呢?當然,STL 演算法 reverse 可以派上用場,不過也不要忘了 STL 還有 reverse_iterator 這回事,用它就無需另外調用 reverse 了:

  std::partial_sum(vect.rbegin(), vect.rend(), vect.rbegin(), _2 = 2*_1 + 1);

得到

511 255 127 63 31 15 7 3 1 0

最後還要提醒大家不要忘了一個很有用的 STL 演算法: random_shuffle 。它可以把 Random access container 裡面的值打亂,配合上面的數列產生,在很多場合是進行測試(例如測試排序演算法)的好工具。在我的機器上,下面兩行

  std::partial_sum(vect.begin(), vect.end(), vect.begin(), _2 = 2*_1 + 1);
  std::random_shuffle(vect.begin(), vect.end());

得到打亂以後的數列:

255 1 511 3 0 31 127 7 15 63

=================================================================================

有了強大的產生機製作基礎,下面的實驗也更加容易了。STL 的 count_if 和 find_if 都接受一個 predicate 作為比較的依據,而這個 predicate 往往非常簡單,以至於為它專門寫一個 functor 簡直不可接受。在第一篇裡面已經展示了用 boost.lambda 產生臨時的無名 functor 的能力,這裡再多說一點。

下面先產生 2^n - 1 的數組,然後找出其中第一個大於100的數

  std::vector<int> vect(10);
  std::partial_sum(vect.begin(), vect.end(), vect.begin(), _2 = 2*_1 + 1);
 
  std::cout << *std::find_if(vect.begin(), vect.end(), _1 > 100);

輸出為 127 ,如我們所料。同樣道理,如果是 count_if ,則會得到大於100的數的個數

  std::cout << std::count_if(vect.begin(), vect.end(), _1 > 100);

輸出是 3 。注意細節:find_if 返回一個 iterator ,所以在它之前有 * 解引用,而 count_if 直接返回一個數字,無需解引用。

與之類似的還有 STL 的 partition 演算法,它根據傳入的 predicate 對一個序列進行劃分,predicate 得到 true 的將放在前面,其餘的放在後面,返回的是那些“放在後面”的元素中的第一個,換言之就是分界點。下面的代碼

  std::vector<int> vect(10);
  std::partial_sum(vect.begin(), vect.end(), vect.begin(), _2 = 2*_1 + 1);
 
  std::cout << *std::partition(vect.begin(), vect.end(), _1 > 100) << std::endl;
 
  std::for_each(vect.begin(), vect.end(), std::cout << _1 << " ");

輸出為

7
511 255 127 7 15 31 63 3 1 0

如果仔細觀察,還可以發現上面的輸出有點問題:數列中原有的順序(0, 1, 3, 7...)不複存在,這是因為 partition 並不是一個穩定排序的演算法,它不保證排序結果保有原來的順序。如果需要穩定排序,可以使用 stable_partition 。只需要更改排序的那一句代碼為

  std::cout << *std::stable_partition(vect.begin(), vect.end(), _1 > 100) << std::endl;

結果是

0
127 255 511 0 1 3 7 15 31 63

當然,如果你還記得大學裡的演算法理論,就知道它們在效率上是有點區別的,partition 的複雜度保證為 O(n) ,具體地說是保證不超過 n/2 次交換;而 stable_partition 在最好情況下為 O(n) ,最差情況則達到 O(n*log(n)) 。

順便說一下,上面的幾件簡單的事情,用標準的 STL 演算法都可以辦到,只不過實在是……面目可憎:

  std::cout << *std::partition(vect.begin(), vect.end(),
    std::bind2nd(std::greater<int>(), 100)) << std::endl;

這句代碼做的事情和前面的 partition 一模一樣,但是孰優孰劣,大家自有公斷。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.