うどん県出身グーナーの呟き

神奈川県在住のグーナーです。プログラムをやっているので勉強していることや作った便利ツールを公開したいと思います。

【Rust】【チュートリアル】世界のトヨタが使っているというRustを使ってみたよ、その2

世界のトヨタが使っているというRustを使ってみたよ、その2

概要

最近、自動運転界隈で注目されているトヨタが開発中のCarOS(Arene)がRustという言語で開発されているということを聞きつけ、

ミーハーな自分を抑えられなかったので簡単に触ってみました。

かなりしっかりしたチュートリアルがあるので、現在はこれを勧めている段階のメモ書きになります。

今回は少しRustの特徴などについても触れていきます。

ターミナルからの入力を受け取る

このページから始めていきます。

このチュートリアルでは、いままでターミナル上に出力するだけでしたが、そうではなく、外部からの入力(数字、文字)の受け取りができるソフトを作っていきます。

ただ、このページの本質はそこではなく、Rustという言語を扱う上で重要な以下の点の説明がなされています。

  • 変数の宣言方法
  • immutable(後で変更できない)とmutable(後で値を変更できる)な変数の違いと宣言方法
  • ライブラリのインポート(インクルード)方法
  • Exception処理の重要性

まず、前回と同じくcargoを作っていきます。

Rustのworkspaceに移動し、以下のコマンドを実行します。

cargo new guessing_game

guessing_gameはプロジェクト名になるので、好きな名前で大丈夫です。

次に guessing_game/src/main.rs を好きなエディタで開いていただき、以下をコピペしてきます。

// std ライブラリ内のio(ファイル入出力)ライブラリをインポート
use std::io;

fn main() {

    // 文字列を出力するマクロ
    println!("Guess the number!");

    println!("Please input your guess.");

    // Point!!
    // letは変数を宣言するよというマクロ
    // mutはこの変数をmutable(後で変更可能)な変数として宣言するよというマクロ
    // Rustの変数はデフォルトはimmutable(後で変更できない, Cのconstかと思ってたが、違うらしい)

    // let foo = 5; // immutable
    // let mut foo = 5; //mutable


    // String::new() String型(文字列型)の空のインスタンス(値を入れる箱)を作成
    let mut guess = String::new();

    // io::stdin()
    //    .readline( &mut guess)
    //ターミナルからのinput を受け付ける関数
    // 引数はmutableな変数へのreference(&) <- C++の参照渡し

    // Point!!
    // .expect("Failed to read line");
    // Rustは関数からの返り値にたいして、

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

        println!("You guessed:{}", guess);

}

インポート

コメントアウト部分は自分のメモ書きになりますので、消して頂いて大丈夫です。

まず、2行目から

use std::io;

これはCでは

#include stdio.h

Pythonでいうimportに相当するものです。

ここではi/oに関わるライブラリのみが使われていますが、他にもstdライブラリだけでもかなりの数が提供されています。

なお、RustはPythonのようにインポートなしでもいくつかの機能が自動的にインポートされていますが、 Rustという言語の性質上、使わないライブラリをなるべく減らしたいため、こちらのページ の機能のみとなっています。

ちなみにこの自動的にインポートされる機能の総称としてpreludeと名付けれらています。

main文

次に4行目、

fn main(){

次に各プログラムのEntry Point(開始地点)になるmain関数です。 ここは他の言語と同じく、変数名を変えてはいけません。

Pythonでのdefと同じく、fnがRustの中で関数を宣言するマクロになります。

変数の宣言

今回のチュートリアルで恐らく一番重要なのがこの部分になります。

上のコードでの11行目。

    let mut guess = String::new();

これがString型の変数の宣言になります。

まず、マクロを羅列して機能を一覧にしてみます。

  1. let -> 変数を宣言しますよというマクロ

  2. mut -> mutable(変数の中身を後で変えられるよう)にしますよというマクロ

  3. String::new() -> String型のインスタンス(値を入れる箱)を作りますよというマクロ

ここで重要なのは2.のmut

つまり、Rustでは基本的に変数はimmuttableな = "後で値が変更できない” ものになります。

これは高信頼性を謳う言語ならではという感じですね。

なので、例えば以下のようなコードがあったとします。

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

    let test = guess;
    println!("Test={}",test);
}

このコードの場合、testという変数は宣言時にguessという変数の値をコピーして格納します。

なので、実行するとguess(何も値を入れていないので空)になります。

ワーニングが出ますが、これは値を変更していないのにguessをmutableにしているためなのでここでは無視します。

ryo_udon@ryo-udon-desktop:~/workspace/rust_workspace/guessing_game$ cargo run
   Compiling guessing_game v0.1.0 (/home/ryo_udon/workspace/rust_workspace/guessing_game)

warning: variable does not need to be mutable
  --> src/main.rs:23:9
   |
23 |     let mut guess = String::new();
   |         ----^^^^^
   |         |
   |         help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

warning: 1 warnings emitted

    Finished dev [unoptimized + debuginfo] target(s) in 1.14s
     Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
Test=

しかし、これに対して以下のようにコードを変えて実行するとこうなります。

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

    let test = guess;
    test = "something".to_string(); //<-この行を追加
    println!("Test={}",test);
}

実行結果:

f:id:ryo_udon:20210131183126p:plain

エラーを簡単に説明すると、最初にtestという変数を宣言したときにもう変更しないよ(immutable)と宣言したのに値を変更しているよ、と怒られています。

C言語などでは、基本的にconstを使わない限り、このようなエラーはでません。

そう考えるとこれもRustの特徴のように感じます。

ちなみにRustにもconstがあります。

constant と let(変数)との違い

三章で詳しく説明されていますが、基本的にRustではグローバル変数をletで作成できません。

なので、Constantなグローバル変数を作りたいときに

const MAX_POINTS: u32 = 100_000;

のような形で宣言するようです。

なのでconstantの一番の違いは、どこでも宣言ができること、のようです。

(違っていたらコメントをください。)

またここでは特に言及されていませんが、mutableなグローバル変数を作るのはなかなか骨が折れそうです。

こちらはまたいずれ調査して、別途まとめたいと思います。

ターミナルからの入力受付と参照渡し

40行目付近、ioライブラリを使って、ターミナルから文字列を受け取っています。

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

ここでまず、.(コロン)ごとに改行していますが、これは可読性のためで、別につなげて書いても問題ありません。

次に各関数の説明をしていきます。

        .read_line(&mut guess)

この関数が入力を受け付ける関数になります。

この関数が呼ばれるとターミナルで打ち込まれた文字を読み込みます。

その格納先が、&mut guess になります。

&mut guessは簡単に説明すると、guessという変数(mut)のReference(&)を渡しますよ、ということです。

Referenceというのはなにかというと、C++での参照渡しのようなものになります。

都度都度メモリを確保して値を取り込むのは処理速度的にあまりよろしくないので、Rustでは先に宣言しておいた変数のメモリに直接値を書き込みます。

例外処理

続いて、その下の**.expect()を説明していきます。

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

.expect()では、.read_line()からio::Resultという型で帰ってきた実行結果に対して、もしエラーが起きていたらどういう動作をするかを宣言しています。

基本的にStandard ライブラリには中身は違えどこのio::Resultのような結果が格納できる構造体を持っています。

Rustで面白いのは、Cなどではこういった例外処理は宣言する必要がないのですが、Rustではこれを入れないとコンパイル時にワーニングが発報されます。

f:id:ryo_udon:20210131183226p:plain

プログラムが突然落ちることを防ぐためにもいい仕様だなと個人的に思います。

乱数を生成する

チュートリアルこの部分

インポート方法

ここで乱数を生成するために必要なライブラリがrandと呼ばれるライブラリになります。

そのインポート方法を説明していきます。

まず、Cargoではライブラリをバイナリの状態(コンパイルをせずに動く状態)でインポートできることが強みとしています。

なので、まずソースコードにrandを追加するまえに、今回作成したguessing_gameにrandというライブラリをこのプロジェクトに追加していきます。

まずCargo.tomlを開いて、そのdependenciesの部分にrandを追加します。

[dependencies]
rand = "0.5.5"

追加後、以下のコマンドを実行するとライブラリの追加が始まります。

f:id:ryo_udon:20210131183249p:plain

ちなみに cargo updateとうつとdependency内のライブラリをupdateしてくれます。

乱数生成

乱数を生成して、暗証番号を作る機能を加えたのが以下のコードになります。

// std ライブラリ内のio(ファイル入出力)ライブラリをインポート
use std::io;

// 乱数生成
use rand::Rng;

//let mut global = String::new();
//static mut GLOBAL: String = String::new();

fn main() {

    // 文字列を出力するマクロ
    println!("Guess the number!");

    println!("Please input your guess.");

    let secret_number = rand::thread_rng().gen_range(1,101);

    // Point!!
    // letは変数を宣言するよというマクロ
    // mutはこの変数をmutable(後で変更可能)な変数として宣言するよというマクロ
    // Rustの変数はデフォルトはimmutable(後で変更できない, Cのconstかと思ってたが、違うらしい)

    // let foo = 5; // immutable
    // let mut foo = 5; //mutable


    // String::new() String型(文字列型)の空のインスタンス(値を入れる箱)を作成
    let mut guess = String::new();

    // io::stdin()
    //    .readline( &mut guess)
    //ターミナルからのinput を受け付ける関数
    // 引数はmutableな変数へのreference(&) <- C++の参照渡し

    // Point!!
    // .expect("Failed to read line");
    // Rustは関数からの返り値にたいして、

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

        println!("You guessed:{}", guess);

    println!("Secret Number was : {}", secret_number);

    //let test = guess;
    //test = "something".to_string();
    //GLOBAL = "2".to_string();
    //println!("Test Global={}", GLOBAL);
}

追加したのは5行目と13行目になります。

5行目はただのインポートの宣言。

13行目が実際に乱数を生成する部分になります。

    let secret_number = rand::thread_rng().gen_range(1,101);

といってもここでは特に説明することはありません。

secret_numberというimmutableな変数に1~101の間でランダムに選ばれた値が代入されています。

入力した値と生成した値を比較する

次に中で生成した暗証番号と入力した数字が一致するのか、はたまた小さいのか大きいのかを判断していきます。

ちなみに値の比較のために以下のライブラリもインポートします。

use std::cmp::Ordering;
    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            println!("You Entered:{}",guess);
        },
    };

    match guess.cmp(&secret_number)
    {
        Ordering::Less => println!("Too Small!"),
        Ordering::Greater => println!("Too Big!"),
        Ordering::Equal => println!("You Win!"),
    }

値の比較とその制限

上のコードで値を比較しているのが以下の部分になります。

guess.cmp(&secret_number)

しかし、ここでguessという変数とsecret_numberという変数の型が一致しないとエラーになります。

(最初に上げたコードのままだと、guessはString型、secret_numberはデフォルトのu32型になります)

そのため、ここではguessという変数をu32型で再度宣言して、trim().parse()という関数を使うことで型変換を行っています。

ちなみにその後のErr(_)は数字以外が来た場合の処理になります。

match

ここでmatchというマクロについて説明します。

例えば上のcmpという関数を使った場合、引数に与えた値に対し、

  1. Ordering::Less -> 小さい
  2. Ordering::Greater -> 大きい
  3. Ordering::Equal -> 一致

という結果を返します。

ここでmatchはこのcmpという関数が上の3つの結果を返してくるというのを先に確認します。

そのため、例えば下のようなコードの場合、分岐に抜けがあるよとエラーを発報してくれます。

    match guess.cmp(&secret_number)
    {
        Ordering::Less => println!("Too Small!"),
        //Ordering::Greater => println!("Too Big!"),
        Ordering::Equal => println!("You Win!"),
    }

f:id:ryo_udon:20210131183400p:plain

入力した数字と生成した数字が一致するまでループを回してみる

最終的なコードがこちらになります。

// std ライブラリ内のio(ファイル入出力)ライブラリをインポート
use std::io;
use std::cmp::Ordering;

// 乱数生成
use rand::Rng;

fn main() {

    // 文字列を出力するマクロ
    println!("Guess the number!");


    let secret_number = rand::thread_rng().gen_range(1,101);
    loop {
        println!("Please input your guess.");
        let mut guess = String::new();
        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");


        //let guess: u32 = guess.trim().parse().expect("Please type a number!");
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("You Entered:{}",guess);
                continue;
            },
        };
        println!("You guessed:{}", guess);


        match guess.cmp(&secret_number)
        {
            Ordering::Less => println!("Too Small!"),
            //Ordering::Greater => println!("Too Big!"),
            Ordering::Equal => {
                println!("You Win!");
                break;
            }
        }

        println!("Secret Number was : {}", secret_number);


    }
}

loop

C言語でいうwhile(1)に相当するのがこちらのloopになります。

この場合、ループ内でbreakが呼ばれるか、外部からCtrl + Cによるプロセス終了のコマンドが来るまで処理が回り続けます。

実行結果

実行結果が以下になります。

生成した数字と一致したループでbreakが呼ばれ、プロセスが終了していることがわかります。

f:id:ryo_udon:20210131183438p:plain

最後に

今回はチュートリアルの2章部分に少し情報を足した形でメモを残して見ました。

なかなかボリュームがあるので進みが遅いですがめげずにすすめて行きたいと思います。

ではでは。

【Python】【サンプルコード】実際の用途別チートシート を作ってみたよ【複数ファイル、ディレクトリ削除、作成、書き込み、読み込み編】

実際の用途別チートシート を作ってみたよ【複数ファイル削除、作成、書き込み、読み込み(File I/O)編】

概要

たまに使うと忘れがちなファイル書き込み、読み込み

大体そういう時は複数の(大量の)ファイルやディレクトリを作成したり、削除したり、読み込んだりと、

調べるにしてもいくつかに分けて検索する必要があり、意外と大変。

その後にはmatplotlibやpandasも毎回調べるし。

そんなときにとりあえずコピペしておけば動くそんなコードをまとめたシートになります。

なお、ここでは一機能の説明ではなく、実際の用途ごとにまとめてあります。

各関数ごとの細かい機能は下の関数ごとのリンク先か別途Google先生で確認してください。

随時更新中

このチートシートで使われているライブラリ一覧

ファイル、ディレクトリ探索

glob

ファイル、ディレクトリ存在確認

os.path系

ディレクトリ作成

os.mkdir

ディレクトリ削除

os.remove

複数(大量)の同じ名前のファイルを作成

データをロギングする、タイムスタンプで区切って別々のファイルに分割する。

意外と使うことが多い、ファイル名 = 名前+インデックス+拡張子(.csvなど) での複数(大量の)ファイル作成

そのときは以下のコードを使いましょう。

path = "./data/"
filename = "sample"
filetype = ".csv"
filenum = 10

for i in range(filenum):
    open_filename = path + filename + str(i) + filetype
    with open(open_filename, mode='w') as f:
        f.write("1,1,1,1")

これであなたも大量にファイルを製造できちゃう!!

複数(大量)の同じ名前のファイルを作成(ヘッダー付きCSV)

ちなみにCSVファイルを作る際には各行の最後に","をつけたくなりますが、つけないことをオススメします。

それはpandasで読み込むときに無駄に1列、列に追加されてしまうからです。

path = "./data/"
filename = "sample"
filetype = ".csv"

header = "timestamp, data1, data2\n"

for i in range(10):
    open_filename = path + filename + str(i) + filetype
    with open(open_filename, mode='w') as f:
        f.write(header)
        for j in range(10):
            timestamp_str = str(j)
            str_data = timestamp_str + ",1,1"
            f.write(str_data)
            f.write("\n")

複数(大量)の同じ名前のディレクトリ(フォルダ)にファイルを作成する場合

正直インデックスで分ければいいじゃんと思うかもしれないが、たまに水準を振って、同じファイル名で保存したいときがある。

そんなときはファイル名はそのままで、複数のディレクトリを作成し、分けて入れてあげましょう。

import os

path = "./data/"
filename = "sample"
filetype = ".csv"

header = "timestamp, data1, data2\n"

for i in range(10):
    dir_path = path + filename + str(i)
    if os.path.exists(dir_path) == False:
        os.mkdir(dir_path)

    open_filename = dir_path + "/" + filename + filetype
    with open(open_filename, mode='w') as f:
        f.write(header)
        for j in range(10):
            timestamp_str = str(j)
            str_data = timestamp_str + ",1,1\n"
            f.write(str_data)

複数(大量)の同じ名前のファイルを削除

逆に大量のファイルを通流前は一度ディレクトリの中をきれいにしたくなりますよね。

そんなときは以下のコードを使いましょう。

import os
import glob

path = "./data/"
file_type = ".csv"
filenames = glob.glob(path + "*" + file_type)

for filename in filenames:
    os.remove(filename)

指定ディレクトリ以下の同じ拡張子のファイルを削除する

複数のディレクトリに渡ってデータを保存しているとき、一度きれいにしたくなることがありますよね。

そんなときは以下のコードを実行しましょう。

import os
import glob

path = "./data/"
file_type = ".csv"
filenames = glob.glob(path + "**/*" + file_type)
for filename in filenames:
    os.remove(filename)

指定ディレクトリ以下のすべてのファイル、ディレクトリを削除する

とりあえず問答無用で全部削除してまっさらにしたい!! そんなときありますよね。

そんなときは以下のコードを実行しましょう。

import shutil
import os

path = "./data/"
if os.path.exists(path):
    shutil.rmtree(path)
os.mkdir(path)

CSVファイルを読み込んで配列に格納する

とりあえず文字列でいいからデータの中身を確認したい。

そんなときは以下のコードを使ってください。

path = "./data/"
filename = "sample0.csv"

with open(path + filename, mode='r') as f:
    line = f.readline()
    while line:
        line = line.replace('\n','')
        str_data = line.split(',')
        print(str_data)

        line = f.readline()

CSVファイルを読み込んで数値として配列に格納する

数値として配列に格納してささっとグラフを書きたい。

そんなときは以下をどうぞ。

path = "./data/"
filename = "sample0.csv"

with open(path + filename, mode='r') as f:
    line = f.readline() # headerをスキップ
    line = f.readline()
    while line:
        line = line.replace('\n','')
        str_data = line.split(',')

        data = [int(x) for x in str_data]
        print(data)

        line = f.readline()

【Rust】【チュートリアル】世界のトヨタが使っているというRustを使ってみたよ、その1

Rustを使ってみたよ、その1

概要

最近、自動運転界隈で注目されているトヨタが開発中のCarOS(Arene)がRustという言語で開発されているということを聞きつけ、

ミーハーな自分を抑えられなかったので簡単に触ってみました。

かなりしっかりしたチュートリアルがあるので、現在はこれを勧めている段階のメモ書きになります。

今回はRustのインストールと簡単なHello Worldの実行方法です。

なにか間違っている箇所などがあれば、教えていただければ嬉しいです。

そもそもRustってなに?

昔から存在は知っていましたが、正直、僕も最近になって世界中で使われていることを知りました。

公式サイトには、以下のようにあります。

www.rust-lang.org

Hundreds of companies around the world are using Rust in production today, like Firefox, Dropbox, and Cloudflare, uses Rust. From startups to large corporations, from embedded devices to scalable web services, Rust is a great fit.

FireFoxDropboxでも使われているのは知らなくてびっくり。

なぜそんな企業に選ばれているのか、その理由は以下のようです。

for fast, low-resource, cross-platform solutions.

実行速度が早く、大きなリソースを必要とせず、さらにクロスプラットフォームなソフト(Windows, Mac, LinuxなどOSが変わっても実行できるソフト)を作成できることが大きいようです。

また、その他にも下のように、メモリ、スレッドセーフ、メモリや処理の効率化、などこれまでの研究向けな言語とは違い、より現場向きな言語なのかなという印象です。

f:id:ryo_udon:20210124151733p:plain

というわけで使ってみた。

Rustはチュートリアルを始めてとしたドキュメントがかなり充実しているので、簡単に情報を集めることができます。(まだ始めたばかりなので、いずれ印象が変わるかも)

なので、今回からチュートリアルをやってみましたの備忘録から始まり、なにか自分でアプリをRustで書いてみたいと思います。

Rust のインストール

今回は手元のMacではなく、以前購入したRaspberry Pi 4Bにインストールしてみました。

ryo-udon.hatenadiary.jp

インストールはこちらを参考にしています。

curlのインストール

まず、curlがインストールできていなかったので、インストールしていきます。

sudo apt-get Update

sudo apt-get isntall curl

Rustのインストール

次のコマンドを打ちます。

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

これでEnterを推すと、途中で下の画像のようにDefault Installかを確認されますので、

1

と打ってEnterを押すとインストールが開始します。

[f:id:ryo_udon:20210124150205p:plain]

インストールが終わると以下のように完了の文字がでます。

[f:id:ryo_udon:20210124150222p:plain]

しかもその下にpath設定の必要があるけど、次回起動時に勝手にやってくれるよとのメッセージが。なんて親切。

なお、この際に公式の手順を見るとC言語コンパイラのインストールを推奨しているようです。

Additionally, you’ll need a linker of some kind. It’s likely one is already installed, but when you try to compile a Rust program and get errors indicating that a linker could not execute, that means a linker isn’t installed on your system and you’ll need to install one manually. C compilers usually come with the correct linker. Check your platform’s documentation for how to install a C compiler. Also, some common Rust packages depend on C code and will need a C compiler. Therefore, it might be worth installing one now.

自分はgcc, g++をすでにインストールしているので、問題なく動きました。

これでインストールは完了です。

開発環境

まだ触り始めたばかりで今後色々と設定を加えていくとは思いますが、今はこれまで通りVSCodeで書いています。

(リモートアクセスが簡単にできて、ターミナルも自動でsshしてくれるので)

なのでVSCodeRustのExtensionを追加だけして作業をしています。

ちなみに他にもIDEに対応しているようなので、お好みのIDEで開発してください。これのほうが便利などの情報もお待ちしています。

[f:id:ryo_udon:20210124150349p:plain] リンク

個人的にGeanyとかまでちゃんと対応しているのに驚きました。使ってる人いるんだ。。。

Hello Rust World!!

workspaceの作成

ではプログラムではおなじみ、"世界よ、こんにちは"の時間です。

まずはworkspaceを作っていきます。

ターミナルを開き、以下を入力していきます。

mkdir rust_workspace
cd rust_workspace
mkdir hello_world
cd hello_world

vscodeを使っている人は右側のFolderからworkspace、.rsファイルを作っていただいて大丈夫です。

ちなみにご存知の方が多数だと思いますが、"mkdir"がディレクトリ(Windowsでいうフォルダ)を作成するコマンド、"cd"が指定したディレクトリに移動するコマンドになります。

プログラムを書いていく

hello_worldのディレクトリの中でプログラムを書いていきます。

まずはhello_worldの中に"main.rs"というファイルを作っていきます。

ここで、"main.rs"の"main"の部分は何でも構いません。

"main"でもいいですし"a"でも構いません。

次に作った.rsファイルを編集していきます。

自分の好きなIDEで開いて以下のように編集してください。

fn main()
{
    println!("Hello Rust World!");
}

編集が終わったら保存をして、hello_worldのディレクトリをターミナルで開きます。

そして以下のコマンドを叩いていきます。

rustc main.rs

これがRustのコンパイルコマンドになります。

これを実行すると同じディレクトリ内に名称が同じの実行ファイルが生成されます。

ここまで来たら、後は実行するだけです。

./main

すると以下のような実行結果が得られます。

f:id:ryo_udon:20210124150429p:plain

ようこそ、Rustの世界へ!!

Cargoを使っていく

cargo new (Package作成)

さてこれでRustのプログラムは書けたわけですが、実はこの方法は公式推奨の方法ではありません。

公式では"Cargo"と呼ばれる、C, C++でいうCMakeのような外部のライブラリやパッケージのDependency(依存関係)を記載することができます。

なので、実は上で実際に書いたことがコマンド3つで実行できます。

まずrust_workspaceにもどって以下のコマンドを実行します。

cargo new hello_cargo

これでhello_cargo というcargoを使うためのディレクトリが作成されました。

また、その中を覗いてみると、 hello_cargo/ ├── Cargo.toml └── src └── main.rs

が自動で生成されています。

ここでさらにsrcディレクトリの中を覗いてみると、main.rsという先ほど作ったのと中身まで同じファイルが生成されています。

ここでCargo.tomlファイルというのがmakefileのようなものでpackage名やversion, dependencyなどを設定することができます。

cargo build (ビルド、コンパイル)

ここまで見たらまたまたターミナルに戻り、以下のコマンドを実行します。

cargo build

これでCargo.tomlの指示通りにビルド、コンパイルをしていきます。

これが終わり、またまたディレクトリ内を確認すると、今度はtargetというディレクトリができています。

その下も覗いてみると大体このような形になっています。

hello_cargo/
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    ├── .rustc_info.json
    ├── CACHEDIR.TAG
    └── debug
        ├── build
        ├── deps
        ├── examples
        ├── hello_cargo
        ├── hello_cargo.d
        └── incremental

cargo run (実行)

最後に実行です。

実行ファイルは"target/debug/hello_cargo"なので、

./target_debug/hello_cargo

でも実行できますが、もちろん実行コマンドが準備されています。

hello_cargoディレクトリで以下のコマンドを実行してください。

cargo run

すると実行すべき実行ファイルを勝手に探して、実行してくれます。

f:id:ryo_udon:20210124150529p:plain

そのほかのコマンド

このcargoには他にもコマンドがあるようで、個人的にお、いいなと思ったのが真っ先に紹介されていた"cargo check"コマンドです。

プログラムを書いた人なら夢でうなされたことがあるであろう、Segmentation Fault, コアダンプ。 これらが起きないかを確認してくれるコマンドになります。

まだこちらは試せていないので、色々と作っていく中で紹介できればなと思います。

まとめ

とりあえずRustを始めてみました。

特に何を作りたいというわけではありませんが、どういうことができるのか知りたいのでこれで色々とつくっていければなと思います。

おそらく定期的に更新していくと思うのでお楽しみに。

ではでは。

M1 MacBook Proを買ってみたよ

M1 MacBook Proを買ってみたよ

概要

現在使っているMacBook(2013年モデル)を使い続けてはや6年。

まだまだ現役で使えるけど、そろそろ心機一転で買い替えてみようかな、と思い、最近話題になっているM1 MacBookProを購入しました。

なので、その使用感やセットアップでのコツのようなもの、最終的に今買うべきかを話していきます。

f:id:ryo_udon:20210123164126j:plain

外観

本体

外観はこんな感じです。

f:id:ryo_udon:20210123164201j:plain

もともと持っているMacBookと比べると一回りぐらい小さくなっています。

f:id:ryo_udon:20210123164313j:plain

また薄さもかなりのもの。

f:id:ryo_udon:20210123164332j:plain

重さについても、持ってみた感じ2,3割ぐらい軽くなってそうです。

調べてみたらこのようになっていました。左が2013年モデル、右が2020lateモデルです。

(意外と自分の感覚も馬鹿にできない笑)

f:id:ryo_udon:20210123164547p:plainf:id:ryo_udon:20210123164555p:plain

このスペックは公式サイトから抜粋しています

2013モデル 2020モデル

キーボード

数年前から採用されているタッチバーは健在。ただ、以前買うことを断念した理由の一つ、Escキーがタッチパネルだった問題は、今のモデルでは改善されて普通の物理ボタンになっています。

押し心地も薄い割にストロークもありいい感じです。

f:id:ryo_udon:20210123164201j:plain

セットアップ

とりあえず、過去のMacBook Proからデータをそのまま引き継ぐためにTime Machine(Macにデフォルト搭載されているバックアップソフト)を使ってみました。

やり方はこちらを参考にしてください。

自分の場合、3時間ぐらいかかりました。

f:id:ryo_udon:20210123164749j:plain

ただ、後で話しますが、この方法は絶対にやらないほうがいいです!!

あれ?処理が速いって話じゃ。。。

試しにGoogle ChromeYoutubeでも見ようかと思ったら、なぜかもっさり。。。

何なら前のMacBook Proのほうが速い。。。

なぜだ?と思い調べたら、Intel Core のMacBook Pro からM1 チップにデータを引き継ぐと、Intel用のソフトがそのまま入ってしまうそうです。

とはいえ、世の中のソフトはIntel想定のものばかりのため、そこをカバーするためにRosetta2と呼ばれる翻訳ソフトを介して処理を行います。

なので、Google Chromeなど、すでにM1 チップに対応しているソフトはM1用のものをインストールしないとかなりもっさりしてしまいます。

また、その他にも長年Macを使っているとバックグラウンドで動くソフトがいくつか入っているもので、それによって全体の操作がもっさりしてしまうという事態が発生してしまいます。。。

なので、上でも書いたように Intel -> M1に移行する場合はTime Machineでの移行はオススメしません!!

え? MacBook Proなのに?

ここで白状しますが、実は今回購入したMacBook Pro、自分は返品しました。

その理由は、

"外部ディスプレイが一枚しか繋げないから!!"

今自分のデスクでは、持ち運ぶとき以外は基本MacBookを閉じたままで、ディスプレイアームで支えられたモニタ二枚で作業しています。

そんなデスク環境で一枚しか繋げないのはつらすぎる。。。

f:id:ryo_udon:20210123164943p:plain

僕の中でMacBook Pro, Airの違いはつなげる外部ディスプレイの数だったのですが(7年前の情報)、いつの間にかそれも変わってしまったようです。

Intel Coreであれば今ではAirでも外部ディスプレイ二台につなげることができるようです。

ちなみにカスタマーサポートにこれがM1用のOS側の原因かそれともハード側に起因するものなのか確認したところ、もちろんハードの問題のため、今後その台数が増えることはないと回答されました。

うーん。残念。

結論

というわけで操作感やサイズ感などはすごく良かったのですが、残念ながら自分の使用用途とは合わなかったので、今回は泣く泣くキャンセルとなりました。

ちなみに、こんなM1 MacBookでも実は複数台の外部ディスプレイにつなぐ方法が(非公式ですが)あります。

それが、DisplayLink Docking Station。

調べたところDisplayLinkと呼ばれる認証を受けたDocking Station(下の画像のようなものです)ではドライバなどを追加でインストールする必要がありますが、

これをつなぐことで複数台(中には6台もつないでいる猛者もいたようです)につなげるようです。

探した動画の中でかなり詳しく説明されていました。 youtu.be

六台もつないだ猛者がこちら。 youtu.be

ただ、金額が1万円以上するので、これ以上出費する気にならなかったので、ちょっと今回は返品して後1,2年様子を見ようと思っています。

やはりパソコンなどはこういうこともあるから返品対応してくれるメーカーがやっぱりいいですよね。

ちなみにAppleは届いてから一週間以内はヤマト運輸のみですが、送料無料で送り返すことができるのでその点も安心です。

ではでは。

Raspberry Pi 4 ModelBを買ってみたよ

Raspberry Pi 4 ModelBを買ってみた

概要

すでに2 B plusを持っていますが、最近さらに性能が上がっているRaspberry Piを購入してみました。

これを使ってWebスクレイピングBotなどを常時稼働させたりとかしたいと思っています。

今回はその中でRaspberry Piの立ち上げと画像処理の勉強用にROS2のインストールまでを目標にやりたいと思います。

Raspberry Pi とは?

そもそもRaspberry Piとはなにかご存知ではない方も多いでしょう。

Raspberry Piはなにかというと、

"安価かつコンパクトなパソコン"です。

性能

まずお得感を感じるためにはその性能から語る必要あるでしょう。

現在最新モデルであるRapberry Pi 4 Model Bの性能は以下のとおりです。

CPU: ARM Quad-core 64bit @ 1.5GHz RAM(メモリ): 4GB/8GB インターフェイス: 2.4GHz/5.0GHz Wireless LAN, Bluetooth 5.0, USB3.0 x 2, USB2.0 x 2 ビデオ/オーディオ出力: Micro HDMI x 2(4K60fps), ステレオ音声出力など 電力: 15W程度(マニュアルに5V3A程度と書かれているのでおそらくこのくらい)

この他GPIOピンなども入っているので、かんたんな電子工作からWebブラウジング程度のライトな作業は何でもこなせます。

値段

ここで値段ですが、 最新モデルであるRaspbery Pi 4Bは少し過去のモデルから値段が上がって、

メモリ4GBモデルであれば6500円程度、 8GB(今回はこちらを購入しました)であれば9000円となっています。

これだけの機能がありながら1万円を切るコスパの高さは本当に魅力的です。

というわけで今回は様々なボットを動かすためのサーバとして構築していきたいと思っています。

セットアップ

セットアップに必要なもの

セットアップに必要なものはいかになります。

  • Raspberry Pi 本体
  • micro SDカード
  • micro SDカード <-> USB 変換機
  • キーボード/マウス(USB)

ここから下はお使いのディスプレイの端子とRaspberry Piのモデルに合わせて準備してください。

自分の場合(Raspberry Pi 4B)は以下を用意しました。 * HDMIケーブル * micro HDMI <-> HDMI 変換ケーブル * USB Type-Cケーブル(給電用)

もしお使いのRaspberry Piが3以前のものでしたら、必要なものは以下になります。 * HDMIケーブル * micro USBケーブル

なお、ここで注意ですが、HDMIには大きく分けて三種類規格があります。 1. HDMI 2. mini HDMI 3. micro HDMI

上から下に向けて端子が小さくなります。 自分もやってしまったのですが、間違ってmini HDMIを買わないように気をつけてください。

不安でしたら、下にリンクを貼りますのでこちらを使ってみてください。

OSインストール

こちらの公式サイトを参考にしました。

今まではSDカードにimgを書き込むソフトを使っていましたが、最近になった今更ながらRaspberry Pi Imager というソフトが提供されていることを知ったので、今回はこちらを説明します。

使い方は簡単です。

Raspberry Piに差す予定のmicro SDカードをメインPC(Mac, Windows, Linux)につないだ状態でソフトを起動した後、 下のような画面がでてきます。 f:id:ryo_udon:20210108102314p:plain 後は左から順番に自分に合わせてOS, 書き込むSDカードを選択していきます。

まず、CHOOSE OS ボタンをクリックし、OSを選択します。 f:id:ryo_udon:20210108102331p:plain

ここではRasbianを始め、Ubuntuのデスクトップイメージなどが選択できます。

今回はUbuntu20.10をインストールしたので、Ubuntu -> Ubuntu 20.10 Desktop (Raspberry Pi 4) を選択します。 f:id:ryo_udon:20210108102358p:plainf:id:ryo_udon:20210108102404p:plain

次にSDカードを選択します。 CHOOSE SD CARDをクリックすると今パソコンに接続されている外部ストレージの一覧が出てきます。

(画像を取り忘れたため、下の画像ではひとまず適当なHDDをさしています。)

f:id:ryo_udon:20210108102511p:plain

この中からRaspberry Pi用のSDカードを選択してWRITEボタンを押せば完了です。

OS起動

こちらについては画面に従って操作するだけなので説明は省きます。 もし分からなければコメントをいただければお応えします。

なおここからはディスプレイに接続する必要がありますのでケーブルの準備などご注意ください。

SSH

ネットワークを介して、他のパソコンを操作できるSSHを使う設定をしていきます。 基本はこちらのサイトを参考にしました。

今回はpassphrase(パスフレーズ)を使った公開鍵を作っていきます。

パスフレーズ?

そもそもパスフレーズとはなにか。 こちらのサイト を参照すると以下のように説明されています。

パスフレーズとは、10文字以上の単語(フレーズ)を組み合わせて作られたパスワードの一種です。 パスフレーズとは、本人認証で用いるパスワードの一種です。通常のパスワードが8文字前後のところ、パスフレーズは10文字以上の単語を組み合わせた文字列で構成されているため、セキュリティが強固になります

要は、一般的なパスワードと違い、

文字列が長いため不正にパスワードを暴く「総当り攻撃」に対して有効

ということです。

設定

以下の手順で実施していきます。

  1. メインPC上で鍵ペアファイル作成
  2. 1.で作成した鍵ファイルのうち、公開かぎファイルをRaspberry Piに送信
  3. Raspberry Pisshサーバ設定ファイルを編集
  4. Mac or Windows PC側でのRaspberry Piの登録
鍵ペアファイルの作成

まず以下のコマンドを実行します。

ssh-keygen -t rsa

ここで-tのオプションで指定しているの公開鍵の形式です。 作成できるキーペアは以下の4つのようです。

  • dsa
  • ecdsa
  • ed25519
  • rsa

それぞれの形式の良し悪しについてはまた別に話すとして、今回は最後のrsaで作成しました。

Raspberry Piへの公開鍵の登録
scp id_rsa.pub [raspiのユーザ名]@[raspiのIPアドレス]:

(最後の:を忘れがちなので注意)

これをするためにはIPアドレスの固定化が必要なので、以下のサイトを参考に設定。

raspberry pi内のターミナルで、

ifconfig

次に同じネットワーク上に存在するIPアドレスを確認。

Mac or Windows PCでターミナルを立ち上げ、以下のコマンドを実行

ipconfig /all

あるいは、

arp -a

ここで使われていないIPアドレスを確認し、合わせてルータのIPアドレスを確認

(ルータについては各社ごとにルータのIPアドレスを確認するソフトが公式に配布されているので、そちらで確認するのがベスト)

ここで調べたIPアドレスをもとに、Raspberry Piのアドレスを固定化していきます。

またRaspberryPiのターミナルに移動し、以下のコマンドを実行。

sudo vim /etc/dhcpcd.conf

ここではエディタでvimを使っていますが、他のエディタでも可です。 ただsshで接続している場合、geditなどは開けませんのでご注意ください。

重要なのは管理者権限(sudo)で編集するということ。

以下の四行を追加

interface wlan0
static ip_address=[固定化したいIPアドレス]
static routers=[ipconfig /allで調べたルータのIPアドレス]
static domain_name_servers=[上のルータのIPアドレス]

上記の設定が終わったところでRaspberry Pi を再起動する。

reboot

再度Raspberry Piが立ちあがったらifconfig でアドレスが固定化されているかを確認して設定は終了。

もし不安だったら、Mac or Windows PCからsshでアクセスできるか確認する。

ssh [raspiのユーザ名]@[固定化したraspiのIPアドレス]

これでパスワードを要求されるので入力して、ログインできれば設定完了です。

この後のサーバ設定ではssh接続しておくと作業が捗るので試しておくことをおすすめします。

sshサーバ設定

sshで Raspberry Piに接続しているターミナルから以下のコマンドを実行。

mkdir .ssh

ここがsshを実行する際に公開鍵を探しに行くディレクトリになります。

次に先程scpでRaspberry Piのホームディレクトリにコピーした"id_rsa.pub"ファイルを.sshに移動します。

cat id_rsa.pub >> .ssh/authorized_keys

次に.sshディレクトリとauthorized_keysの権限を変更していきます。

(グループ権限、他のユーザの権限を無効にします。)

chmod 700 .ssh
chmod 600 .ssh/authorized_keys

その後、もう必要はないのでid_rsa.pubを削除します。

rm id_rsa.pub

次にsshサーバ設定ファイル(/etc/ssh/sshd_config)を編集していきます。

sudo vim /etc/ssh/sshd_config

まずPortを設定します。

sshだとデフォルトは22番ポートになっています。

ただ、このままだと外部から簡単に不正アクセスされてしまう恐れがありますので、

こちらのページ を参考に使われていないポートを設定しましょう。

予約済みのポートは同時に使えないので、予約済みになっていないポートを使いましょう。

#Port 22
Port 49151

次にリモートでのRootログインを禁止します。

PermitRootLoginという項目をsshd_configの中から探しだし、yes -> noに変更します。

#PermitRootLogin yes
PermitRootLogin no

最後に公開鍵ファイル認証を有効にします。

RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile ~/.ssh/authorized_keys

もしパスワードの入力を省略したい場合はこれに加えて以下の設定を変更します。

#PasswordAuthentication yes
PasswordAuthentication no

これで編集は完了になります。 最後に保存して、編集画面を閉じてください。

その後、設定を反映させるためにMac or Windows PC 内でRasberry piにsshでつないでいるターミナルで以下のコマンドを実行します。

sudo /etc/init.d/ssh restart

これで設定は完了になります。

正常に設定ができているかを確認したい場合はMac or Windows PCで新しくターミナルを開き以下のコマンドを実行してsshできるか確認してください。

ssh -i [秘密鍵ファイル このページだと~/.ssh/id_rsa] -p [設定したポート番号 このページだと49151] [raspberrypiのユーザ名]@[raspberry Pi のIPアドレス]
Mac or Windows PC側でのRaspberry Piの登録

最後にsshする際に一々上の長いコマンドを打つのは面倒なので、以下のようにユーザ名を設定していきます。

ここでもターミナルを開いてもらって、以下のファイルを編集していきます。

vim .ssh/config

開いたら、以下の5行を追加します。

Host raspi4b
    HostName [固定化したIPアドレス]
    User [Raspberry Piのユーザ名]
    Port 49151
    IdentityFile ~/.ssh/id_rsa

編集終了したら保存して、ファイルを閉じると、以下のコマンドで接続ができるようになります。

ssh raspi4b

これで接続できれば成功です。

余談

ちなみに私はこれに加えて、mac内の.bashrcを変種し、以下のalias(ショートカット)を足しています。

alias raspi4b='ssh raspi4b'

これで保存すると、ターミナル上で

raspi4b

と打つだけでssh接続ができるようになります。

VSCodeの設定

Remote Developmentとは? / インストール方法

私は普段からVSCodeを利用していますが、最近MicroSoft公式でリモートアクセス機能が使えるExtensionが公開されたと聞いたのでその設定をしてみました。

そのExtension の名前が"Remote Development"になります。

インストール方法はその他のExtensionと同じく左側のメニューバーからExtemsionを選択し、

[f:id:ryo_udon:20210108103834p:plain]

検索タブでRemote Developmentを検索します。

似たような名前のExtensionが上位で出てきますが、それを無視して名前が一致するRemote Developmentをインストールしてください。

Remote DevelopmentでのSSH接続方法

使い方は簡単です。

  1. 左下にある緑のボタンをクリック
  2. Remote-SSH Connect Current Window to Host ... または Remote-SSH Coneect to Host ...を選択(後者は新しくwindowを立ち上げる)
  3. 接続先を指定(.ssh/configを設定しているとクリックのみで接続可)
  4. パスワードを入力(パソワード入力を必要なしにしていた場合3.で接続完了)

上の4ステップでRemote接続ができます。

どうやって使えばいいの?

基本的にはメインPCと同じ感覚で作業ができます。

また、VSCode内でターミナルを開くと自動的にSSHした状態のターミナルが開けるので、ディレクトリの作成などの操作も簡単に行えます。

Raspberry PiPython環境構築

色々なサイトでRaspberry PiでのPython環境構築についての記事が上がっていますが、 色々と試した結果以下の方法でインストールができました。

pyenv

今回はAnacondaのインストールをしましたが、動作の確認などができていないのでひとまず確認できたpyenvのインストールを行います。

pyenvPythonのバージョンを簡単に切り替えられるツールになります。

なので、例えば基本は3.8で開発したいが、一部のアプリは2.7でといったアプリごとに使い分けることができるようになります。

なお、ここでは何もPython関連のライブラリがインストールされていない状況を想定して書いていきます。

pyenvのインストール

まず、パッケージリストの更新とインストール済みのパッケージを更新します。

sudo apt-get update
sudo apt-get upgrade
sudo reboot

最後に再起動を行い、改てpyenvをインストールしていきます。

git clone https://github.com/pyenv/pyenv.git ~/.pyenv

これで必要なパッケージはインストールしました。

次にpyenvを使うためにパスの設定を行っていきます。

sudo vim ~/.bashrc

.bashrcに以下のようにパスを設定していきます。

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

上の設定が終わったら、保存してファイルを閉じた後、

source ~/.bashrc

を実行すればパスの設定は完了です。 ちなみにターミナルを立ち上げ直しても反映されます。

Python3のインストール

Python3.7 のインストールにはgcc, makeなどのライブラリが必要になります。 なのでまずはそれをインストールしていきます。

sudo apt install gcc make zlib1g-dev libffi-dev libbz2-dev libssl-dev libreadline-dev libsqlite3-dev python3-tk tk-dev

次にpyenvでインストール可能なバージョンを確認していきます。

pyenv install --list

こちらのコマンドでインストール可能なバージョンのリストが出てきます。 ここで、anacondaがリストの中に出てきますが、Raspberry PiではCPU(ARM)の関係でインストールできませんのでご注意ください。

今回は3.9.1をインストールしたのでそれを例にしていきます。

pyenv install 3.9.1
pyenv global 3.9.1

これでターミナルでpythonを起動する際にpyenvでインストールされたpython3.9.1が起動します。 起動するpythonのバージョンは以下で確認ができます。

python --version

終わりに

というわけでRaspberry Pi 4Bのインストール方法について書いてみました。 以前にもサラッと説明したのですが、自分の備忘録も兼ねて今回、整理しております。

なにかうまくいかないやここがおかしいなどがあればご連絡ください。

ではでは。

【Python】自作の 行列演算クラス、 MatrixLib を作ってみたよ

自作のMatrixクラスを作ってみたよ

github.com


追記

'2020/07/07' Det(), Inverse()の部分で、間違って元の行列を書き換えるようになっていたため修正。


概要

最近、仕事の関係でMATLAB/Simulinkを使うことが多くなったのですが、色々とイライラが募っています。

特に、最近S-Functionを使ったC++ソースコードSimulinkに移植する作業をしているのですが、

もうイライラがとまりません。

関数の使い方はよくわからないし、何より、行列が使えない!! MATLABなのに!!

(本当は使い方があるのかもしれませんが、すいません。僕にはレベルが高すぎました。。。)

というわけで、ひとまずオレオレMatrixライブラリをC++で作ろうと思い立ったわけです。

そこで、とりあえず逆行列行列式を求めたりするので、そのロジックがあっているのかを確認するため、

今回はMatrixクラスをPythonで組んでみたいと思います。

具体的に今回やったこと

今回やったことは主に以下の4つです。

  1. 二次元行列を格納できる
  2. 行列式を計算できる
  3. 逆行列を計算できる
  4. 加減などの計算も符号演算できるようにする

それぞれ、コードとともに具体的に説明していきます。

コードの中身

まず、今回のソースコードはJupyterにて作成しました。

ryo-udon.hatenadiary.jp

もし、実際のPythonコードに使いたいという方は、Jupyterからクラス部分のみをコピーアンドペーストしていただければ

大丈夫です。

importするのも、pythonインストール時にデフォルトでインストールされている,math, copyのみになります。

二次元行列の格納

import math
import copy
class Matrix:
    def __init__(self, row, col):
        self.row = row
        self.col = col
        self.data = [ 0 for i in range(row * col)]
        self.error = False

    def ResetMatrix(self, row, col):
        self.row = row
        self.col = col
        self.error = False

    def InitializeWithIdentity(self):
        for row in range(self.row):
            for col in range(self.col):
                if row == col:
                    self.data[ row, col] = 1
                    #self.data[row * self.col + col] = 1
                else:
                    self.data[ row, col] = 0
                    #self.data[row * self.col + col] = 0
class Matrix:
    def __init__(self, row, col):
        self.row = row
        self.col = col
        self.data = [ 0 for i in range(row * col)]
        self.error = False

まず、データの格納部分です。 今回は色々と考えたのですが、とりあえずデータの格納は一次元の配列にし、このクラスを呼び出した際に、行、列を宣言するようにしました。

ここについては[ 1, 2, 3, 4] ではなく[[ 1, 2],[ 3, 4]]することも考えたのですが、ひとまずはこの形でも問題ないことが確認できたので、

簡単なこちらにしています。

なので、行数などを変えたいときはResetMatrix()関数を、単位行列にしたいときはInitializeWithIdentity()関数を呼び出すようにしています。

ちなみに、InitializeWithIdentity()内のここの部分、

                if row == col:
                    self.data[ row, col] = 1
                    #self.data[row * self.col + col] = 1
                else:
                    self.data[ row, col] = 0
                    #self.data[row * self.col + col] = 0

あえてコメントアウトしていますが、本来、コメントアウトされた書き方でないとエラーになります。

しかし、今回、メソッドのオーバーライドをしているので、この記法ができるようになっています。

この方法についてはまた後ほど説明します。(実はこれが今回このライブラリを作ってみようと思ったモチベーションの一つだったりします。)

行列式の計算

    def Det(self):
        temp = copy.deepcopy(self)
        det = 1.0
        for i in range(self.row):
            if self[i,i] == 0:
                for j in range(i,self.row):
                    for k in range(self.row):
                        buf = self[i,k]
                        temp[i,k] = temp[j,k]
                        temp[j,k] = buf
                    det *= -1.0
                    break   


        for i in range(self.row):
            for j in range(self.row):
                if i < j:
                    buf = temp[i,j] / temp[i,i]
                    for k in range(temp.row):
                        temp[j,k] = temp[j,k] - temp[i,k] * buf

        for i in range(temp.row):
            det *= temp[i,i]
        return(det)

次に行列式の計算です。

行列式の計算については、こちらのサイトを参考に、上三角行列への変形 -> 対角成分の積を取る

方法にしています。

ここは、ただ数学の方程式をプログラムに書き起こしただけなので、深くは説明しません。

逆行列の計算

    def Inverse(self, inv_type="GaussJordan"):

        if inv_type == "GaussJordan":
            return(self.GaussJordanInverse())



    def GaussJordanInverse(self):
        det     = self.Det()
        ans     = Matrix(self.row, self.col)
        temp    = copy.deepcopy(self)
        ans.InitializeWithIdentity()

        for i in range(self.row):
            buf = 1.0 / temp[i,i]
            for j in range(self.row):
                temp[i,j] = temp[i,j] * buf
                ans[i,j] = ans[i,j] * buf
        
            for j in range(self.row):
                if i != j:
                    buf = temp[j,i]
                    for k in range(self.row):
                        temp[j,k] = temp[j,k] - temp[i,k] * buf
                        ans[j,k] = ans[j,k] - ans[i,k] * buf

        return(ans)

続いて逆行列の計算です。

本当はLU法もやるつもりだったのですが、とりあえずGaussJordan法が形になったので、このブログにはここまでしか掲載しません。

Gitには追々追加予定です。

こちらも同じくこのサイトの数式を書き起こしただけになります。

加減などの計算も符号演算できるようにする

最後に今回、演算子オーバーロードしてみたので、その説明をしたいと思います。

(正直、この記事はこれを書きたいがためにやったと言っても過言ではないです。)

そもそも演算子オーバーロードとは何か、わからないですよね。

簡単な例を説明すると、例えば以下のような行列があったとします。

A=
|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   7.000   8.000   9.000   |
B=
|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   0.000   0.000   0.000   |

例えばこの2つの行列を足し合わせたい。そう思ったときに、どうするか。

もし、数式で書くなら次のようになります。

ANS = A + B

ANSが3行3列のA,B,2つの行列の和の答えです。

しかし、実際にプログラムを書かれた方ならわかるかと思いますが、自作のクラスではこの書き方では計算ができません。

なぜか。

それは、計算の方法がわからないからです。

なので、だいたいの場合、Add関数なるものを作成し、

ANS = Add(A,B)

このような形で、Add関数内で各要素にアクセスし、その結果を返り値としてANSに格納するという方法をとっていました。

しかし、不思議には思いませんか? ではなぜ、numpyなどのライブラリは行列で+などの演算子が使えるのか。

それは、この+などの演算子を使った場合の処理(特殊メソッド)をオーバーロード、つまり書き換えられるからです。

Pythonの公式ドキュメントにはこちら のように、様々な特殊メソッドが公開しており、これと同じ関数名でクラス内で宣言するだけで、簡単にオーバーロードをすることができます。

では、簡単な例を見ていきましょう。

    def Add(self, other):
        scalar = self.CheckScalar(other)
        ans = Matrix(self.row, self.col)

        if scalar==False:
            if self.row != other.row or self.col != other.col:
                ans.error = True
                return(ans)

            for row in range(self.row):
                for col in range(self.col):
                    ans[row, col] = self[row, col] + other[row, col]

        else:
            for row in range(self.row):
                for col in range(self.col):
                    ans[row, col] = self[ row, col] + other
        return(ans)

    def __add__(self, other):
        return(self.Add(other))

今回のMatrixクラスに入っている足し算のメソッドを紹介します。

まずは先に実行例を示します。

    a.data = [1,2,3,4,5,6,7,8,9]
    b.data = [1,2,3,4,5,6,0,0,0]
    print("a=\n",a)
    print("b=\n",b)
    ans = a+b
    print("ans=\n",ans)

この実行結果がこちらになります。

a=
|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   7.000   8.000   9.000   |

b=
 |  1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   0.000   0.000   0.000   |

c=
 |  2.000   4.000   6.000   |
|   8.000   10.000  12.000  |
|   7.000   8.000   9.000   |

と、このように、def add(self, other): という形で関数を再度宣言して上げることで、+演算子でも計算ができるようになりました。

その他

今回、いくつか特殊メソッドをオーバーロードしてみたので、その紹介もしてみたいとおもいます。 オーバーロードしたのは、以下の3つです。

  1. __str__(self)
  2. __getitem__(self, key)
  3. __setitem__(self, key, value)

1. __str__(self)

これはstr()という文字列に変換する関数でこのクラスが呼ばれたときに何を返すのかを決める特殊メソッドです。

なので、簡単な例でいうと、print()関数がいい例でしょう。

例えば、

a = 1
print(a)

としたときに、出力はどうなるかというと、もちろん下のようになります。

1

ですが、今回のように複数のデータがあるクラス、しかも今回は行列のクラスなので、

できれば行ごとに開業してきれいに表示してほしい。

というわけで、オーバーロードしてみました。

    def __str__(self):
        if self.error == True:
            return("No Matrix")

        s = ""
        for row in range(self.row):
            s += "|"
            for col in range(self.col):
                s += "\t" + "{:.3f}".format(self[row,col])
            s += "\t|\n"
        return(s)

この関数を加えたことで、行列が下のようにprint()できるようになります。

|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   7.000   8.000   9.000   |

|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   0.000   0.000   0.000   |

|   2.000   4.000   6.000   |
|   8.000   10.000  12.000  |
|   7.000   8.000   9.000   |

2. _getitem__(self, key), 3. _setitem__(self, key, value)

この2つについてはほぼ役割は同じなのでまとめて説明します。

この2つの特殊メソッドの役割は、以下のような場面です。

a       = [1,2,3]
b       = a[0]
a[1]    = 4

このコードの2行目が_getitem__, 3行目が_setitem__を使っています。

つまり、

要素を指定して値を取り出すときにどの要素を取り出すかを決めるのが__getitem__,

要素を指定して値を書き込むときにどの要素に書き込むかを決めるのが__setitem__

になります。

    def __setitem__(self, key, value):
        if len(key) == 2:
            row = key[0]
            col = key[1]
        elif len(key) == 1:
            row = 0
            col = key[0]
        else:
            return(None)
        if row>=self.row or row < 0 or col >= self.col or col < 0:
            return(None)

        self.data[ row * self.col + col ] = value


    def __getitem__(self, key):
        if len(key) == 2:
            row = key[0]
            col = key[1]
        elif len(key) == 1:
            row = 0
            col = key[0]
        else:
            return(None)
        if row>=self.row or row < 0 or col >= self.col or col < 0:
            return(None)

        #print("row:",row," col:",col)
        return(self.data[ row * self.col + col])

この2つを宣言することで、クラス内やクラス外で行列の要素にアクセスする際、

A[0,0]

のような形でアクセスすることができるようになります。

最後に

というわけで、今回は行列のクラスを試しにつくってみました。

今まで、手を出してなかった特殊メソッドのオーバーロードを試すことができたので、かなりいい勉強になりました。

今後もクラスを作る際には積極的に作って行きたいとおもいます。

ではでは。

Jupyterにも差し込める作図ツールdraw.ioを使ってみたよ

draw.io を使ってみたよ

draw.ioとは?

draw.ioは高性能な作画ツールです。

G Suite Markerplaceの説明文をお借りすると以下のように説明しています。

draw.io is completely free online diagram editor built around Google Drive(TM), that enables you to create flowcharts, UML, entity relation, network diagrams, mockups and more.

draw.ioはGoogle Driveを利用した完全フリーのダイアグラムエディタです。 このエディタでは、フローチャートUML、ネットワーク図などなどを描くことができます。

つまり、フローチャートやクラス図など、プログラムを書き始める前やあるいはあとに、プログラム全体の構成を書くのに便利なツールなわけです。

では、なぜこれをここで紹介したのかというと、最近紹介しました、Jupyter Notebook を使ってみたよ でJupyter Notebookにフロー図を差し込みたいと思ったからです。

最近、Deep Learningの勉強を今更ながらはじめたのですが、Notebookにレイヤやパーセプトロンなどの図を差し込まないとなかなか後で見て分かりづらかったので、 それに適したツールを探したところ見つかったのがこちらというわけです。

どういうところで使えるの?

プログラムを書きなれた方であれば、このツールを起動すればこのツールの良さがすぐに分かるかとは思いますが、

そもそも、あまりプログラムを書かない方だと、なかなかこのツールの良さは伝わらないかとおもいます。

そこで、今回はあまりクラス図やフロー図など、事前知識が必要な用途については言及せず、あくまでJupyterに差し込む図を簡単に作れるよという紹介をしたいとおもいます。

今回は自分が学習用に分けていたリポジトリを参考に説明します。 

github.com

まずは下の画像を見てください。

f:id:ryo_udon:20200614155211p:plain

こちらに差し込んでいる見た目のいい図(自分でいうなよと思わないでください。。。)がすべてこのdraw.ioで作成したものになります。

また、こちらの図のように、図の中にTex形式で数式を記述することもできます。

f:id:ryo_udon:20200614155311p:plain

このようにdraw.ioは、そこそこ見た目のいい図が簡単に作成でき、しかもjupyter notebookに簡単にコピーアンドペーストができる、

そういう超便利ツールなわけです。

どうやって使うの?

使い方は至って簡単ですので順を追って説明していきます。

まずはこちらのページに行きます。

すると、次のように新しくダイアグラム(図)を作るか、それとも既存のものを作るかを聞いてきます。

f:id:ryo_udon:20200614155332p:plain

今回は新しく図を作っていきますので "Create New Diagram" を選択します。

するとどういう図を作りたいかというフォーマットの選択画面に移動します。

f:id:ryo_udon:20200614155348p:plain

今回は特にフローチャートなどはつくらず、簡単なお絵かきだけしようと思うので、 "Blank Diagram" を選択します。


注意

ちなみに、おそらく起動した際にDesktop版のインストールを進められるかと思いますが、現状はインストールをおすすめしません。

理由は、上のdraw.ioの良さの一つに上げた、数式が書けないからです。

Q&Aを確認したところ、LaTex形式での数式記入はブラウザ版のみの対応となっており、デスクトップ版にはまだ追加されていないようです。

そのため、数式を書くたびにわざわざ別のウィンドウで開き直すという作業が必要になるデスクトップ版はあまりおすすめしません。


図の描き方

図の描き方は至ってシンプルです。

左のサイドバーにある、豊富な図形の中から使いたいものをクリックするだけで、真ん中の編集エリアに図形が現れます。あとはそれをPowerPointの図形を編集するのと同じ用に、 上下左右などにある青い点をクリックし、ドラッグしてあげることでサイズを変更できるようになります。

f:id:ryo_udon:20200614155441p:plain

もし図形同士を先で結びたいのなら、線を結びたい元の図形をクリックし、結びたい方向に表示された矢印をクリックしてあげれば、簡単に線をつないでくれます。

f:id:ryo_udon:20200614155452p:plain

もちろん、クリックのあとにドラッグをしてあげれば任意のところに線を伸ばすことも可能です。

ここについては、自分で色々と弄っていくと操作方法もわかってくるでしょう。

数式の描き方

数式については、少し混乱するかもしれませんが、やり方を覚えればあとは、都度都度文法をGoogleで調べるだけになります。

例えばこちらの図の矢印の横に、

f:id:ryo_udon:20200614155514p:plain

$$y = \frac{1}{x}$$

という数式を追加したいとします。

まず、メニューバーから"Extras"->"Mathmatical Setting" をクリックします。

f:id:ryo_udon:20200614155529p:plain

もし、この設定がONになっていれば、再度"Extras"を開いたときに"Mathmatical Setting"の横にチェックがついているはずです。

その後、Latex形式で数式を書いていきます。

ちなみに余談ですが、draw.ioでは、3つの数式記述法に対応しています。リンク

今回は、二番目の "$$"で前後を囲むLatexの記述法で数式を書いていきます。

まず、左にあるサイドバーから、Textという図をクリックし、編集エリアにテキストボックスを追加します。

その後、このテキストボックスに以下の文を追加します。

$$ y = \frac{1}{x} $$

f:id:ryo_udon:20200614155554p:plain

ここで、

$$ がこの間に数式を書くよという宣言

\frac が分数を意味しています。

この記入が終わったら、あとはテキストボックス外の編集ページのどこかをクリックすると、勝手に数式に変換してくれるのです。

f:id:ryo_udon:20200614155609p:plain

これを使うことで、自分のJupyter Notebookの完成度をより一層高めることができるようになります。

保存や出力について

保存は他のアプリケーションと同じく、

"File" -> "Save" または "Save as"

から保存ができます。

また画像やpdfで出力したいときには、

"File" -> "Export as"

から好きな形式で保存することができます。

Jupyterへの添付

最後に、今回の目玉であるJupyter Notebook への挿入についてです。

今回作成した図を実際にJupyter Notebookへペーストする方法について順を追って説明します。

図形の整理

まず、編集ページの中を貼り付けたい図形のみにします。

これは、このあとにコピーする際に、page単位でのコピーとなるためです。

なので、もし貼り付けたくない図形などがある場合は、新たにpageを作成して、そちらへ移動しましょう。

埋め込み式(html形式)への変換

図形の整理が終わったら、下の図のように"File" -> "Embed" -> "Image"を選択していきます。

f:id:ryo_udon:20200614155642p:plain

次に色々と設定項目があるウィンドウがでてきますが、影の追加などについてなので、特にイジる必要はありません。

そのまま、"Embed"をクリックします。

すると下の図のように、html形式に変換してくれます。

f:id:ryo_udon:20200614155701p:plain

あとはこれを Ctrl + C か、下のCopyをクリックしてコピーしたら、あとはJupyter Notebookに貼り付けるだけです。

Jupyter Notebookへの貼り付け

Jupyter Notebookへの貼り付けは何も難しいことはありません。

ただ、図を差し込むようの Markdownブロックを作ってあげ、そこに先程コピーしたhtml形式のimageタグを貼り付けて上げれば、

あとはおなじみの Ctrl + Enter(Return)を押したらJupyter Notebookに図形が表示されます。

f:id:ryo_udon:20200614155746p:plain

 終わりに

というわけで、今回はdraw.ioという描画ツールについて紹介しました。

draw.ioは、これまでパソコンでノートを作成する際に障害になっていた"手書きの図を作るのに時間がかかる”という問題を解決してくれる超便利ツールです。

今回はJupyter に差し込むにはという部分しか説明していませんが、プログラムを書く際の簡単な構成図を書くのにもかなり使えます。

ぜひ、色々と活用してみてください。

ではでは

/*コードブロックに言語名を表示*/ pre.code:before { content: attr(data-lang); display: inline-block; background: white; color: #666; padding: 3px; position: absolute; margin-left: -10px; margin-top: -30px; } pre.code { padding-top: 30px !important; }