質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

3回答

2665閲覧

C++ 合計値 範囲指定

totake

総合スコア6

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2018/01/23 15:22

編集2018/01/23 17:25

C++で合計値を出力するプログラムを作成しています。プログラムを実行するとすべての和が出力されるのですが、
これを指定した範囲、例えば2、3、4、5の合計値を出力したい場合どうしたらいいのでしょうか?
よろしくお願いします。

C++

1#include <iostream> 2#include <vector> 3#include <string> 4#include <numeric> 5 6int main() 7{ 8 const std::vector<int> v = {1, 2, 3, 4, 5}; 9 10 // 合計値を求める 11 int sum = std::accumulate(v.begin(), v.end(), 0); 12 std::cout << "sum : " << sum << std::endl; 13}

C++

1#include <string> 2#include <vector> 3#include <algorithm> 4#include <iostream> 5 6struct score { 7 std::string name; 8 int aaa; 9 int bbb; 10 int ccc; 11 12 score(std::string n, int a, int b, int c) : 13 name(n), aaa(a), bbb(b), ccc(c) {} 14}; 15 16 17class score_a { 18public: 19 bool operator()(const score& value) const { 20 return value.aaa ; 21 } 22}; 23 24int main() { 25 std::vector<score> v1; 26 v1.push_back(score("青木", 5, 10, 15)); 27 28 int tokuten = std::count_if(v1.begin(), v1.end(), score_a()); 29 std::cout << "青木:" << tokuten << std::endl; 30 31 return 0; 32}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

もう少しだけ詳しい仕組みも説明しようかと。

イテレータについて

イテレータは、簡単に言えば「ある要素の位置」を表すものです。簡単に言えばですが。std::vector もイテレータを実装していて、メンバ関数begin()によって要素の先頭を指すイテレータ、end()によって要素の末尾を指すイテレータを得ることができます。

C++のSTL関数では、範囲を受け取るときに範囲の「開始位置を指すイテレータ」と「終了位置を指すイテレータ」を取るものが多くあります。上のソースで使われている std::accumulate() もその一つです。

cpp

1std::accumulate(開始位置, 終了位置, 初期値);

このように書くと、開始位置から終了位置までの要素の和 (を初期値に足したもの) を求めることができます。だから、

cpp

1std::accumulate(v.begin(), v.end(), 0);

というのは「初期値を0としてv.begin()からv.end()までの要素を全部足していく」という意味になります。v.begin()は先頭要素を指すイテレータを返しますし、v.end()は末尾要素を指すイテレータを返しますので、結局「先頭から末尾までの要素の値の和」ということになります。

では、質問の例のように

cpp

1const std::vector<int> v = {1, 2, 3, 4, 5};

の {2, 3, 4, 5} の和を求めるときにはどうすればよいか。

この範囲は、「先頭の次の要素」から「末尾の要素」まで、ということになりますね。なので、これらの位置を表すイテレータを得て、それを開始位置と終了位置に指定してやればよいわけです。あるイテレータをいくつか進めたイテレータを得るには std::next(イテレータ, 進める数) を使います。進める数は、指定しなければデフォルトで1になります。逆の働きをする std::prev(イテレータ, 戻す数) という関数もあります。これは末尾から戻す必要があるときに使えます。

以上のことから、次のように書けば求める結果が得られます。

cpp

1int sum = std::accumulate ( 2 std::next(v.begin()), // 開始位置 --- 先頭から1進めたもの。 3 v.end(), // 終了位置 --- 末尾 4 0 // 初期値 --- 要素だけの和をとるなら0ですよね 5);

ちなみに、終了位置は必ずしも末尾から考える必要もなく、和をとりたい範囲が「先頭の次の要素」から要素3つ分、つまり「[先頭の次の要素]の3つ先」まで (例のvなら {2, 3, 4}) だった場合は次のようにすればよいです。

cpp

1// 開始位置 := 先頭の次の要素 2auto beg = std::next(v.begin(), 1); 3 4int sum = std::accumulate ( 5 beg, // 開始位置 つまり 先頭の次の要素 6 std::next(beg, 3), // 「開始位置」に指定したものの3つ先 7 0 // 初期値は0 8);

std::vectorのイテレータは実は足し算ができるので、std::next()を使わずv.begin() + 1とかbeg + 3とかでもいくつか進めたイテレータを得ることができます。

いくらかごまかしている場所があって、そのうちの1つは、実は終了位置が指す要素は範囲に含まないということです。要するに [begin, end) なる半開区間ということです。そのために end() は実は末尾位置の"1つ先"を返しています。

質問文後半のプログラムについて

std::count_if関数は、

cpp

1std::count_if(開始位置, 終了位置, 条件の関数);

で、範囲内で 条件の関数が true を返す「要素の数」 を数える関数です。

cpp

1int tokuten = std::count_if(v1.begin(), v1.end(), score_a());

を翻訳すると


tokuten に「v1の先頭から末尾まで」の範囲で、関数 score_a()(要素) が true を返す要素の数を代入せよ。

つまり

tokuten に「v1の先頭から末尾まで」の範囲で、メンバ変数 aaa が true となる要素の数を代入せよ。

つまり (aaa は int なので)

tokuten に「v1の先頭から末尾まで」の範囲で、メンバ変数 aaa が 0 でない要素の数 を代入せよ。


今 v1 には score("青木", 5, 10, 15) という要素があります。この要素の aaa は 5 であり、5 は 0 でないので条件に一致します。他に要素がないので std::count_if() の仕事は終了です。
結果、条件に一致した v1 の要素の数は 1 つということでtokuten == 1となります。aaa の中にある 5 を出力したいのなら、素直に int tokuten = v1[0].aaa; とすればよいです。


3つの得点の和がほしい場合でも LouiS0616 さんもおっしゃっている通り愚直に aaa + bbb + ccc とするしかないです。

便利な関数群が使えるように、少し改変して配列を使うようにしてみました。例えば次のようになります。ついでに青木くん以外のクラスメイトも増やしてみました。

cpp

1#include <string> 2#include <vector> 3#include <numeric> 4#include <algorithm> 5#include <iostream> 6#include <array> 7 8constexpr const size_t NUMOF_SUBJECTS = 3; 9// std::array を使いますが、ちょっと長くて面倒なので別名をつけます。 10using score_array = std::array<int, NUMOF_SUBJECTS>; 11 12struct score { 13 std::string name; 14 score_array scores; 15 16 score(std::string n, score_array const &s) 17 : name(n), scores(s) {} 18 19 int get_total_score() { 20 return std::accumulate(scores.begin(), scores.end(), 0); 21 } 22}; 23 24int main() { 25 std::vector<score> v1; 26 v1.push_back(score( 27 "青木", 28 score_array{{5, 10, 15}} 29 )); 30 v1.push_back(score( 31 "田中", 32 score_array{{3, 20, 4}} 33 )); 34 v1.push_back(score( 35 "出来内", 36 score_array{{0, 0, -3}} 37 )); 38 39 // range-based for というのを使うともう少し楽に書けますが。 40 // C言語的にループしましょう。 41 for (size_t i = 0; i < v1.size(); i++) { 42 std::cout 43 << v1[i].name 44 << ":" 45 << v1[i].get_total_score() 46 << std::endl; 47 } 48 49 return 0; 50}

実行結果:

青木:30 田中:27 出来内:-3

投稿2018/01/24 06:57

Eki

総合スコア429

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

寝る前にちょっと遊んでみました。外れてそうな気もしますが、参考になるかも。
sum()関数が見慣れない形なのは、scoresを生配列にも対応させたからです。
(C++11が必要です。)

C++

1#include <string> 2#include <vector> 3#include <numeric> 4#include <iostream> 5 6struct Person 7{ 8 std::string name; 9#if 0 10 int scores[5]; 11#else 12 std::vector<int> scores; 13#endif 14}; 15 16template<class tContainer> 17int sum(tContainer const& iContainer) 18{ 19 return std::accumulate(std::begin(iContainer), std::end(iContainer), 0); 20} 21 22int main() 23{ 24 std::vector<Person> aPersons = 25 { 26 {"佐藤", { 1, 2, 3}}, 27 {"鈴木", {11, 12, 13, 14}}, 28 {"田中", {21, 22, 23, 24, 25}} 29 }; 30 31 for(auto& person : aPersons) 32 { 33 std::cout << person.name << " : " << sum(person.scores) << " ("; 34 bool first = true; 35 for(auto score : person.scores) 36 { 37 if (!first) std::cout << ", "; 38 first = false; 39 std::cout << score; 40 } 41 std::cout << ")\n"; 42 } 43}

wandboxで結果をみる

投稿2018/01/23 18:34

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

std::advanceを使うと良いかと思います。

C++

1#include <cassert> 2#include <iterator> 3#include <iostream> 4#include <numeric> 5#include <vector> 6 7int slice_sum(const std::vector<int>& v, size_t from, size_t to) { 8 assert(to <= v.size()); 9 10 auto it_begin = v.cbegin(), it_end = v.cend(); 11 std::advance(it_begin, from); 12 std::advance(it_end, to-v.size()); 13 14 return std::accumulate(it_begin, it_end, 0); 15} 16 17int main(void) { 18 const std::vector<int> v = {1, 2, 3, 4, 5}; 19 20 int sum = slice_sum(v, 1, v.size()); 21 std::cout << "sum : " << sum << std::endl; 22 23 return 0; 24}

修正:yumetodoさんからご指摘を受けて

今回の場合、std::nextの方が簡潔に処理を記述できるようです。

yumetodoさんが書かれたslice_sum関数はこちら。

C++

template<typename T>
int slice_sum(const std::vector<T>& v, size_t from, size_t to) {
assert(to <= v.size());
return std::accumulate(std::next(v.cbegin(), from), std::next(v.cbegin(), to), T{});
}

コメントを受けて

構造体を利用する例。

C++

1#include <iostream> 2#include <numeric> 3#include <vector> 4 5struct Person { 6 Person(const std::string& name, const std::vector<int>& scores) 7 : name_(name), scores_(scores) { } 8 9 auto& get_name(void) const noexcept { 10 return this->name_; 11 } 12 auto& get_scores(void) const noexcept { 13 return this->scores_; 14 } 15 16private: 17 const std::string& name_; 18 const std::vector<int> scores_; 19}; 20 21int main(void) { 22 Person p("青木", {2, 3, 4, 5}); 23 24 int sum = std::accumulate( 25 p.get_scores().cbegin(), 26 p.get_scores().cend(), 27 0 28 ); 29 std::cout << p.get_name() << " sum : " << sum << std::endl; 30 31 return 0; 32}

投稿2018/01/23 15:39

編集2018/01/24 05:42
LouiS0616

総合スコア35660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

totake

2018/01/23 15:58

ご回答ありがとうございます。 v = {1, 2, 3, 4, 5}の部分なんですけど、1の部分を日本語の名前例えば、 v = {"青木", 2, 3, 4, 5}などにしたい場合はどうすればいいでしょうか? そもそも日本語名を使うことはできますか?
LouiS0616

2018/01/23 16:03 編集

基本的に型の違うデータを同じvectorに押し込むことは出来ないですね。 適切に構造体/クラスを組むか、連想配列を使えばいいんじゃないでしょうか。
yumetodo

2018/01/23 16:04

それは何がしたいかによります。もうすこしやりたいことと目的を詳しく補足してください。現状だとstd::variantをすすめるべきなのかもっと適切な方法をすすめるべきか判別つきません
totake

2018/01/23 16:08

ご指摘ありがとうございます。 今やりたいことは、v = {"青木", 2, 3, 4, 5}と想定して、 名前:合計値 と出力させたいのですが、 青木:14 のように出力させたいです。
yumetodo

2018/01/23 16:20

std::vector<int>の要素型はintであり、文字列は格納できないんですね。でまた、一つの配列にいろんなデータが有るというのは根本的に間違っているので、上記提案をしました。
yumetodo

2018/01/23 16:21

>Personクラス そちらのほうが意味的にはわかりやすいですが。
LouiS0616

2018/01/23 16:26

今後どれくらい属性が増えるか分からないですものね。 yumetodoさんのように色んなアイデアが出てこないもので、非常に参考になります。
yumetodo

2018/01/23 16:55

それはそうと、std::advanceは引数を書き換えるのでstd::nextのほうが一般に扱いやすいと思われます。
totake

2018/01/23 17:38

もう1つソースコードを載せさせていただきました。 今のままでは、1と出力されるのですが、 これをaaaの場所にある5という数字を出力することはできますでしょうか? またこれから5,10,15を足して出力することはできますでしょうか?
LouiS0616

2018/01/24 05:42

@yumetodo さん 回答に引用する形で紹介させていただきました。ご指摘ありがとうございます。
LouiS0616

2018/01/24 05:48

@totake さん > 今のままでは、1と出力されるのですが、これをaaaの場所にある5という数字を出力することはできますでしょうか? --- もともと数えるためにcount_ifに放り込んだのですよね? > またこれから5,10,15を足して出力することはできますでしょうか? --- コンテナではなく別々の変数に値が格納されているので、愚直に足すしかないと思います。
yumetodo

2018/01/24 09:42

>愚直に足すしかない 一応initizer_listにぶち込むという手法もないわけではないですが(イテレータが使える)
LouiS0616

2018/01/24 09:58

std::array<int, 3> arr{aaa, bbb, ccc}; ってことですか?
yumetodo

2018/01/24 10:31

それstd::arrayや。まあそれでもいいけど
Eki

2018/01/24 11:07

auto list = {aaa, bbb, ccc}; ということですよね。まあ結局列挙すると考えると、この例では素直に+演算子を書くほうが楽ではありますが。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問