kalax’s diary

日本語の勉強のためのブログ

「Pythonでつくる対話システム」を読んでます

詰まったところをメモする。
なお、書籍は第1版第1刷(2020/2/25発行)のものを読んでいる。

www.ohmsha.co.jp

p.21: mecab-python3_test.pyが動作しない

実行時にエラーが出た。

$ python3 mecab-python3_test.py

Failed initializing MeCab. Please see the README for possible solutions:

    https://github.com/SamuraiT/mecab-python3#common-issues

If you are still having trouble, please file an issue here, and include the
ERROR DETAILS below:

    https://github.com/SamuraiT/mecab-python3/issues

issueを英語で書く必要はありません。

------------------- ERROR DETAILS ------------------------
arguments:
error message: [ifs] no such file or directory: /usr/local/etc/mecabrc
----------------------------------------------------------

  1 from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
Traceback (most recent call last):
  File "mecab-python3_test.py", line 3, in <module>
    mecab = MeCab.Tagger()
  File "/home/*****/.local/lib/python3.8/site-packages/MeCab/__init__.py", line 124, in __init__
    super(Tagger, self).__init__(args)
RuntimeError
$


以下の記事を見て解決した。 min117.hatenablog.com


もしくはサポートページにあった方法でも行けるかもしれない(試していないためわからないが)。

PythonMeCabを使用した際にRuntime Errorが出る

mecab-python3のバージョンにより正しく動作しない場合があります.バージョンを指定してインストールしてください (例: $pip install mecab-python3==0.996.5)


サポートページはこちら↓
github.com

p.22: PC版telegramのアカウント作成

認証コードがSMSに届かなかった(おそらくこちらの単なるミス)。
先にAndroid版telegramアプリでアカウント作成し、PC版のサインイン画面で「Quick log in using QR code」を押して表示されるQRコードを読み込んでログインした。

p.27: echo_system.pyが動作しない

実行時にエラーが出て、メッセージを送っても返信されない。

$ python3 echo_system.py
No error handlers are registered, logging exception.
Traceback (most recent call last):
  File "/home/*****/.local/lib/python3.8/site-packages/telegram/ext/dispatcher.py", line 447, in process_update
    handler.handle_update(update, self, check, context)
  File "/home/*****/.local/lib/python3.8/site-packages/telegram/ext/handler.py", line 160, in handle_update
    return self.callback(update, context)
  File "/(中略)/dsbook/telegram_bot.py", line 13, in start
    input = {'utt': None, 'sessionId': str(update.message.from_user.id)}
AttributeError: 'CallbackContext' object has no attribute 'message'

(編注:以下、メッセージを送るたびに上記のエラーが吐き出される)


これについてはサポートページに説明があった。

Telegramから応答が返ってこない

python-telegram-botのバージョンが上がったため正しく動作しなくなりました.過去のバージョンを指定してインストールしてください (例: $pip3 install python-telegram-bot==12.8)

BrainCrushのインタプリタを作った

Brainfuckの後継言語であるBrainCrushのインタプリタを作成した。
新たな命令や仕様変更があっただけで、大部分はBrainfuckと変わっていない。

Brainfuckインタプリタについては前回の記事を参照してほしい。

everykalax.hateblo.jp

また作成にあたり、以下のページを参考にさせていただいた。

[1] dic.nicovideo.jp

[2] blog.metarin.net

Brainfuckからの変更箇所

参考資料[1]から引用する。

追加された命令

・「|」 … 現在のポインタの位置にある値と、その次のポインタの位置にある値をビットOR演算し、その次のポインタの位置に代入する。また、ポインタは現在の位置から1つ進められる。
「&」 … 現在のポインタの位置にある値と、その次のポインタの位置にある値をビットAND演算し、その次のポインタの位置に代入する。また、ポインタは現在の位置から1つ進められる。
・「~」 … 現在のポインタの位置にある値をビットNOT演算(つまりビット反転)する。
・「^」 … 現在のポインタの位置にある値と、その次のポインタの位置にある値をビットXOR演算し、その次のポインタの位置に代入する。また、ポインタは現在の位置から1つ進められる。

追加された仕様

・実行時、メモリにはHello, world!に相当する72,101,108,108,111,44,32,119,111,114,108,100,33が0番地よりこの順で積まれる。
・終了時、現在指し示すポインタの値を、ポインタの値が0になるまでポインタを進めて出力する。

参考資料[2]によれば、この2つ目の仕様は、終了時に[.>]を実行することと等価である。 そのため今回は、getline()で受け取ったBrainCrushコードの末尾に[.>]を付加することにより実装した。

コード

前回作成したBrainfuckのコードに加筆する形で作成した。
2行目で宣言しているフラグ"IS_CRUSH"をfalseにすれば、Brainfuckインタプリタとして使用することも可能である。
(メモリの初期化部分はもっといい書き方があるはずなので、後日修正するかもしれない)

#define MEMORY_NUMBER 20
#define IS_CRUSH true
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main() {
  int ptr = 0;
  vector<char> memory(MEMORY_NUMBER, 0);
  vector<char> hello = {72, 101, 108, 108, 111, 44, 32,
                        119, 111, 114, 108, 100, 33};

  // BrainCrushの場合、初期化時にメモリにHello, world!を入れておく
  if (IS_CRUSH) {
    for (int i = 0; i < 13; i++) {
      memory.at(i) = hello.at(i);
    }
  }

  string prog;
  getline(cin, prog);

  // BrainCrushの場合、終了時に[.>]が実行される
  if (IS_CRUSH) {
    prog += "[.>]";
  }

  char c;
  int endParenthesis = -1;
  stack<int> frontParenthesis;
  
  for (int i = 0; i < prog.length(); i++) {
    c = prog.at(i);

    switch (c) {
      case '>':
        ptr++;
        break;
      case '<':
        ptr--;
        break;
      case '+':
        memory.at(ptr)++;
        break;
      case '-':
        memory.at(ptr)--;
        break;
      case '.':
        cout << memory.at(ptr);
        break;
      case ',':
        cin >> memory.at(ptr);
        break;
      case '[':
        if (memory.at(ptr) == 0) i = endParenthesis - 1;
        frontParenthesis.push(i);
        break;
      case ']':
        endParenthesis = i;
        if (memory.at(ptr) != 0) i = frontParenthesis.top() - 1;
        frontParenthesis.pop();
        break;
      // 以下、BrainCrushで追加された命令(and, or, not, xor)
      case '|':
        ptr++;
        memory.at(ptr) = memory.at(ptr) | memory.at(ptr-1);
        break;
      case '&':
        ptr++;
        memory.at(ptr) = memory.at(ptr) & memory.at(ptr-1);
        break;
      case '~':
        memory.at(ptr) = ~memory.at(ptr);
        break;
      case '^':
        ptr++;
        memory.at(ptr) = memory.at(ptr) ^ memory.at(ptr-1);
        break;
      default:
        break;
    }
  }

 
  cout << endl << "[END OF RUNNING]" << endl;
}

実行

最初からメモリに"Hello, world!"が積まれており、なおかつ終了時に勝手に[.>]を実行してくれるので、何も入力しなくても"Hello, world!"が出力される。

$ g++ brainfuck.cpp
$ ./a.out

Hello, world!
[END OF RUNNING]
$

c++でBrainfuckのインタプリタを作った

プログラミングの練習として。
単に動作するものを目標として作成したため、動作速度などは無視している。

追記)後継言語であるBrainCrushのインタプリタも作った。 everykalax.hateblo.jp

コード

標準入力からgetline()Brainfuckコードを受け取り、そのコードをfor文で1文字ずつswitch文を使いながら実行しているだけである。
また、[ ]の対応関係についてはスタックを使うことで解決した。 [の実行時に現在位置をスタックfrontParenthesisに入れ、]の実行時にスタックから取り出した位置に移動するという方法で実装した。

#define MEMORY_NUMBER 20
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main() {
  int ptr = 0;
  vector<char> memory(MEMORY_NUMBER, 0);
  
  string prog;
  getline(cin, prog);

  char c;
  int endParenthesis = -1;
  stack<int> frontParenthesis;
  
  for (int i = 0; i < prog.length(); i++) {
    c = prog.at(i);

    switch (c) {
      case '>':
        ptr++;
        break;
      case '<':
        ptr--;
        break;
      case '+':
        memory.at(ptr)++;
        break;
      case '-':
        memory.at(ptr)--;
        break;
      case '.':
        cout << memory.at(ptr);
        break;
      case ',':
        cin >> memory.at(ptr);
        break;
      case '[':
        if (memory.at(ptr) == 0) i = endParenthesis - 1;
        frontParenthesis.push(i);
        break;
      case ']':
        endParenthesis = i;
        if (memory.at(ptr) != 0) i = frontParenthesis.top() - 1;
        frontParenthesis.pop();
        break;
      default:
        break;
    }
  }
 
  cout << endl << "[END OF RUNNING]" << endl;
}

動作方法

適当なファイルに以上のコードを張り付けて保存し、ターミナルから

$ g++ brainfuck.cpp
$ ./a.out

で実行。
入力受付け状態になるので、ここでbrainfuckコードを入力すると実行される。仕様上、一行しか受け付けないので注意(これについては後々修正するかも)。

製作過程

wikiで言語仕様を見て、大部分はすぐに書けた。

ja.wikipedia.org

スタックについてはわからなかったので、以下のサイトを参考にした。

qiita.com

最初に書いたコードは以下であった。

#define MEMORY_NUMBER 20
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main() {
  int ptr = 0;
  vector<char> memory(20, 0);
  
  string prog;
  getline(cin, prog);

  char c;
  int endParenthesis = -1;
  stack<int> frontParenthesis;
  
  for (int i = 0; i < prog.length(); i++) {
    c = prog.at(i);

    switch (c) {
      case '>':
        ptr++;
        break;
      case '<':
        ptr--;
        break;
      case '+':
        memory.at(ptr)++;
        break;
      case '-':
        memory.at(ptr)--;
        break;
      case '.':
        cout << memory.at(ptr);
        break;
      case ',':
        cin >> memory.at(ptr);
        break;
      case '[':
        if (memory.at(ptr) == 0) i = endParenthesis;
        frontParenthesis.push(i);
        break;
      case ']':
        endParenthesis = i;
        if (memory.at(ptr) != 0) i = frontParenthesis.top();
        frontParenthesis.pop();
        break;
      default:
        break;
    }
  }
 
  cout << endl << "[END OF RUNNING]" << endl;
}

試しにHello worldを出力するコードの冒頭部分(Hを出力するところまで)を実行させてみたら、Segmentation faultエラーが発生してしまった。

$ ./a.out
>+++++++++[<++++++++>-]<.
Segmentation fault (core dumped)
$ 

gdbデバッガを用いてステップ実行させながら動作を確認すると、2回目のループでエラーが起こることが分かった。frontParenthesisというスタックが空なのに、中身を確認しようとしたために起きたエラーであった。
(gdbについてはここを参照した)

Program received signal SIGSEGV, Segmentation fault.
0x0000000008002730 in main () at brainfuck.cpp:45
45              if (memory.at(ptr) != 0) i = frontParenthesis.top();

そのため該当箇所をif (memory.at(ptr) == 0) i = endParenthesis - 1;if (memory.at(ptr) != 0) i = frontParenthesis.top() - 1;のように修正した。 つまり]があったら対応する[の1つ前に飛ばすことで、[を実行させるように変更した。 そうしたらしっかり動いてくれた。

$ ./a.out
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++.
Hello World!

[END OF RUNNING]
$ 

Hello worldのコードについてはこちらから拝借した。

www.kmonos.net

draw.ioで既存図形の形を編集する

draw.ioというサイトがある。

app.diagrams.net

このサイトではオンライン上で回路図を作成することができるのだが、既存の図形では不都合だったり、単に形が気に食わないことがある。
そんな時は既存の図形を編集して微調整すればよい。

方法

初めに、変更したい図形を配置・選択した状態にしておく。
そして右側にあるメニューから「スタイル」(Style)タブを開き、タブ中央にある「シェイプを編集」(Edit Shape)を押せば編集画面に移行できる。

実践

今回はMOSFET No Bulk (N)という図形を編集することにする。
この図形を囲む円を除去することが目的である。

f:id:kalax:20210428170543p:plain
MOSFET No Bulk (N)

編集画面に行くと、以下のようなコードがずらずらと表示されるはずだ。

<shape aspect="variable" h="110" name="MOSFET N No Bulk" strokewidth="inherit" w="100">
  <connections>
    <constraint name="G" perimeter="0" x="0" y="0.5" />
    <constraint name="D" perimeter="0" x="0.7" y="0" />
    <constraint name="S" perimeter="0" x="0.7" y="1" />
  </connections>
  <background>
    <ellipse h="90" w="90" x="10" y="10" />
  </background>
  <foreground>
    <fillstroke />
    <path>
      <move x="45" y="35" />
      <line x="70" y="35" />
      <line x="70" y="0" />
      <move x="45" y="75" />
      <line x="70" y="75" />
      <line x="70" y="110" />
      <move x="42" y="32" />
      <line x="42" y="78" />
      <move x="0" y="55" />
      <line x="42" y="55" />
    </path>
    <stroke />
    <strokewidth width="2" />
    <path>
      <move x="45" y="30" />
      <line x="45" y="80" />
    </path>
    <fillstroke />
  </foreground>
</shape>

今回のように除去するだけなら、このコードのすべてを理解する必要はない。
ヘルプページを確認して、除去したい線がどこで描画されているのかを探すだけでよい。

www.diagrams.net

ここでは円を削除したいので、コード中からそれっぽい文言を探す。 すると<ellipse>というタグが見つかった(ellipce=楕円)。

一応ヘルプページも確認すると、

<ellipse> - attributes "x", "y", "w", "h", all required decimals.

とあり、おそらく中心点と半径を指定していると推測できる。

それでは実際に<ellipse>を消去してみる(<ellipse>を囲む<background>も不要かと思われるが残しておいた)。

<shape aspect="variable" h="110" name="MOSFET N No Bulk" strokewidth="inherit" w="100">
  <connections>
    <constraint name="G" perimeter="0" x="0" y="0.5" />
    <constraint name="D" perimeter="0" x="0.7" y="0" />
    <constraint name="S" perimeter="0" x="0.7" y="1" />
  </connections>
  <background>
  </background>
  <foreground>
    <fillstroke />
    <path>
      <move x="45" y="35" />
      <line x="70" y="35" />
      <line x="70" y="0" />
      <move x="45" y="75" />
      <line x="70" y="75" />
      <line x="70" y="110" />
      <move x="42" y="32" />
      <line x="42" y="78" />
      <move x="0" y="55" />
      <line x="42" y="55" />
    </path>
    <stroke />
    <strokewidth width="2" />
    <path>
      <move x="45" y="30" />
      <line x="45" y="80" />
    </path>
    <fillstroke />
  </foreground>
</shape>

編集後に右下の「印刷プレビュー」を押すと、図形のプレビュー表示ができる。

f:id:kalax:20210428170315p:plain
<ellipse>を消去した図形
しっかり円が消去できた。

編集が終わったら「適用」を押して編集内容を適用する。
この編集は最初に選択していた図形にのみ適用されるため、編集した図形を複数使用したい場合は、使いたい分だけコピーペーストすればよい。

さらに改造

まだ気になる箇所があるので、さらに変更を加える。

コネクタの設定を追加し、MOSFETのBodyから矢印を引けるようにした。 また線の太さや長さ、間隔を微調整した。

完成したp-ch MOSFETを以下に示す。

<shape aspect="variable" h="110" name="MOSFET N No Bulk" strokewidth="inherit" w="100">
  <connections>
    <constraint name="G" perimeter="0" x="0" y="0.5" />
    <constraint name="D" perimeter="0" x="0.7" y="0" />
    <constraint name="S" perimeter="0" x="0.7" y="1" />
    <constraint name="B" perimeter="0" x="0.7" y="0.5" />
  </connections>
  <background>
  </background>
  <foreground>
    <fillstroke />
    <path>
      <move x="45" y="35" />
      <line x="70" y="35" />
      <line x="70" y="0" />
      <move x="45" y="75" />
      <line x="70" y="75" />
      <line x="70" y="110" />
      <move x="37" y="35" />
      <line x="37" y="75" />
      <move x="0" y="55" />
      <line x="37" y="55" />
    </path>
    <stroke />
    <strokewidth width="1" />
    <path>
      <move x="45" y="35" />
      <line x="45" y="75" />
      <move x="45" y="55" />
      <line x="70" y="55" />
      <line x="63" y="50" />
      <line x="63" y="60" />
      <line x="70" y="55" />
      <fillcolor color="#000000" />
    </path>
    <fillstroke />
  </foreground>
</shape>

n-ch MOSFETも作ったので以下に示す(矢印の向きが違うだけだが)。

<shape aspect="variable" h="110" name="MOSFET N No Bulk" strokewidth="inherit" w="100">
  <connections>
    <constraint name="G" perimeter="0" x="0" y="0.5" />
    <constraint name="D" perimeter="0" x="0.7" y="0" />
    <constraint name="S" perimeter="0" x="0.7" y="1" />
    <constraint name="B" perimeter="0" x="0.7" y="0.5" />
  </connections>
  <background>
  </background>
  <foreground>
    <fillstroke />
    <path>
      <move x="45" y="35" />
      <line x="70" y="35" />
      <line x="70" y="0" />
      <move x="45" y="75" />
      <line x="70" y="75" />
      <line x="70" y="110" />
      <move x="37" y="35" />
      <line x="37" y="75" />
      <move x="0" y="55" />
      <line x="37" y="55" />
    </path>
    <stroke />
    <strokewidth width="1" />
    <path>
      <move x="45" y="35" />
      <line x="45" y="75" />
      <move x="70" y="55" />
      <line x="46" y="55" />
      <line x="52" y="50" />
      <line x="52" y="60" />
      <line x="46" y="55" />
      <move x="47" y="53" />
      <fillcolor color="#000000" />
    </path>
    <fillstroke />
  </foreground>
</shape>

f:id:kalax:20210428170427p:plain
p-chとn-chの完成図

これで使いたい形に編集できた。

文章校閲のためにTomarigiを入れた際のメモ

書いた文章を校閲したいが、オンラインツールはサーバに情報が送信されてしまうため避けたい。 そこでローカルで使用可能な校閲ツール"Tomarigi"をインストールした。

環境

Windows10 home(バージョン20H2)

インストール方法

公式ページからダウンロードできる。
Tomarigi | PaWeL:日本語表現法開発プロジェクト-青山学院大学-

.NET Framework 3.5が入っているか確認する方法

アプリと機能からは確認できない。レジストリエディタで確認することになる。
どのバージョンの .NET Framework がインストールされているか確認する方法