C++で単純なPythonのenumerateを作ってみる

Sept. 9, 2019, 1:05 a.m. edited Sept. 9, 2019, 2:40 a.m.

#C++  #Python 

C++ガチ勢の書いたenumerateは

http://pushl.hatenablog.com/entry/2013/12/13/053711
http://reedbeta.com/blog/python-like-enumerate-in-cpp17/
https://cpplover.blogspot.com/2017/10/range-based-for.html
https://cpplover.blogspot.com/2017/10/range-based-forif-constexpr.html

とかを見ると良いと思う.

##########

以下は誰でもわかる簡単なもの.効率も悪い.

C++には範囲for文 (range-based for) があり,これを使うと

int main() {
    std::vector<std::string> values = {"hoge", "foo", "bar"};
    for(auto&& value : values) {
        std::cout << value << std::endl;
    }
    return 0;
}

動くコード

と書けて,インデックスを使った場合に起こりうる範囲外アクセスの危険性がなくなる(ちなみにauto&&についてはここに説明があった).ここで,Pythonでも同じように

values = ['hoge', 'foo', 'bar']
for value in values:
    print(value)

と書くことができる.Rustなどでもインデックスを使わないforが一般的なので,今はこちらの方が主流なのかもしれない.

ところで,それでもやはりインデックスが欲しくなるときはある.例えば,見ている要素が奇数番目なのかが必要になるときなど色々ある.こういうときPythonでは,

values = ['hoge', 'foo', 'bar']
for i, value in enumerate(values):
    print(f'{i}: {value}')

動くコード

というようにenumerateを使ってインデックスと中身の両方をforで得ることができる.

しかし,C++にはenumerateは残念ながらないので,これを実装したいというのが今回の目的である.最初に申し上げたように実用的で効率的なコードは本稿の一番上に羅列した記事を見るのが良い.ここでは,誰でもわかる単純な方法でやってみるというものである.

まず,forでコンテナから2つの変数へ持ってくることは構造化束縛を使うとできる.具体的には,

int main() {
    std::vector<std::tuple<size_t, std::string>> values = {
        {0, "hoge"}, {1, "foo"}, {2, "bar"},
    };
    for(auto&& [i, value] : values) {
        std::cout << i << ": " << value << std::endl;
    }
    return 0;
}

動くコード

という書き方ができる.したがって,valuesの中身をインデックスを持つtupleにするenumerateを作れば良いということになる.

それでは,シンプルにenumerate関数を書いてみる.

std::vector<std::tuple<size_t, std::string>> enumerate(const std::vector<std::string>& values) {
    auto length = values.size();
    auto values_with_indices = std::vector<std::tuple<size_t, std::string>>(length);
    for(size_t i = 0; i < length; ++i) {
        values_with_indices[i] = std::make_tuple(i, values[i]);
    }
    return values_with_indices;
}

あまりに愚直であるが,これで

int main() {
    std::vector<std::string> values = {"hoge", "foo", "bar"};
    for(auto&& [i, value] : enumerate(values)) {
        std::cout << i << ": " << value << std::endl;
    }
    return 0;
}

動くコード

と書くことができる.

さすがにstd::vectorの中身を任意のものにしたいので,templateを使って,

template<typename T>
std::vector<std::tuple<size_t, T>> enumerate(const std::vector<T>& values) {
    auto length = values.size();
    auto values_with_indices = std::vector<std::tuple<size_t, T>>(length);
    for(size_t i = 0; i < length; ++i) {
        values_with_indices[i] = std::make_tuple(i, values[i]);
    }
    return values_with_indices;
}

動くコード

もっと任意のコンテナ(vector, list, ...)を渡せないのか?

template<template<typename T> class Container, typename E>
Container<std::tuple<size_t, E>> enumerate(const Container<E>& values) {
    auto length = values.size();
    auto values_with_indices = Container<std::tuple<size_t, E>>(length);
    size_t i = 0;
    std::transform(values.begin(), values.end(), values_with_indices.begin(), <a href="auto& value" target="_blank" rel="noopener">&</a>{return std::make_tuple(i++, value);});
    return values_with_indices;
}

動くコード

とりあえず,これで終わり.