要素を取り除く 解答ページ | Programming Place Plus 新C++編

トップページ新C++編要素を取り除く

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (確認★)

“Hello” という文字列が格納された std::string の変数s に対して、s.clear(); を実行したあとの状態として、正しいものをすべて選んで下さい。

  1. s.length() で得られる結果が 0 になる
  2. s.capacity() で得られる結果が 0 になる
  3. s.empty() で得られる結果が true になる
  4. if (s == "") が true になる


clear関数は、要素をすべて取り除きます(本編解説)。

1番は正しいです。要素がなくなったのですから、length関数で得られる要素数(長さ)は 0 になります。

2番は正しくありません。要素を取り除いても、容量は減らないので、capacity関数で得られる値が 0 になることはありません(最初から空だった場合は除いて)。

3番は正しいです。empty関数は、要素がなければ true、要素があれば false になります。

4番は正しいです。std::string にとって、要素がないという状態は、空文字列であるということです。

問題2 (基本★)

“Hello, C++ World.” が格納された std::string から文字列を取り除いて、“Hello, World.” にするプログラムを作成してください。


途中の要素を取り除くので erase関数を使います(本編解説)。取り除きたい要素の位置をあらわす添字と、取り除きたい要素の個数(文字数)を指定します。今回は取り除きたい文字列の位置が明確なので、決め打ちで指定してしまえます。

#include <iostream>
#include <string>

int main()
{
    std::string s {"Hello, C++ World."};

    s.erase(6, 4);
    std::cout << s << "\n";
}

実行結果:

Hello, World.

もちろん、まじめに “C++” の部分を探すという方法も取れます。ただ、ここまでのページでの知識だけでは少々やっかいです。普通は、文字列の中から文字列を探すコードを書いて対応します。

【上級】たとえば、find関数を使うと文字列の中から、特定の文字列が現れる位置を調べられます。erase関数と組み合わせて、次のように書けます。
auto index = s.find("C++ ");
if (index != std::string::npos) {
s.erase(index, 4);
}

問題3 (基本★★)

1~50 の整数が格納された std::vector を用意し、標準入力によって指定された数の倍数の値を持った要素を取り除くプログラムを作成してください。


たとえば、次のように書けます。

#include <algorithm>
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v(50);
    for (int i = 0; i < v.size(); ++i) {
        v.at(i) = i + 1;
    }

    std::cout << "Please enter the integer.\n";
    int value {};
    std::cin >> value;

    v.erase(std::remove_if(std::begin(v), std::end(v), [value](int e){ return e % value == 0; }), std::cend(v));

    for (auto e : v) {
        std::cout << e << "\n";
    }
}

実行結果:

Please enter the integer.
3  <-- 入力した整数
1
2
4
5
7
8
10
11
13
14
16
17
19
20
22
23
25
26
28
29
31
32
34
35
37
38
40
41
43
44
46
47
49
50

特定の値を持った要素を取り除くのならば、std::remove関数が使えますが、今回は倍数での一致という条件なので、std::remove_if関数を選びます(本編解説)。

std::remove_if関数に指定する条件は、ラムダ式を用いて記述します。標準入力から受け取った値が、変数value に入っているので、これをラムダ式の中から使うために、[] の内側に変数名を記述する必要があります。

std::remove_if関数は、実際には要素を取り除いていませんから、その後の取り扱いに注意が必要です。このサンプルのように、erase関数と組み合わせて、実際に要素を取り除いてしまうのが分かりやすいと思います。

問題4 (応用★★★)

標準入力からの入力によって、整数を追加・削除・一覧できるプログラムを作成してください。


たとえば、次のように書けます。

#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

int main()
{
    std::vector<int> v {};

    while (true) {
        std::cout << "Please enter the command.\n";
        std::string input_string {};
        std::getline(std::cin, input_string);

        // コマンド名のみのものを先に調べる
        if (input_string == "exit") {
            break;
        }
        else if (input_string == "list") {
            for (auto e : v) {
                std::cout << e << "\n";
            }
        }
        else {
            // コマンド名と整数に分解
            std::istringstream iss(input_string);
            std::string command {};
            int value {};
            iss >> command >> value;

            // 入力が不適切な場合、無視する
            if (iss.fail()) {
                continue;
            }

            if (command == "add") {
                v.push_back(value);
            }
            else if (command == "del") {
                v.erase(std::remove(std::begin(v), std::end(v), value), std::cend(v));
            }
        }
    }
}

実行結果:

Please enter the command.
add 10  <-- 入力した内容
Please enter the command.
add 20  <-- 入力した内容
Please enter the command.
list  <-- 入力した内容
10
20
Please enter the command.
del 10  <-- 入力した内容
Please enter the command.
list  <-- 入力した内容
20
Please enter the command.
del 20  <-- 入力した内容
Please enter the command.
list  <-- 入力した内容
Please enter the command.
exit  <-- 入力した内容

std::getline関数を使って文字列の入力を受け取ったあと、コマンド名だけで構成される “exit” と “list” を先に判定します。“exit” なら break文で無限ループを抜け出して終了。“list” なら、範囲for文を使って、すべての要素の値を出力します。

次に、std::istringstream を使って、入力された文字列を、コマンド名と整数値に分解します。“add hello” のような不適切な入力に備えて、fail関数によるエラーチェックを行っています。

コマンドが “add” だったら、セットで入力された整数を、std::vector に push_back関数で登録します。“del” の場合は、std::remove関数を使って、一致する要素を探して取り除きます。std::remove関数は実際には要素を取り除かないので、erase関数と組み合わせて、本当の意味で取り除いておきます(そうしないと、“list” で余分なものが出力されます)(本編解説)。

問題5 (発展★★★)

std::vector から任意の値を持った要素を取り除く処理を、削除のための各種関数やイテレータを使わず、添字を使ったアクセスのみで記述してください。


この問題は、自力で削除の処理を書くことの難しさを理解するためのものであって、通常なら std::remove関数を使うべきです。

たとえば次のように書けます。

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v {1, 2, 2, 3, 1, 2};

    int target_value {2};  // 取り除く値

    auto size = v.size();  // 実際の要素数を管理する
    for (int i = 0; i < size; ++i) {

        // 取り除く値か?
        if (v.at(i) == target_value) {

            // 後続の要素を1つずつ手前にずらす
            for (int j = i + 1; j < size; ++j) {
                v.at(j - 1) = v.at(j);
            }

            // 実際の要素数は1つ減った
            --size;

            // 取り除いた要素があったところは、後続の要素がずれ込んできている。
            // その要素がまた target_value と一致するかもしれないから、
            // 変数i の値を進めてはいけない。
            --i;
        }
    }

    // v.size() で要素数を取得できないので、自力で管理している要素数(size)を使う。
    // 同じ理由で、範囲for文や、イテレータを直接使うこともできない。
    for (int i = 0; i < size; ++i) {
        std::cout << v.at(i) << "\n";
    }
}

実行結果:

1
3
1

動作については、詳細にコメントを入れているので、そちらもご覧ください。

後続の要素をずらさなければならず、その部分での添字の操作が少々難しい部分です。範囲外アクセスしないように注意が必要です。また、このとき、std::vector の末尾にあった要素が増えることがイメージできるでしょうか? たとえば {0, 2, 4} から 2 を取り除いたとすれば、{0, 4, 4} になります。

要素を取り除いた周回では、変数i を進めてしまうと、取り除くべき要素を見過ごす恐れがあるため、ここも難しいところです。++i を回避する方法もありますが、ここでは --i によって、インクリメントを打ち消すようにしています。

削除の操作後は、要素数を v.size() のように問い合わせられません。実際には要素数が減っていないからです。


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る