渡るネットは嘘ばかり

元文系、米国大学院CS修士号持ちITエンジニア。自称エンジニアが撒き散らすゴミを少しでもキレイにしたい

プログラミングは奥が深い

お久しぶりです。3ヶ月のモニター2期プログラミング教室が終わりました。その辺をメインにあれこれ。

本題の前に、仮想通貨の話。ちょくちょく仮想通貨(海外では暗号通貨と言われますが)いじってないの?的なことを聞かれますが、あれは博打なのでやりません。まぁ、初期に買って去年くらいに売るなら手を出しても良かったかも知れませんが、投資で良く言われる、素人が買いだしたら相場は終い、と言うのもあり、今更手を出す気になりません。先行して多額の投資をしている金持ちは売り抜けるのにも同数の買いがないと利確できないこともあり、素人が増えてボリュームが増えたら少しずつ売って逃げたりします。そもそも、仮想通貨だと取引量が少なすぎて金持ちには向かないんですよね。数億買い入れたら買値上がっちゃうし、売りでも値崩れの引き金になりかねない。なので、大きな買いを入れられる銘柄、もしくはタイミング(買いが膨らんだところで売ったり、逆に成行じゃない売りが膨らんだところで買ったり)。基本的に仮想通貨の現状じゃ、機関とか大金持ちには向いてません。ハッキングで出金できなくなる危険性もある。来ても仕手くらい。仕手が来ると値は上がりますが、たいてい素人はハメられます。買ってる金持ちって投資素人の芸人とか情弱ビジネス界(情弱を鴨に広告収入とかコンテンツ売ってる)のトップスターのイケハヤとか与沢翼でしょ。與沢君後輩だけど、半夜間学部で情報処理系の共通授業で俺が授業中5分しかちゃんとやらずにA取ったプログラミングの授業でC取ってるし、ネットビジネスで成功した人、なんだけど、IT系は名乗らんで欲しい。インターネット使ってるだけでエンジニアリングではない…というか、情報技術って広すぎるんだよなぁ。そういう技術関係ないアフィリエイターとかまでIT系名乗るから新しい業種名が必要な気がする。與沢君が出てきた時点で胡散臭いグレーゾーンのものな確率が高いので撤退したほうがいい。金持ちとか機関が来ないって言うことは、底堅い上昇に繋がりにくくかんたんに崩れるってことです。

なんにせよ、ファンダメンタルとかない、チャートと場の空気だけで予想する博打な上に相当数を上位数%に握られてたり、マイニングの売りあったりとか、指標のないものを買うのは博打です。まともな人のすることじゃない。買ってる中の情弱の方々はブロックチェーンがすごい、中央支配されない、価値が上がって当たり前、とか思ってるかも知れませんが、ブロックチェーンは取引の正当性を保障するだけで、アカウント乗っ取られたら何の効力もないし、むしろ、その取引すら正当化される気が。コインチェックは保障するみたいですが、海外では保障しないハッキングされたところもありますし。

中央がコントロールしないのはいい面も悪い面もあって。不当な介入はないですが、通貨の水準が決まらないんですよね。国家の通貨っていうのは国力が反映されるので、大きく暴騰暴落しても妥当な範囲です。明確な価値基準がある。だって、それがなければ、海外でPS4を1$で買えたり、フェラーリを日本で50万で買えたり、iPhoneXが100万とかになりうるわけです。国の通貨として認定されて国の管理の下にあるというのは国がある程度の問題を保障できるということだったりもするわけです。

というわけで、仮想通貨・暗号通貨は博打。元の金がなくてなくなってもいい人は自己責任で、という感じ。元銭あるなら、株とか税金や手数料低い安定したところで確実に勝った方がいい。最近だと、1,2ヶ月に1度暴落あるので、そこで買ったら結構勝てます。銘柄ある程度ウォッチしてれば安い水準もわかるから、そこまで我慢すればたいてい負けません。中長期でできる余剰資産あるなら、ファンダメンタルズ分析が最終的には強い。銘柄選べば月に10%位は難しくないです(去年は月に平均5〜10%儲けました)。

特に去年のいつ買っても待てば儲けられる相場で利確して今年入って暴落で損切りとか強制決済させられた人達は結構な人数で去年の億り人から6月位に刑務所送りになってそうですね…。国税庁結構本気だと思うよ。

3ヶ月の第2期が終わって

11月からの3ヶ月ですが、6人(3人ずつの2クラス)で開始しましたが、最終的に2ヶ月目で1人脱落、3ヶ月目はチーム課題を出したので、課題提出がほとんどなかった1名は打ち切らせて頂きました。元々課題やアンケートのフィードバック(以下FB)を前提に安く提供する話だったのですが、ちゃんと契約書とか作るべきだったなぁ、と。
プログラミングは初期(基礎構文とかのレベル)は知識より手を動かすことが大事です。そもそも、基礎構文でデータ構造もクソもないからね。とはいえ、知識をベースに手を動かすと理解が数倍に跳ね上がるので、授業で知識を教えた上でそれを実践する課題を与えていました。スポーツや言語と同じで、リーディングが上手くなったから話せるわけじゃないし、身体の使い方がわかったからと言って反射的にそれをできるかは別です。例えばシャドーボクシングとかも、正しい動きを反復するのが大事で、ただシャドーしていると変な癖の付いた効率の悪い力の入らない打ち方とか、手打ちで身体の力が活きない動きで脳が最適化されたりします。プログラミング教室では基礎のインプットとして授業があり、課題でのアウトプットにより、その知識を定着させて使いこなせるよう脳を最適化するわけです。課題をやらないと、頭を通り抜けて翌週には忘れます。課題をやるにしても、重要なポイントを意識しないでやると、できるけど理由はわからない、という曖昧な状態で、後々、致命的なエラーを起こすコードを書きかねません。

講師が好む生徒

これを出来の良い生徒とか質問を良くする生徒と勘違いする人が多いですが、個人的には「努力して成長する生徒」です。これを言ってしまうと、元々できる人は困るかもしれませんが、元々できる人は本当に全部知ってるなら受けに来るわけ無いですし、自分の新しく知った知識に関して、なぜ自分が知らなかったかを考察したり、新しくわかったことについて調べたり、知ってるからこその角度で質問したりすると、教える側にも学びがあります。アメリカの授業ではトップレベルの学生はこういうことが得意で、教師側も、それはやったことないから、確認する、と言って次の授業でFBをしたりしていました。講師が全部知ってるわけでもないので、時折、そういう質問があると、日本では無駄なプライドで変なことを口走ったりしますが、海外では双方向での学びの機会と喜びます。また、質問に関してはすればいいというわけじゃなくて、「それ、前に説明したよね」というのが中心だと、逆にちゃんと聞いてない生徒、となりかねません(ただし、1,2ヶ月前の話とかなら再確認としてOK。その場合、前に聞いた気がしますが、〜って、○○でしたっけ?とかどう動いてるんでしたっけ、という聞き方がpolite)。ちなみに最低の生徒はわかってるふりして全然わかってない生徒。授業のペースが壊れます。
自分でわからないことを努力して、わからない部分を説明しながら質問してもらえると、講師としては詰まりどころがわかるので、次の回に活かせます。元々あまりできなかった生徒が質問と課題への努力を通じてできるようになっていく、成長していくのは何度か書いたかも知れませんが、教育者の最大の喜びです。
というのも、自分が、数学とかの用語の分からない英語(プログラミング用語は最初から余裕でした)の授業で他専攻から大学院に入ったこともあり、数学力をかなり求められる授業では最初は落ちこぼれで、リーディングと質問で最後はクラスのトップレベルまでできるようになった(正直血尿出るんじゃないかくらい頑張りました)ことが何度かあって、最終的にA-位で終わって、教師にやけに気に入られた事があったりして、その気持ちが今はわかります。最初できないことは必ずしも悪いことではありません。ただし、最初から基礎がある人の何倍も努力しないといけないので、ちゃんと基礎は出来たほうが良いです。その経験からここでは基礎はちゃんと積み上げるべき、の思考を繰り返し書いています。

プログラミングは常に新しい発見ができる

大学院在学中の短期も含め、今の現場で3年位いますが、この1年半はcocos2dでC++を使っています。元々Javaはかなり使えて、大学院でCやPython, Haskellとか、Prologいじったので、C++JavaとCの知識であまり言語仕様をしっかり勉強せず、現場で使われてる書き方をリバースエンジニアリング(以下RE)で身につけた感じですが、最近、ガッチリ系エンジニアのコードを見て言語仕様をちゃんと学びだしたら、それまで自分が書いてたコードが稚拙に思えました。おそらくアルゴリズム的にはあっと思う部分もあっても、言語仕様が使いこなせてないからconstをあまり使ってなかったり、参照を他で使われてるやり方(キャッシュしてるマスタデータのエンティティの参照とか)しか使ってなかった部分がありました。後はconstexprとか。それにともなって色々論理式の使い方も見直しました。数学的なプログラミング、論理式の正しい使い方ができてるとコードが全然違うものになります。

論理式を使いこなす

ここからの例では最近使ってるcocos2dがベースですが、関数は名前見れば意味がわかるでしょう。_で始まるのはメンバ変数です。Node::setVisible(bool isVisible)はその要素の可視を設定して、要素を出したり消したりします。

if (classA.isVisible())
    _nodeA->setVisible(true);
else
    _nodeA->setVisible(false);

とかって、結構慣れてる10年選手のエンジニアでも思考停止してると書いちゃうんですが、trueの時にtrueを与えて、falseの時にfalseなら最初からsetVisibleに渡しちゃえよ、という話です。

_nodeA->setVisible(classA.isVisible());

これでいいじゃん。まぁ、可読性は多少落ちますが、これくらい読めなきゃプログラマ辞めちまえ、と思ったりします。
他にも、

enum class TabType {
    TAB_A = 0,
    TAB_B,
    TAB_C,
};

という定義があって、タブを切り替える関数で

void SceneA::setTab(TabType tabType)
{
    if (tabType == TabType::TAB_A)
    {
        _nodeTabA->setVisible(true);
        _nodeTabB->setVisible(false);
        _nodeTabC->setVisible(false);
    }
    else if (tabType == TabType::TAB_B)
    {
        _nodeTabA->setVisible(false);
        _nodeTabB->setVisible(true);
        _nodeTabC->setVisible(false);
    }
    else
    {
        _nodeTabA->setVisible(false);
        _nodeTabB->setVisible(false);
        _nodeTabC->setVisible(true);
    }
}

とか、もうこんなコードは見たくないんです。

void SceneA::setTab(TabType tabType)
{
    _nodeTabA->setVisible(tabType == TabType::TAB_A);
    _nodeTabB->setVisible(tabType == TabType::TAB_B);
    _nodeTabC->setVisible(tabType == TabType::TAB_C);
}

これでよくないですか?3択で1つしか取らないわけですから、対象のnodeだけが見えます。

数学的思考

何度か取り上げた1からNまでの等差数列の和

int sumByIdiot(int n)
{
    int sum = 0;
    for (int i = 1; i <= n; ++i)
        sum += i;

    return sum;
}
int sumByFormula(int n)
{
    return int(n * (n + 1) * 0.5);
}

がありますが、この辺は実はコンパイラが最適化してくれて両方同じ機械語に翻訳されたりします。
最近見た見たくないコード。

std::map<int, bool> visibleMap;
for (auto &data : result->visibleList())
{
    visibleMap[data.asInt()] = true;
}
for (int y = 0; y < result->numRows(); y++)
{
    for (int x = 0; x < result->numCols(); x++)
    {
        int index = y * result->numCols() + x;
        _imgParts->setVisible(x, y, visibleMap.count(index) == 0 ? false: visibleMap.at(index));
    }
}

これは開始地点から、x * y個に分かれた表示領域で渡された位置のみ表示するコード(初期値は全部visible=false)なんですが、これ、map必要ですか?おそらく、リストの数字からx, yを算出できなかったんだろうけど。これ見て、ああ、数学できないんだな、と思った。画像データってピクセルのstreamなので、開始位置から終了位置まで連続で情報が詰まってます。おそらく、画像関係の勉強した人や数学力のある人が書いたら、

for (const auto &data : result->visibleList())
{
    int index = data.asInt();
    int x = index % result->numCols();
    int y = index / result->numCols();
    _imgParts->setVisible(x, y, true);
}

indexとx, yは無名でもOK。x, yは割り算理解してれば算出できるわけです。割り算の本質と2次元配列のstreamのどちらかが理解できれば無駄な2重ループなんて書きません。

bitsetはすごく便利

std::bitset<n>は知る人ぞ知るプログラミングコンテストの強い味方ですが、これ、enumのグループ分けした判定とかでも強い味方です。
よくあるコード

enum class Cause {
    CAUSE_BY_AGE_A = 0,
    CAUSE_BY_AGE_B,
    CAUSE_BY_AGE_C,
    CAUSE_BY_AGE_D,
    CAUSE_BY_LIFESTYLE_A,
    CAUSE_BY_LIFESTYLE_B,
    CAUSE_BY_LIFESTYLE_C,
    // ... some 10 more causes
    SIZE,
};

bool isByAge(Cause cause)
{
    bool result = false;
    switch (cause)
    {
        case Cause::CAUSE_BY_AGE_A:
        case Cause::CAUSE_BY_AGE_B:
        case Cause::CAUSE_BY_AGE_C:
        case Cause::CAUSE_BY_AGE_D:
            result = true;
            break;
        default:
            break;
    }
    return result;
}

こんなのでswitchで並べたりする。bitsetを使うと

enum class Cause {
    // ... same as above
}
static constexpr size_t SIZE = static_cast<size_t>(Cause::SIZE);
static constexpr std::bitset<SIZE> CAUSE_BY_AGE = 
 std::bitset<SIZE>(1 << static_cast<int>(Cause::CAUSE_BY_AGE_A))
 | std::bitset<SIZE>(1 << static_cast<int>(Cause::CAUSE_BY_AGE_B))
 | std::bitset<SIZE>(1 << static_cast<int>(Cause::CAUSE_BY_AGE_C))
 | std::bitset<SIZE>(1 << static_cast<int>(Cause::CAUSE_BY_AGE_D));

static constexpr std::bitset<SIZE> CAUSE_BY_LIFESTYLE = 
 std::bitset<SIZE> (1 << static_cast<int>(Cause::CAUSE_BY_LIFESTYLE_A))
 | std::bitset<SIZE>(1 << static_cast<int>(Cause::CAUSE_BY_LIFESTYLE_B))
 | std::bitset<SIZE>(1 << static_cast<int>(Cause::CAUSE_BY_LIFESTYLE_C))

bool isByAge(case)
{
    return (CAUSE_BY_AGE & std::bitset<SIZE>(1 << cause)).any();
}

固定値の定義がくどめだけど、isByAgeはスッキリ。bitset<N>はN個のbitが並んでるデータ構造なので、boolがN個並んでるようなもの。なので、対象のenumの位置(enumの宣言で0だけしてるが、それ以降は1ずつ足されてる)にビットが立つ(1になる)ので、渡されたenum分左にシフトして論理積をすると、両方で1が立ってるときのみany()が1を返すわけです。1回AND回路通すだけで判定ができるってわけ。これ、例ではAGEとLIFESTYLEだけなんですが、AGE x 何か、LIFESTYLE x 何か位に要素が2つ3つなるとすごい便利です。
ちなみにプログラミングコンテストでよく使われるのはN個の数字が与えられて1〜N個の任意の組み合わせでmが作れるか、とかね。普通にやるとN個使った場合だけでN!通りあるんですが、O(N)で解けるという。この辺は説明してるところ結構あると思いますが、新しい数nでn個分左にシフトすると今ある数とnでできる全ての組み合わせがわかり、それと今ある数とnを合わせたものも論理和で足してあげれば、そこまである数値全てで作れる数に1が立つという。パッと聞いて理解できない人は論理値の理解が浅いので色々遊んで身につけて下さい。

Note始めます

最後ちょっと宣伝なんですが、プログラミング教室はもしかしたら始めないかも知れません。というのも、顎変形症の手術が夏前になってしまい、タイミングが微妙で、今の職場の契約が6月までで、延長せず、7月1ヶ月休んで外資系で8月から働こうかな、と思ってます。まぁ、世界トップ数社何個か受ければ受かるかな、という軽いノリです。当然準備もガッチリしてます。ですが、T社とかどうしようもないプログラミング教室が年商億稼いで、「使い物にならないプログラミングをマスターした即戦力エンジニア(Rubyに限る)」が大量発生するのが怖い。なので、C++を使って、プログラミングの基礎とオブジェクティブ指向やデータ構造、アルゴリズムが学べる講座をNoteで格安で販売してみようかな、と。筆不精なので、ちょっと自信ないけど…。1個の講座を短めに100円とか200円位で行こうと思ってるので気が向いたらよろしくお願いします。