SOFT SKILLSの自分メモ

学習すること

  • ソフトウェアエンジニアは絶えず勉強が必要で(SEに限らずだけれど)、効率的な勉強法が重要となる。
  • 効率よい学習とは、好奇心を持って行動を起こすこと。そして人に教えると知識が補強される。
  • だから、まず最速で試すポイントまで立つことを意識する。そして、それをブログなどで自分の言葉でまとめる。
  • LDLT方式(Learn-Do-Learn-Teach)を意識する。
  • 本は最初から順番に読むんでも何も出ない。
  • 具体的には次のステップを踏む(一部省略)。
    1. 全体像をつかむ:予め把握しておくことで、そもそも挫折しないか、適切なテーマかを判断しておく。
    2. スコープを決める:具体的に学習可能な問題に限定する。一度に学べることは一つ、欲張らない。
    3. 成功の基準を決める
    4. 使い始められるようにするための学習(Learn 1st)
    5. 遊ぶ/使う(Do):遊びながら、使いながら疑問点をリストアップ(好奇心)
    6. 疑問点を学ぶ
    7. 教える(自分の言葉で纏める):人に説明したり、ブログを書いたり。

生産性について

  • 引用されているスティーブン・キングの言葉が印象的(とにかくやれ、って感じの言葉)
  • 集中こそすべて。集中はスイッチみたいに入らない。やりだすとじわじわ入る。だから、最初の何分か我慢してとにかくやるしかない。
  • 割り込みを許さない。バッチ処理を活用する(メールの返信とか)。
  • マルチタスクは本当に並列化するものにだけ適用する。じゃないと実際はコンテキストスイッチが入ってタスク切り替えている。
  • ポモドーロは予め必要なポモドーロ数の見積もりと、実際にこなしたポモドーロ数をトラックすることが重要。自分の生産性の可視化。
  • 具体的な行動
    • 4半期の目標はある程度固まったもの。
    • 月次の計画は正確に立たない。あくまでも何日(何時間)使えるのか?を可視化する。
    • 週次計画はある程度正確に。ルーチンも入れる。各タスクは何ポモドーロか?を見積もる。
    • 日次は2時間以内(4ポモドーロの1セット)に終わるタスクの実行計画を作ることが肝。

   

Pythonの開発環境 on Emacs

前提

  • 最低限として文法チェック、補完、REPLが使えれば良い。
  • ipython notebook(Jupyter)は使わない。 (notebookはやっぱり慣れないのと、別にmarkdownは別ファイルに書けば良いし、図もEmacs内に埋め込み表示しなくてREPLでやれば良いように思う。)
  • virtualenvを考慮してLintして欲しいのでelpyを使う。注意はpython3用にしておくこと、elpy自体だけで補完、lintする訳じゃないようなのでそれは結局入れる。

設定

  • まず各種インストール
# aptで入れるもの
$ sudo apt-get python3-pip ipython3 python3-tk
$ sudo ln -s /usr/bin/ipython3 /usr/bin/ipython

# Caskで入れるもの(Cask自体も)
$ curl -fsSkL https://raw.github.com/cask/cask/master/go | python
$ cd .emacs.d
$ cask init
$ export PATH=/home/nobunaga/.cask/bin:$PATH # add it in .bashrc
$ vim Cask # elpy, company, campany-jedi, flycheck
$ cask install
$ pip3 install rope jedi autopep8 flake8
  • 次に、init.elに下記を追加。
;; init.el
(elpy-enable)
(elpy-use-ipython)
(setq elpy-rpc-python-command "python3")
(setq elpy-rpc-backend "jedi")
(when (require 'flycheck nil t)
  (setq elpy-modules (delq 'elpy-module-flymake elpy-modules))
  (add-hook 'elpy-mode-hook 'flycheck-mode))

最低限の使い方

# MISC
M-x elpy-config : 設定の確認

# REPL
C-c C-z : REPLバッファに移動
C-c C-c : バッファをREPLに送信
C-M-x   : 現在のトップレベルのクラスまたは関数を送信
C-ENTER : カレントラインをREPLに送信

;移動
M-. : 定義にジャンプ
M-* : ジャンプ元に戻る
M-, : M-*のジャンプ前に戻る
C-x 4 M-. : 別ウィンドウ開いて定義にジャンプ

Cmakeのメモ

 使い方

  • CMakeList.txtにMakefileの生成ルールを記述.
  • CMakeLists.txtを用意したディレクトリを指定してcmakeを実行するとMakefileが生成される.後は通常通りにmakeでコンパイル
  • ただし,cmakeするとCMakeCache.txtやら色々できてディレクトリが汚れるのでソース外ビルドでやるのが良い.やりかたは,buildディレクトリなりを一旦作って,そのディレクトリに移動して"cmake ..“すれば,buildディレクトリにビルドセットアップ一式が生成される.そこでmakeする.相対パスを使っている場合は注意する.

CMakeList.txtの書き方(基本)

# cmakeのバージョン(必須)
cmake_minimum_required(VERSION 2.8) 

# コンパイラ指定
set(CMAKE_CXX_COMPILER /usr/bin/clang++ )

# コンパイルオプション
set(CMAKE_CXX_FLAGS "-Wall")

# -I : include pathを順に追加
include_directories(/usr/share/path1)
include_directories(/usr/share/path2)

# -D : 定義を追加
add_definitions(-DDEBUG)

# -L : ライブラリ検索パスを追加
link_directories(/usr/share/lib)

# 出力する実行可能形式と,それが依存するソースファイル.(ヘッダは自動解析)
add_executable(Main main.cpp src1.cpp src2)       

# -l : リンクライブラリ (add_executableよりも後ろに書く) -lm -lglut を指定する
target_link_libraries(Main glut m)

複数のディレクトリを一括コンパイル

- root_dir
 - CMakeList.txt   # add_subdirectory(hoge), add_subdirectory(foo)を記述しておく.
 - hoge
   - CMakeList.txt # hoge.cppをコンパイルする記述をしておく.
   - hoge.cpp
 - foo
   - CMakeList.txt # foo.cppをコンパイルする記述をしておく.
   - foo.cpp

cmakeのバージョン

cmake_minimum_required(VERSION 2.8)

find_package(GLUT) find_package(OpenGL)

set(CMAKE_CXX_FLAGS “-g -Wall”) add_executable(Main main_openGL.cpp) target_link_libraries(Main ${GLUT_LIBRARY} ${OPENGL_LIBRARY} m) ||<

特定のアプリ(OpenGL, OpenCV, Qt)などはcmake側で予め用意されている. 用意されているアプリは/usr/share/cmake/Module/Find.cmakeにある.

コンテナからの要素削除

ひさしぶりに書くとこんな事も忘れてしまっていた.下記のコードでセグメンテーションフォールトが出た. でも,そもそもremove_ifをこんな時には使うようだ.

std::list<int> lis {0,1,2,3,4,5,6,7,8,9};
for (auto itr=lis.begin(); itr!=lis.end(); ++itr) {
  if (*itr == 2)
    lis.erase(itr);
}

どこで出るかと言うと,eraseした後のforループの更新(++itr). lis.erase(itr)でitr(何かしらのアドレスitr)が指す先が消される.と,そのポインタitrもなくなって++itrが出来なくなる? ならばということで,

std::list<int> lis {0,1,2,3,4,5,6,7,8,9};
for (auto itr=lis.begin(); itr!=lis.end(); ) {
  if (*itr == 2)
    lis.erase(itr++); // auto t = itr; itr = itr+1; lis.erase(t)と同じことになる.
  else
    ++itr;
}

もうforっぽくないので,whileで似たように書ける.

std::list<int>::iterator itr = v.begin();
for (itr != v.end()) {
    if (*itr == 2) {
        itr = lis.erase(itr); // eraseの返り値は次のポインタ
    } else ++itr;
}

ポリモルフィズム

目的

  • オブジェクトによって振る舞いを変える.

方法

  • 子クラスのポインタを親クラスのポインタに代入するのがミソ.(子クラスは親クラスのポインタに代入可能.逆はだめ.)
  • それを使う側は親クラスのポインタを引数なりにして受け取って,親クラスのメソッドを呼ぶ.
  • その時に,親クラスでvirtual宣言しておけば,親クラスのポインタであっても,実態が子クラスのポインタである場合は子クラスのメソッドを実行する.
  • これで,分岐したい処理の分だけ子クラスで分岐できる.
  • virtual付けたときと付けないときの違いは下記を参照.virtual付けていないと親クラスのメソッドが実行される.
class Parent {
protected:
  int x_;
public:
  int get_x() const {return x_;}
  void set_x(int x) {x_ = x;}
  void non_virtual_func() {cout << "I'm parent.\n";}
  virtual void virtual_func() {cout << "I'm parent.\n";}
  Parent(int x): x_(x) {}
};

class Child : public Parent {
public:
  void non_virtual_func() {cout << "I'm child.\n";}
  virtual void virtual_func() {cout << "I'm child.\n";}
public:
  Child(int x) : Parent(x) {}
};

int main()
{
  //std::make_unique<Parent> p = std::make_unique<Child>(1);
  Parent* p = new Child(1);
  cout << "non virtual function call: ";
  p->non_virtual_func();
  cout << "virtual function call: ";
  p->virtual_func();

  return 0;
}

テンプレートのメモ

基本

型推論

  • 関数テンプレートの場合は型推論されるので,実体化の際に明示的に型を書かなくても良い.
  • クラステンプレートの場合には推論されないので注意.

特殊化

  • テンプレート関数,テンプレートクラスがある時に特定の型でだけ別の動きをしたい場合には,その型専用のテンプレートを作れば特殊化される.
  • あるテンプレートを具象化すること(普通にテンプレートの実態?を作ること)も特殊化と言う場合があって混乱するので注意.
#include <iostream>
#include <string>

using namespace std;

template <typename X>
X add(X a, X b){ return a+b;}

// 特殊化
template<>
double add(double a, double b) {return a+b;}

int main()
{
  cout << add<int>(1, 2) << "\n";
  cout << add(1, 2) << "\n";                // 型が推論される
  cout << add<float>(1.0, 2.0) << "\n";
  cout << add(1.0, 2.0) << "\n";            // 同様
  cout << add<string>("Oda", "Nobunaga") << "\n";
  //cout << add("Oda", "Nobunaga") << "\n"; // これはエラー.char*になって'+'が無いと怒られる.
  return 0;
}

値引数,値パラメタ

  • テンプレートの宣言,定義に型ではなくて値を書く例を見る.例えば下記.hoge1, hoge2はそれぞれNが1,2で実態が作られる.
  • 実体化するインスタンス毎に設定値を変えたい場合に使うのかな?とも思うけどテンプレートでやらなくても普通にコンストラクタでやれば?と思う.どういう時に使うんだろう?コンパイル時に設定値も分けて実態を作りたい時かな?
template<int N>
class Hoge {
private:
  int m_ = N;
public:
  int get_m() const {return m_;}  
};

int main()
{
  Hoge<1> hoge1;
  Hoge<2> hoge2;

  cout << hoge1.get_m() << "\n";
  cout << hoge2.get_m() << "\n";
  return 0;
}

引数を柔軟に取るためのテンプレート

  • 何という名前のテクニックなのか知らないけどたまに見る.
  • ある関数やメンバ関数に与える引数を,その引数が適切に定義されていれば型の正式な名前に依らずに受け取りたい時とかに使える.
  • 例えば,色々な人(Human)の定義が作り得るとして,それらの人全般を受け取るような関数を作る時.下記例は,Human型はsay_helloというメンバ関数さえあれば,どんな型のものであろうと渡すことが出来る.実際に,NobunagaもIeyasuもHumanとは関係ないけど,say_helloがあるのでHumanの満たすべき性質は満たせている.
template<typename Human>
void call_say_hello(Human human) {human.say_hello();}

class Nobunaga {
public:
  void say_hello() {cout << "I'm Nobunaga.\n";}
};

class Ieyasu {
public:
  void say_hello() {cout << "I'm Ieyasu.\n";}
};

int main()
{
  Nobunaga nobunaga;
  Ieyasu   ieyasu;
  call_say_hello(nobunaga);
  call_say_hello(ieyasu);
  return 0;
}

二分探索 バイナリサーチ

  • 簡単でかつ効果が高いのでよく使うサーチ.
  • 意外と境界条件でバグりがち.味噌は,右側を配列の外において置く.
#include<iostream>
#include<vector>
using namespace std;
int main() 
{
  int d, n;
  cin >> d;
  cin >> n;
  vector<int> v(n);
  for (int i=0; i<n; ++i) cin>>v[i];

  int l=0, r=v.size(), m=(l+r)>>1;
  while (l<r) {
    if (d == v[m]) {
      cout << "find: " << m << "\n";
      return 1;
    }
    else if (d < v[m]) 
      r=m;
    else
      l=m+1;
    m=(l+r)>>1;
  }
  cout << "not found\n";
  return 0;
}