nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

C++のコードレビューまとめ

C++のコードレビューのまとめを行います

派生される可能性があるクラスの場合、デストラクタにvirtualをつけよう

ポリモーフィズムを利用すべく作った基底クラスのデストラクタはvirtualが必要であるためです。 基底クラスのデストラクタにvirtualを付けない場合、派生クラスのデストラクタが呼ばれずメモリリークが起きる

ルール厳守なもの

[Guilty Code]

class Test
{
    Test(){}
}

[Correct Code]

class Test
{
    Test(){}
    virtual ~Test() = default;
}

std::cinとstd::coutへのアクセスは副作用があるため呼び出す場所は限定すること

std::cinとstd::coutは副作用があるため呼び出す場所は限定すること (main関数内を強く推奨)

不要な#include文は削除しましょう

使用していない#include文は削除しましょう

コードを意味のあるまとめまりにしよう

単体テストを活用しよう

#include <cassert>
#include <string>
#include "Main.h"

bool isIPAddr(const std::string& inputConsoleLine);

class Test
{
public:
    Test()
    {
        ExecuteTest();
    }
private:    
    static void ExecuteTest()
    {
        // ---- 正常パターン単体テスト ---- //
        // 正規表現ロジック上の下限値・上限値確認
        assert(isIPAddr("0.0.0.0"));
        assert(isIPAddr("255.255.255.255"));
        assert(isIPAddr("9.9.9.9"));
        assert(isIPAddr("10.99.10.99"));
        assert(isIPAddr("100.199.100.199"));
        assert(isIPAddr("200.249.200.249"));
        assert(isIPAddr("250.255.250.255"));
        
        // ---- 異常パターン単体テスト ---- //
        // 前置きの0は失敗判定させる
        assert(!isIPAddr("00.00.00.00"));
        assert(!isIPAddr("000.000.000.000"));
        assert(!isIPAddr("0000.0000.0000.0000"));
        
        // 異常値を与える
        assert(!isIPAddr("."));
        assert(!isIPAddr(","));
        assert(!isIPAddr("2147483647"));
        
        // コロンを挿入する位置を変更する
        assert(!isIPAddr("12.3..4"));
        assert(!isIPAddr("..1.2.3.4"));
        assert(!isIPAddr("1.2.3.4.."));
        
        // 誤った桁数を与える
        assert(!isIPAddr("1"));
        assert(!isIPAddr("1.2"));
        assert(!isIPAddr("1.2.3"));
        assert(!isIPAddr("1.2.3.4.5"));
        
        // 範囲外の値を与える
        assert(!isIPAddr("-1.-1.-1.-1"));
        assert(!isIPAddr("256.256.256.256"));
        assert(!isIPAddr("2147483647.2147483647.2147483647.2147483647"));  // __int32の+側最大値
    }
};

const static Test Test;

intよりint32_tを使おう

型エイリアス宣言を用いて、クラス側でクラスで使用する型を決定することで、ソフトウェアの堅牢性を向上させる

型エイリアスは以前に定義された型を参照する名前です (typedef と同様です)

以下のように型を別名で宣言します。 using TestData = std::array<int, 10>;

【修正前】 [Main.cpp]

#include <iostream>
#include "Test.hpp"

int main(void)
{
    std::array<int, 10> testData{1,2,3,4,5,6,7,8,9,10};
    
    Test test{testData};
    
    for (const auto& i : test.GetMultiplyData(5))
    {
        std::cout << i << std::endl;
    }
}

[Test.hpp]

#include <array>
#include <vector>
#include <algorithm>    // std::transform

class Test
{

private:
    std::array<int,10> m_inputData;

public:
    Test(std::array<int,10> inputData) : m_inputData{inputData}
    {}
    
    // 倍数したデータをvectorで取得する
    std::vector<int> GetMultiplyData(int times)
    {
        std::vector<int> resultData;
        
        std::transform(std::begin(m_inputData), std::end(m_inputData), std::back_inserter(resultData), [times](int data){return data * times;});
        
        return resultData;
    }
};

【修正後】 [Main.cpp]

#include <iostream>
#include "Test.hpp"

int main(void)
{
    Test::TestData testData{1,2,3,4,5,6,7,8,9,10};
    
    Test test{testData};
    
    for (const auto& i : test.GetMultiplyData(5))
    {
        std::cout << i << std::endl;
    }
}

[Test.hpp]

#include <array>
#include <vector>
#include <algorithm>    // std::transform

class Test
{
    // クラス側でクラスで使用する型を決定することで、ソフトウェアの堅牢性を向上させる
public:
    using TestData = std::array<int, 10>;
    
private:
    TestData m_inputData;

public:
    Test(TestData inputData) : m_inputData{inputData}
    {}
    
    // 倍数したデータをvectorで取得する
    std::vector<int> GetMultiplyData(int times)
    {
        std::vector<int> resultData;
        
        std::transform(std::begin(m_inputData), std::end(m_inputData), std::back_inserter(resultData), [times](int data){return data * times;});
        
        return resultData;
    }
};

リンク

pandasを使って株価情報を取得しましょう

はじめに

pandasを使うと、webページの表(tableタグ)のスクレイピングが簡単にできます。

環境

  • Windows version : 1903 (Windows 10 Home)
  • conda version : 4.7.12
  • conda-build version : 3.18.8
  • python version : 3.7.3.final.0
  • selenium : 3.141.0

手順

(1) 必要なモジュールをインストールします

conda install pandas

conda install beautifulsoup4

conda install html5lib

(2) コードをjupyter notebookに入力します

import pandas as pd
tables = pd.read_html('https://info.finance.yahoo.co.jp/ranking/?kd=4', flavor='bs4', header=0)

first_five_data = tables[0].head()
last_five_data = tables[0].drop(len(tables[0])-1).tail()

display(first_five_data.append(last_five_data))

f:id:nprogram:20191022231420p:plain

コード説明

pandasのread_htmlメソッドを用いて、webページの表(tableタグ)のスクレイピングを行います。

上記のメソッドを呼び出すとページ内の表をすべて取得しDataFrameのリストとして返します。今回の場合は表が1つしかないため、tables[0]で表データを参照できます

read_html型の戻り値は、リスト(<class 'list'>)を作成します。引数はの意味は以下のとおりです。

# flavor
## 解析に使用するパッケージ種別
# 引数 : header
# 表タイトルに指定する行番号
tables = pd.read_html('https://info.finance.yahoo.co.jp/ranking/?kd=4', flavor='bs4', header=0)

以下のコードで表の最初の5行分のデータを表示します。

display(tables[0].head())

f:id:nprogram:20191022223757p:plain

以下のコードで表の最後の5行分のデータを表示します。しかし、最終行にゴミデータが入っています。

display(tables[0].tail())

f:id:nprogram:20191022223923p:plain

上記のように最終行にゴミがある場合は、dropメソッドを呼び出し、最後の行を指定して削除した後に、最後の5行を取り出せば問題ありません。

tables[0].drop(50).tail()

f:id:nprogram:20191022230954p:plain

上記のコードは以下でも代用できます

display(tables[0].drop(len(tables[0])-1).tail())

csvファイルに保存・csvファイルから読み込み

次に取得した情報をcsvファイルに保存しましょう。csvファイルに保存することで、取得したデータを永続化できます。

前回で、dropメソッドで不要な行を削除しましたが、今回は別な方法で削除します。

tables[0][:-1].to_csv("./stock_data_list.csv")

df_csv = pd.read_csv('./stock_data_list.csv', index_col=0)
display(df_csv.head().append(df_csv.tail()))

以下のコードで、一番目のテーブルの最後の行を除去して、csvファイルとして保存します。

[:-1]で最初の行から最後から一行を意味します。ここで、0行目はタイトル行です。

次のread_csv関数で、0行目をインデックス行(タイトル行)(index_col=0)と判断して、csvファイルをリードして、DataFrameに変換します。

Yahooサイトから、株価情報を取得します

Yahooサイトはスクレイピングでの高速アクセスを禁止しています。

そのため、高速アクセスを禁止するため、一度の処理の間にはウェイト(スリープ)を必ず入れたいと思います

参考リンク

あとがき

株価の予想をAIを用いて実行できるようにしたいです。まずは、スクレイピング(ネットから情報を取得)をマスターしたいと思います。

ただ、スクレイピングは国内サイトの場合、規制が厳しいです。海外のサイトから株価情報を取得することも考慮したいと思います。

機械学習で未来を予測する - scikit-learn の決定木で未来の株価を予測 - Qiita

pythonで東証から株価をAPI取得して、データをChartに表示させる - Qiita

Anaconda3で作成した仮想環境上で、Seleniumを使ってブラウザを操作する [Windows]

はじめに

PythonとSeleniumを使ってブラウザを操作する作業を行いたいと思います。

Pythonのバージョンやインストールするモジュールのバージョンによって、プログラム動作が変わる可能性があります。

そのため、仮想環境上でPythonやSeleniumのモジュールをインストールして実行したいと思います。

環境

  • Windows version : 1903 (Windows 10 Home)
  • conda version : 4.7.12
  • conda-build version : 3.18.8
  • python version : 3.7.3.final.0
  • selenium : 3.141.0

手順

(1) Anaconda3をインストールする

(2) Anaconda3で仮想環境を作成する

Anaconda3のGUIから行う方法とAnaconda Promptから行う方法があります。 (3) Pythonをインストールする

(4) Pythonのインストールを確認する

(5) Seleniumをインストールする

上記の手順(3) - (5) は、Anaconda Promptでは以下の手順で実施可能です。

  • conda create --name myenv python=3.7

    • 仮想環境作成 (仮想環境名をmyenv、使用Python versionを3.7にする) (※Seleniumを使う場合はPython3.7が必要と思われます)
  • activate [仮想環境名]

    • 仮想環境を切り替える
  • conda install selenium

    • Seleniumをインストールする

(6) WebDriverをインストールする ダウンロードしてきた実行ファイル(chromedriver.exe)をPC内の任意の場所に保存してください。(本コード例ではF:/tool/ChromeDriver_77_0_3865_40/chromedriver.exeに保存)

(7) jupyter notebook上でコードを動作させる

早速ブラウザを自動操作しましょう

以下のコードをjupyter notebook上で動作させます。

コード例

from selenium import webdriver
 
driver = webdriver.Chrome("F:/tool/ChromeDriver_77_0_3865_40/chromedriver.exe")
driver.get("http://www.yahoo.co.jp")
 
elem_search_word = driver.find_element_by_id("srchtxt")
elem_search_word.send_keys("test")

elem_search_btn = driver.find_element_by_id("srchbtn")
elem_search_btn.click()

コード説明

  • 1行目:webdriverモジュールをインポートしています
  • 3行目:WebDriverのパスを指定してChromeを起動します
  • 4行目:Yahooのページをブラウザで開きます
  • 6行目は入力したいテキストフィールドの要素を取得しています。このページのHTMLソースを見ると、検索語を入力するテキストフィールドのIDがsrchtxtということがわかるので、find_element_by_idメソッドを使ってこの要素を取得します。
  • 7行目は取得した要素にsend_keysメソッドを使ってtestという文字を入力しています。同様にして8行目は検索ボタン要素の取得、9行目は検索ボタンをクリックします

自動テストイメージ

f:id:nprogram:20190918172354p:plain

参考

リンク

あとがき

比較的、簡単にAnaconda3で作成した仮想環境上で、Seleniumを使ってブラウザを操作できました。

Anaconda環境でパッケージの管理を行う場合は、可能な限りconda installコマンドでパッケージをインストールすることをお勧めします。

pip installでもパッケージのインストールは可能ですが、Anaconda環境と競合する可能性があるため。

template関数でsplit関数を実装する (空白文字列区切り)

template関数でsplit関数を実装する (空白文字列区切り)

template関数を使用することで、複数の型を同じ関数で扱うことができるようになります。

使い方

関数呼び出し時には、通常の関数呼び出しとは異なり、型を指定する必要があります。

const std::vector<std::string> result1 = SplitWords<std::string>(input1);

コード例1

#include <iostream>
#include <vector>
#include <sstream>


template <class T>
std::vector<T> SplitWords(const std::string &line)
{
    std::vector<T> words;
    std::istringstream ss{line};
    std::string buf;
    
    T t;
    int count = 0;
    
    while(ss >> t)
    {
        words.emplace_back(t);
        count++;
    }
    
    std::cout << "Loop count : " << count << std::endl;
    
    return words;
}

int main (void)
{
    const std::string input1 = "abc\n def\n\n ghi\n";
    const std::vector<std::string> result1 = SplitWords<std::string>(input1);
    std::cout << "result 1 size : " << result1.size() << std::endl;
    
    for (const auto & item : result1)
    {
        std::cout << item << std::endl;
    }

    const std::string input2 = "12.3\n 45.6\n 78.9\n";
    const std::vector<double> result2 = SplitWords<double>(input2);
    std::cout << "result 2 size : " << result2.size() << std::endl;
    
    for (const auto & item : result2)
    {
        std::cout << item << std::endl;
    }
    
    const std::string input3 = "1\n 222\n\n 333\n 44\n\n\n";
    const std::vector<double> result3 = SplitWords<double>(input3);
    std::cout << "result 3 size : " << result3.size() << std::endl;
    
    for (const auto & item : result3)
    {
        std::cout << item << std::endl;
    }
    
    EXIT_SUCCESS;
}
Loop count : 3
result 1 size : 3
abc
def
ghi
Loop count : 3
result 2 size : 3
12.3
45.6
78.9
Loop count : 4
result 3 size : 4
1
222
333
44

コード例2(istringstreamのeofメソッドを使った実装)

ifs.eof()の場合は、入力文字列に改行コードがある場合は、ループカウンタが余計に一回実行された。 この方法では、行中の改行文字列'\n'は正しく認識できないため注意してください。

#include <iostream>
#include <vector>
#include <sstream>


template <class T>
std::vector<T> SplitWords(const std::string &line)
{
    std::vector<T> words;
    std::istringstream ss{line};
    std::string buf;
    
    int count = 0;
    
    while(!ss.eof())
    {
        T t;
        ss >> t;
        words.emplace_back(t);
        count++;
    }
    
    std::cout << "Loop count : " << count << std::endl;
    
    return words;
}

int main (void)
{
    const std::string input1 = "abc\n def\n\n ghi\n";
    const std::vector<std::string> result1 = SplitWords<std::string>(input1);
    std::cout << "result 1 size : " << result1.size() << std::endl;
    
    for (const auto & item : result1)
    {
        std::cout << item << std::endl;
    }

    const std::string input2 = "12.3\n 45.6\n 78.9\n";
    const std::vector<double> result2 = SplitWords<double>(input2);
    std::cout << "result 2 size : " << result2.size() << std::endl;
    
    for (const auto & item : result2)
    {
        std::cout << item << std::endl;
    }
    
    const std::string input3 = "1\n 222\n\n 333\n 44\n\n\n";
    const std::vector<double> result3 = SplitWords<double>(input3);
    std::cout << "result 3 size : " << result3.size() << std::endl;
    
    for (const auto & item : result3)
    {
        std::cout << item << std::endl;
    }
    
    EXIT_SUCCESS;
}
Loop count : 4
result 1 size : 4
abc
def
ghi

Loop count : 4
result 2 size : 4
12.3
45.6
78.9
78.9
Loop count : 5
result 3 size : 5
1
222
333
44
44

リンク

*【C/C++】 streamクラスのeofメンバ - http://murakan.cocolog-nifty.com/blog/2009/12/cstreameof-7401.html

Modern_C++_leet文字列変換

#include <string>
#include <iostream>
#include <map>
#include <algorithm> // transform
#include <vector>
#include <numeric> //iota, accumulate

// leetマップ変数 (グローバル変数)
std::map<char, char> leet {
    {'A', '4'},
    {'E', '3'},
    {'G', '6'},
    {'I', '1'},
    {'O', '0'},
    {'S', '5'},
    {'Z', '2'}
};
        
/**
 * @fn
 *  入力文字列をLeet文字列に変換する関数
 * @brief 要約説明
 * @param input 入力文字列
 * @return  Leet変換後の文字列
 * @detail 変換規則はグローバル変数に記載したleetマップ変数に記載
 */
std::string ConvertLeetString(std::string input)
{
    
    std::vector<char> result;
    
    // Map(変換)
    // 入力文字列を1文字ずつLeet文字列に変換する。変換結果はcharを格納するvectorに格納する
    std::transform(input.begin(), input.end(), std::back_inserter(result),
    [=](char item) -> char 
    {
        std::map<char, char>::iterator it;
        
        it = leet.find(item);

        if (it != leet.end())
        {
            // 探索文字(item)と等価なキーがleetマップ変数にある場合はそのvalue値を返す
            return leet[item];
        }
        return item;
    });
    
    // Accumulate(集約)
    return  std::accumulate(result.begin(), result.end(), std::string());
}

int main(void)
{
    // インプット文字列を取得
    std::string input;
    std::cin >> input;
    
    // ConvetLeetStringメソッドを保有するUtilityクラスにしたほうがよかった
    std::string result = ConvertLeetString(input);
    
    std::cout << result << std::endl;

    return EXIT_SUCCESS;
}

UML学習まとめ

UML学習全般を記載する記事です

関連端名(ロール名)

関連の両端にそれぞれのクラスのロール名を記述することができます。

ロール名は相手方クラスとの関連における自分のクラスの役割(role)を示します。

以下の記事を参照ください。

以下の記事を参照ください。

関連名

関連線の中央に関連の名前を記述することができます。一般的には動詞です。

社員クラスと会社クラスがあり、間を関連線でつなぎます。関連名は主語にするクラスで変わります。

  • 主語を社員クラス側にすると、関連名は「勤務する」
  • 主語を会社クラス側にすると、関連名は「雇用する」

以下の記事を参照ください。

UMLはベンダー資格試験があります

理解度確認のために試験を受けてみるもいいかもしれません。

UMLツール使い方

モデル駆動開発でUMLは使用されます

モデル駆動開発(MDD:Model-Driven Development)」(注:MDDはOMGの商標になっています)は、分析・設計・実装・テストといった開発の成果をモデルとして作成し、モデル変換を繰り返し適用することによってプロセスを進める開発手法

Observerパターン [C#] [pull型]

Observerパターンとは

あるオブジェクトの状態に依存するオブジェクトがあるとき、状態の変化をそれらのオブジェクトに依存せずに自動的に通知するパターン

push型とpull型

Observerパターンでは,push型とpull型があります。違いは以下のとおり。

  • push型 : updateの引数に変更された状態を渡すことで、Observerに通知する
  • pull型 : updateを呼び出されたタイミングでObserverがObservableに状態を問い合わせる

本記事では、pull型のObserverパターンについて説明します。

参考

Observerパターンのpull型のクラス図

f:id:nprogram:20190801081705p:plain

コード

using System;
using System.Collections.Generic;

namespace Observer_Pattern_push_type
{
    class Program
    {
        static void Main(string[] args)
        {
            Person model = new Person();
            View view = new View();
            view.DataSource = model;    // データバインド

            model.Id = 123;
            model.Name = "Ichiro";
        }
    }

    interface IObserver
    {
        void Update(Observable observer);
    }

    interface ISubject
    {
        void Add(IObserver item);
        void Delete(IObserver item);
        void Notify();
    }

    /// <summary>
    /// テキスト表示用のUI部品 (ダミー)
    /// </summary>
    class TextControl
    {
        public string Text
        {
            set { Console.WriteLine("TextControl is updated: {0}", value); }
        }
    }

    /// <summary>
    /// 監視するクラス
    /// </summary>
    class View : IObserver
    {
        TextControl nameText = new TextControl();
        TextControl idText = new TextControl();

        public void Update(Observable item)
        {
            var person = item as Person;

            if (person != null)
            {
                nameText.Text = person.Id.ToString();
                idText.Text = person.Name;
            }
        }

        public Person DataSource
        {
            set { value.Add(this); }
        }
    }

    /// <summary>
    /// 監視されるクラス
    /// </summary>
    abstract class Observable : ISubject
    {
        private List<IObserver> _observers = new List<IObserver>();

        public void Add(IObserver item)
        {
            _observers.Add(item);
        }

        public void Delete(IObserver item)
        {
            _observers.Remove(item);
        }

        public void Notify()
        {
            _observers.ForEach(observer => observer.Update(this));
        }

        protected void Update() // 更新イベント
        {
            Notify();
        }
    }

    /// <summary>
    /// Modelクラス
    /// </summary>
    class Person : Observable
    {
        private int _Id;
        public int Id
        {
            get { return _Id; }
            set
            {
                if (value != _Id)
                {
                    _Id = value;
                    Update();
                }
            }
        }

        private string _Name;
        public string Name
        {
            get { return _Name; }
            set
            {
                if (value != _Name)
                {
                    _Name = value;
                    Update();
                }
            }
        }
    }
}
TextControl is updated: 123
TextControl is updated:
TextControl is updated: 123
TextControl is updated: Ichiro