2024年4月29日月曜日

Termux on Debian(GUI)でibusが落ちる問題の解決策

ことのはじまり

前回の記事でKindleFireHD10(13世代)にUbuntuのGUIを構築したが日本語入力がうまく起動しなかった。
ブラウザも使いたかったし、Ubuntuを捨ててDebianに切り替えることにした。
以下その手順のまとめ。

状況整理(環境と症状)

環境は次の通り
  • KindleFireHD10(13世代)
  • Termux上にUbuntuをインストールし、xfce4でGUIを利用
  • 日本語入力はibusとAquaMozcを利用
発生した症状
  • AquaMozcの設定画面をGUIで開くとどこかをクリックした瞬間落ちる
  • 最初は起動していてくれたibus-daemonが途中から起動しなくなる(手動起動も不可)

日本語環境の設定はこの記事を参考にした。

debianの構築

そもそも論として、ubuntuの入れ方とubuntuが相性が悪いのではと疑い始めprootを経由してdebianを構築することにした。
手順としては以下の記事のubuntuをdebianに読み替えて手順を進めればよい。 debianのインストールが完了したら以下のコマンドを実行して必要なものをインストール
apt-get update && apt-get upgrade -y
apt-get install sudo-y
apt-get install xfce4
apt-get install ibus-mozc

xfce4は前回の記事通り数時間かかるのでしばらく待つ。

日本語環境の設定と問題とソリューション

上記の記事に従って日本語環境設定しても上記の症状は改善しなかった。
そこで、以下の方針で解決することにした
  • ibusを手動で起動する
  • 手動起動に伴いibus-daemonの自動起動設定は削除する
ソリューションとしては以下のコマンドを起動時毎回ターミナルから実行すれば解決する.
ibus start

これでとりあえず安定して日本語入力ができるようにはなる。
しかし結果としてAquaMozcのGUI問題は解決できなかった。
結論としてはとりあず使えるのでヨシッ

2024年3月3日日曜日

Kindle FireHD10(13世代)でUbuntu GUIを使おう

ことのはじまり


FireHD10(13世代)が安かったので買ってみたが使い道がなかったので、 Ubuntu on TermuxでLinuxタブレットにしようとした際の作業を備忘として残す。

設定手順

    手順一覧

  1. F-DroidのダウンロードとTermuxのインストール
  2. TermuxへのUbuntu導入(CLI)
  3. Ubuntu初期設定
  4. GUIの導入

F-DroidのダウンロードとTermuxのインストール

TermuxはGoogle Play上にもあるが、こちらは最早アップデートされていないため、 F-Droidからダウンロードする。 Silk Browserで開いて不明なアプリの許可->インストールで導入は終了。 あとはリポジトリとパッケージを更新するので、
pkg update && pkg upgrade -y

TermuxへのUbuntu導入(CLI)

TermuxへUbuntuを導入する方法は、Prootを使ったものやユーザーによるものなどいくつかあるが、 今回は比較的オーソドックスなユーザーによるスクリプトを使って行う。 今回はこれを使う installation stepsに従って上から順に以下のコードを実行していく。
#上から順にひとつずつ
apt-get install wget -y
apt-get install proot -y
apt-get install git -y
cd ~
git clone https://github.com/MFDGaming/ubuntu-in-termux.git
cd ubuntu-in-termux
chmod +x ubuntu.sh
./ubuntu.sh -y
./startubuntu.sh
終わったら次のようにUbuntuが起動してRootユーザーでログインしている。 とりあえず更新をして、sudoとvimを入れる。
#上から順にひとつずつ
apt-get update && apt-get upgrade -y
apt-get install vim -y
apt-get install sudo -y
ずっとルートユーザーでやるわけにはいかないので、ユーザーを追加する。
#ユーザーの追加
adduser [ユーザー名]
echo "[ユーザ名] ALL=(ALL:ALL) ALL" > /etc/sudoers
ユーザーを切り替えたり再度ログインしたりすると
#出るエラー
groups: cannot find name for group[メッセージ]
みたいなエラーが出る。
ユーザーをグループに入れて解決する。
ここではtermuxのユーザー名を使用するのでメモの準備をしておき次のコマンドを実行する。
#グループエラーの解消
exit //ubuntuから抜ける
whoami //termuxのユーザー名を確認しメモする
./startubuntu.sh
addgroup --system --gid 3003 inet
addgroup --system --gid 9997 everybody
addgroup --system --gid 20237 [termuxのユーザー名]_cache
addgroup --system --gid 50237 all_[termuxのユーザー名(u_を除いた部分)]
addgroup --system --gid 99909997 u999_everybody
reset
ここまででCLIのセットアップは完了

GUIの導入

クライアントにはこれを使う とりあえずxfce4のインストール
#xfce4のインストール
sudo apt-get install xfce4

ここに10時間ぐらいかかる。
キーボード選択画面が出るので自由に選択する。
XSDLのアプリを起動し、画面のIPアドレスとポートをメモ接続用に以下の設定を.bashrcに記入する。
#.bashrcに記載
export DIPLASY=:127.0.0.1:0
export PULSE_SEVER=tcp:127.0.0.1:[ポート番号]

これでデスクトップが表示される。
中のアプリのアップデート等はまた今度

参考にさせてもらった記事

Termux Ubuntuにログインとユーザ設定
https://www.lisz-works.com/entry/termux-ubuntu-adduser
Android でもとりあえず Ubuntu のデスクトップ環境を使いたい(Termux 版)
https://qiita.com/tacchi/items/a532aafd9c05fdf7be45
magadan/Ubuntu-Focal-20.04-LTS-for-Termux
https://github.com/magadan/Ubuntu-Focal-20.04-LTS-for-Termux
groups: cannot find name for group ID #2
https://github.com/Neo-Oli/termux-ubuntu/issues/2

2023年12月15日金曜日

JavaScriptで標準入力をPython3っぽく受け取る

Paizaやその他、競プロでJavaScriptを使う時、他の言語と違って面倒なのは標準入力の受取である。 全部一気に受け取ってしまって、それらを配列で管理しなければならない。 この管理が非常に面倒なので、Python3みたいにinput()で一行ごとに受け取りたいと考えることしばしばである。 ふと思ったが、標準入力を一気に受け取って、一行ずつ吐き出すオブジェクトをつくればよいのである。 ということで早速実装
//classは巻き上げられないので最初に書く
class inputClass{
    constructor(){
        this.inputCounter = 0;
        this.list = [];
        this.len = 0
    }
    append (value){
        this.list.push(value);
        this.len++;
    }
    input(){
        if (this.inputCounter < this.list.length){
            return this.list[this.inputCounter++];
        }else{
            return '';
        }
    }
}
let data = new inputClass();
let reader = require("readline").createInterface({
    input: process.stdin,
    output: process.stdout
});
reader.on("line", (line) => {
    data.append(line.split(' '));
})
reader.on("close", () => {
    //読み込み行数を保持するlenプロパティで末尾まで取り出し
    for (let i=0; i < data.len; i++){
        console.log(data.input());
    }
});

入力
1 1
2 2
a
c
f g h

出力
[ '1', '1' ]
[ '2', '2' ]
[ 'a' ]
[ 'c' ]
[ 'f', 'g', 'h' ]

概ね期待通りに実装できた。 イテレーターで実装したほうが便利だと作った後に気づいたが ひとまず実装したのでよしとしたい。

2023年2月17日金曜日

Paizaのレベルアップ問題集『巡回セールスマン問題(近似)』の解説

 Paizaの問題の解説がさっぱりしすぎてて頭を悩ませているのでメモ程度に自分なりの解説を作ってみる。

1. 概要

今回の問題

2-近似によるTSP (paizaランク A 相当)

https://paiza.jp/works/mondai/tsp_problems/tsp_problems__tsp_approx

Python 3で解答に従い書いたコードはこんな感じ

import math

n = int(input())
cities = [list(map(int, input().split())) for _ in range(n)]
routes = []
mst = [[] for _ in range(n)] #隣接行列
tour = []

def calc_dist(a, b):
    return math.sqrt(math.pow(a[0] - b[0], 2.0) + math.pow(a[1] - b[1], 2.0))

class UnionFind:
    def __init__(self, n):
        self.n = n
        self.parent = [i for i in range(n)]
        
    def get_parent(self, a):
        a = self.parent[a]
        while a != self.parent[a]:
            a = self.parent[a]
        return a
    
    def unite(self, a, b):
        a = self.get_parent(a)
        b = self.get_parent(b)
        self.parent[b] = a
    
    def same(self, a, b):
        return self.get_parent(a) == self.get_parent(b)


def dfs(mst, tour, now, before):
    tour.append(now)
    if now != before:
        for nxt in mst[now]:
            if nxt in tour:
                continue
            dfs(mst, tour, nxt, now)
    
for i in range(n):
    for k in range(n):
        tmp = calc_dist(cities[i], cities[k])
        routes.append((tmp, i, k))
        
routes.sort()
uf = UnionFind(n)
for d, i, k in routes:
    if uf.same(i, k):
        continue
    mst[i].append(k)
    mst[k].append(i)
    uf.unite(i, k)

dfs(mst, tour, 0, -1)
print(*tour)


2. 解法

問題を分割して考えていくと答えまでは3ステップ

  1. ユークリッド距離をもとにルートの候補を選定
  2. Union Findをつかって整理
  3. 深さ優先探索でルートを探索
一つ一つ見ていく

2.1 ルートの選定

とりあえず、入力座標の各点から各点までの点と距離を全部計算して、短い順にソートする。短い順にソートすると、最短ルートは、自分から自分に向かう点でユークリッド距離は0になる。
以下の処理がこれに該当する。

for i in range(n):
    for k in range(n):
        tmp = calc_dist(cities[i], cities[k])
        routes.append((tmp, i, k))
        
routes.sort()


2.2 Union Findを使って整理

このままだと、最小全域木はつくれないので、Union Findを使って同じ親を持つルートを排除していく。つまり、(0,0)→(0,0)といったものを消す。また、(0,0)→(0,1)及び(0,0)→(0,2)のうちユークリッド距離の短い方だけを残していく、(0,0) → (0,1)及び(0,1) → (0,0)の一方を消す。そうすることで、mstリストは、最小全域木の隣接リストとなる。

該当部分は以下の通り。

uf = UnionFind(n)
for d, i, k in routes:
    if uf.same(i, k):
        continue
    mst[i].append(k)
    mst[k].append(i)
    uf.unite(i, k)

親が同じならループの先頭に戻り、そうでなければ、隣接リストに情報を記録して、その後に親の情報をufインスタンスに格納していく。

そうすることで親がことなるノードだけの隣接リストが作られる。


3.3 深さ優先探索でルートをたどる。

隣接リストをたどっていく。スタートはどこでもいいが、ここでは0をスタートとしている。

そして出力して終了。


4. まとめ

この問題のポイントは、最小全域木を隣接リストに格納し、それを深さ優先探索でたどっていくとこにあると思う。

参考

公式ブログ
Pythonで「巡回セールスマン問題」を解いてみよう!8つの解法を例題で解説

2022年10月11日火曜日

ワードクラウドを作成する【基本編】

 Colaboratoryで自然言語処理の基本みたいなワードクラウドを生成することができるので使ってみた。

1.そのまま使う(英語のみ)

ワードクライドを作成するライブラリのWordCloudを使うだけならこう。

結果はこんな感じになる。
"the"や"over"が入っていないのは、組み込みのstopwordsに指定されており、画像生成時に削除されているから。
#Colabで使ってDriveに出力する場合
from wordcloud import WordCloud
txt = "The quick brown fox jumps over the lazy dog"
a = WordCloud()
a.generate(text=txt)
#to_fileメソッドで書き出し
a.to_file("/content/drive/MyDrive/tmp/new.png")
wordcloudでそのまま生成したもの
生成例1

stopwordsに"quick"を追加したこのコードでやるとこうなる。
#Colabで実行する場合
#ビルドインのSTOPWORDSをインポートする
from wordcloud import WordCloud, STOPWORDS
txt = "The quick brown fox jumps over the lazy dog"
#brownをstopwordsに加える
stop = set(STOPWORDS)
stop.add("brown")
a = WordCloud(stopwords=stop)
a.generate(text=txt)
a.to_file("/content/drive/MyDrive/tmp/newst.png")
"brown"をstopwordsに加えて生成したもの
生成例2

ワードクラウドでの文字の大きさはword-ranksと出現頻度で決定される。出現頻度への文字の大きさの影響を変更するときはインスタンス作成時にrelative_scalingへ値を与える。
遥かなティペラリー(It's a Long Way to Tipperary)のサビ部分を例にやるとこう。
relative_scaling=0のパラメーターで生成したもの
relative_scaling=0

relative_scaling=0.5のパラメーターで生成したもの
relative_scaling=0.5

relative_scaling=1.0のパラメーターで生成したもの
relative_scaling=1.0

relative_scaling以外で気になったパラメータを公式ドキュメントから抜粋する。
  • height: int (default=200)
    • 画像の縦幅の設定
  • width: int (default=400)
    • 画像の横幅の設定
  • scale: float (default=1)
    • 描写のスケールを設定
  • min_word_length: int, default=0
    • ワードクラウドに含まれるための最低の単語の長さ
  • collocation_threshold: int, default=30 
    • コロケーション(単語と単語のつながり)の判定地
それ以外は公式ドキュメント参照。

2.日本語で使う

wordcloudに日本語辞書は入っていない上に、日本語からワードクラウドを作成するために必要な分かち書きや瀕死の分類などができない。
そこで日本語の形態素解析ツールであるJanomeを使う。
Janomeで簡単に形態素解析を行うには、Tokenizerを使う。日本国憲法前文について、解析を行うとこうなる。
from janome.tokenizer import Tokenizer
txt = """
日本国民は正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民と協和による成果と、わが国全土にわたって自由のもたらす恵沢を確保し、政府の行為によって再び戦争の惨禍が起こることのないようにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は国民の厳粛な信託によるものであって、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基づくものである。われらはこれに反する一切の憲法、法令及び詔勅を排除する。
日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義を信頼して、われらの安全と生存を保持しようと決意した。われらは平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めている国際社会において、名誉ある地位を占めたいと思う。われらは全世界の国民が、ひとしく恐怖と欠乏から免れ、平和の内に生存する権利を有することを確認する。
われらは、いずれの国家も、自国のことのみに専念して他国を無視してはならないのであって、政治道徳の法則は、普遍的なものであり、この法則に従うことは、自国の主権を維持し、他国と対等関係に立とうとする各国の責務であると信ずる。
日本国民は、国家の名誉にかけて、全力をあげて崇高な理想と目的を達成することを誓う。
"""
txt = txt.replace('\n', '')
t = Tokenizer()
for token in t.tokenize(txt):
	print(token)

###出力###
"""
日本	名詞,固有名詞,地域,国,*,*,日本,ニッポン,ニッポン
国民	名詞,一般,*,*,*,*,国民,コクミン,コクミン
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
正当	名詞,形容動詞語幹,*,*,*,*,正当,セイトウ,セイトー
に	助詞,副詞化,*,*,*,*,に,ニ,ニ
選挙	名詞,サ変接続,*,*,*,*,選挙,センキョ,センキョ
さ	動詞,自立,*,*,サ変・スル,未然レル接続,する,サ,サ
れ	動詞,接尾,*,*,一段,連用形,れる,レ,レ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
国会	名詞,一般,*,*,*,*,国会,コッカイ,コッカイ
における	助詞,格助詞,連語,*,*,*,における,ニオケル,ニオケル
代表	名詞,サ変接続,*,*,*,*,代表,ダイヒョウ,ダイヒョー
者	名詞,接尾,一般,*,*,*,者,シャ,シャ
を通じて	助詞,格助詞,連語,*,*,*,を通じて,ヲツウジテ,ヲツージテ
行動	名詞,サ変接続,*,*,*,*,行動,コウドウ,コードー
し	動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
、	記号,読点,*,*,*,*,、,、,、
われ	名詞,代名詞,一般,*,*,*,われ,ワレ,ワレ
ら	名詞,接尾,一般,*,*,*,ら,ラ,ラ
と	助詞,並立助詞,*,*,*,*,と,ト,ト
われ	名詞,代名詞,一般,*,*,*,われ,ワレ,ワレ
ら	名詞,接尾,一般,*,*,*,ら,ラ,ラ
"""

単語自体はtoken.surfaceで取り出せるが、これでは、様々な品詞が混じっているうえ、「日本国民」が「日本」と「国民」に分割されていたりしており、このままワードクラウドに用いることはできない。
そこで、Analizerとtokenfilterを用いる。
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.tokenfilter import *
txt = """
日本国民は正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民と協和による成果と、わが国全土にわたって自由のもたらす恵沢を確保し、政府の行為によって再び戦争の惨禍が起こることのないようにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は国民の厳粛な信託によるものであって、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基づくものである。われらはこれに反する一切の憲法、法令及び詔勅を排除する。
日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義を信頼して、われらの安全と生存を保持しようと決意した。われらは平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めている国際社会において、名誉ある地位を占めたいと思う。われらは全世界の国民が、ひとしく恐怖と欠乏から免れ、平和の内に生存する権利を有することを確認する。
われらは、いずれの国家も、自国のことのみに専念して他国を無視してはならないのであって、政治道徳の法則は、普遍的なものであり、この法則に従うことは、自国の主権を維持し、他国と対等関係に立とうとする各国の責務であると信ずる。
日本国民は、国家の名誉にかけて、全力をあげて崇高な理想と目的を達成することを誓う。
"""
txt = txt.replace('\n', '')
t = Tokenizer()
filters = [CompoundNounFilter(), POSKeepFilter('名詞'), POSStopFilter(['非自立','代名詞'])]
NG = ['非自立', '代名詞'] 
analyzer = Analyzer(tokenizer=t, token_filters=filters)
for token in analyzer.analyze(txt):
  #POSStopFilterがうまく機能しない部分があるので二重にフィルタリングする
  ex = token.extra[0].split(',')
  if all(x not in ex for x in NG):
    print(token)

###出力###
'''
日本国民	名詞,複合,*,*,*,*,日本国民,ニッポンコクミン,ニッポンコクミン
正当	名詞,形容動詞語幹,*,*,*,*,正当,セイトウ,セイトー
選挙	名詞,サ変接続,*,*,*,*,選挙,センキョ,センキョ
国会	名詞,一般,*,*,*,*,国会,コッカイ,コッカイ
代表者	名詞,複合,*,*,*,*,代表者,ダイヒョウシャ,ダイヒョーシャ
行動	名詞,サ変接続,*,*,*,*,行動,コウドウ,コードー
子孫	名詞,一般,*,*,*,*,子孫,シソン,シソン
国民	名詞,一般,*,*,*,*,国民,コクミン,コクミン
協和	名詞,サ変接続,*,*,*,*,協和,キョウワ,キョーワ
成果	名詞,一般,*,*,*,*,成果,セイカ,セイカ
わが国全土	名詞,複合,*,*,*,*,わが国全土,ワガクニゼンド,ワガクニゼンド
自由	名詞,形容動詞語幹,*,*,*,*,自由,ジユウ,ジユー
恵沢	名詞,一般,*,*,*,*,恵沢,ケイタク,ケイタク
確保	名詞,サ変接続,*,*,*,*,確保,カクホ,カクホ
政府	名詞,一般,*,*,*,*,政府,セイフ,セイフ
行為	名詞,サ変接続,*,*,*,*,行為,コウイ,コーイ
戦争	名詞,サ変接続,*,*,*,*,戦争,センソウ,センソー
惨禍	名詞,一般,*,*,*,*,惨禍,サンカ,サンカ
決意	名詞,サ変接続,*,*,*,*,決意,ケツイ,ケツイ
'''
これでワードクラウドに入力する準備は整ったので、あとはjanomeの出力をスペース連結したものをWordCloudに放り込めば完了となる。
ただし、WordCloudには日本語フォントが含まれていないので、フォント指定をすることになる。今回はIPAフォントを利用する。
from pyparsing.helpers import Word
from wordcloud import WordCloud
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.tokenfilter import *
txt = """
日本国民は正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民と協和による成果と、わが国全土にわたって自由のもたらす恵沢を確保し、政府の行為によって再び戦争の惨禍が起こることのないようにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は国民の厳粛な信託によるものであって、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基づくものである。われらはこれに反する一切の憲法、法令及び詔勅を排除する。
日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであって、平和を愛する諸国民の公正と信義を信頼して、われらの安全と生存を保持しようと決意した。われらは平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めている国際社会において、名誉ある地位を占めたいと思う。われらは全世界の国民が、ひとしく恐怖と欠乏から免れ、平和の内に生存する権利を有することを確認する。
われらは、いずれの国家も、自国のことのみに専念して他国を無視してはならないのであって、政治道徳の法則は、普遍的なものであり、この法則に従うことは、自国の主権を維持し、他国と対等関係に立とうとする各国の責務であると信ずる。
日本国民は、国家の名誉にかけて、全力をあげて崇高な理想と目的を達成することを誓う。
"""
cloud_text = '' #出力用
font_path = '/content/drive/MyDrive/tmp/ipaexm.ttf'
output_path = '/content/drive/MyDrive/tmp/constitution.png'

txt = txt.replace('\n', '')
t = Tokenizer()
filters = [CompoundNounFilter(), POSKeepFilter('名詞'), POSStopFilter(['非自立','代名詞'])]
NG = ['非自立', '代名詞'] 
analyzer = Analyzer(tokenizer=t, token_filters=filters)

for token in analyzer.analyze(txt):
  #POSStopFilterがうまく機能しない部分があるので二重にフィルタリングする
  ex = token.extra[0].split(',')
  if all(x not in ex for x in NG):
  	#スペース区切りで単語を連結
    cloud_text += ' '+ token.surface

wc = WordCloud(font_path = font_path, width=1980, height=1080)
wc.generate(text=cloud_text)
wc.to_file(output_path)

日本国憲法前文で作成したワードクラウド
日本語ワードクラウド生成例


ひとまずこれで日本語のワードクラウドが作成できるようになったが、専門用語や流行語など、janomeのビルドイン辞書に記載されていない単語では上手くいかない。従って、辞書を生成していく必要があるが、それはまた書くことににする。




2022年10月6日木曜日

JavaScriptのPromiseを理解する

 JavaScriptの非同期処理がよくわからない、PromiseってなんぞってなったのでPromiseについてまとめてみた。

1. Promiseは何のため?

A. Promiseは非同期処理のため
これが簡潔な答えになる。
JavaScriptでは、あることをしながら別のことをするということができない。つまり、ウェブサイトのデータをダウンロードしながら画面にスピナーを表示するといったことができない。
これを解決するのが非同期処理になる。JavaScriptでは、Callbackやイベント駆動という方法がある。その新しい方法がPromiseになる。

2. Promiseのコードはどう使うか?

JavaScriptでは.then()という形で呼び出すことになる。

//Fetch APIを使う場合
fetch(url)	//urlにアクセスして
 	.then(response => response.json()) //responseを解釈し
	.then(data => console.log(data));	//データを表示する
(https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetchから引用)

このケースではFetch APIを取得し、対象のURLから情報を受け取り、JSONとして解釈して、そのデータを表示するということを行っている。
言い換えれば、.then()内に与えられる関数(ここではアロー関数)を呼び出して、fetch()の戻り値→response.json()の戻り値を順に与えて処理を行っている。


3. Promiseはどのような仕組みで動くか?

Promiseは、ある関数に一時的にPromiseというものを返し、処理を続行する。このPromiseが満たされるか、失敗するか、解決することでその関数は、処理を解決する。
Promiseを返す関数には2つのコールバック関数を登録することができる。1番目のコールバック関数が呼び出されたときにPromiseは満たされ、2番目のコールバック関数が呼び出されたときにPromiseは失敗する。
つまり、以下のようなコードで2つの関数を登録する。
// funcAは処理を実行、funcBはエラーを処理
fetch(url).then(funcA, funcB) 
//または
fetch(url)
	.then(funcA)
	.catch(funcB)

そして、解決するということは、いわばPromiseを返す関数がPromiseを受け取り、処理がペンディングされている状態になる。そして、あるPromiseがPromise以外の値で満たされたとき、連鎖してPromiseが満たされ、あるPromiseが失敗すれば、同じ理由で連鎖してPromiseが失敗する。
つまり、コードで表すと以下のようになる。
//HTTPリクエストを行い、最初にPromiseを返し、
//リクエストが返ってきたら、Promiseはresponseオブジェクトで満たされる
fetch(url)
//HTTPリクエストのPromiseが満たされるまで
//fetch()から返ってきたPromiseで解決される
//HTTPリクエストのPromiseが満たされると
//response.jsonが返すPromiseは満たされる
    .then(response => response.json())
//response.json()の受け取ったPromiseが満たされるまで
//最初のthen()が返すPromiseで解決される
    .then(data => displayData(data));

4. Promiseの動きを見るためのコード

Promiseは、組み込みのPromiseを返す関数以外にPromiseコンストラクタを使うことでPromiseを返す関数を作成することができる。
これを利用してミリ秒を引数として受け取り、その値を返す関数を書くと次のようになる。
//durationを引数として受け取り
//durationミリ秒後にPromiseをduratoinで満たす
function wait(duration){
    return new Promise((resolve, reject)=>{
        setTimeout(() => {resolve(duration)}, duration);
    })
}


//start→4000→setTimeoutの順でコンソールに表示される 
console.log("Start");
setTimeout(() => {console.log("setTimeout")}, 5000);
wait(4000).then(time => {console.log(time)});

//start→setTimeout→4000の順でコンソールに表示される 
console.log("Start");
setTimeout(() => {console.log("setTimeout")}, 3000);
wait(4000).then(time => {console.log(time)});

5.まとめ

Promiseは仮の値を関数に与えることで処理したことにして(処理したとみなし)、次の処理に進む。そのうえで、最初のPromiseが満たされたとき、連鎖的にPromiseが満たされて結果が返ってくる。

2022年9月28日水曜日

Pythonでヒストグラムを生成する【3/3】

 前回



前回はデータの分類をやったので今回はグラフの作成


3.グラフの作成



こういったグラフにするためには、「階級数」だけリストを作成し、それらの要素数は「最大の階級の値」する。そのうえで、各リストを最後尾から「■」で埋めて、最後に縦軸を合成すればいい。つまり、階級数が10、最大の階級の値が10であれば、10個の空白の要素を持つ10個のリストを用意すればよい。
#sep_listは階級(横軸)
#separatedは元データを階級別に分けたもの

longest = 0
for i in separated:
    if len(i) > longest:
        longest = len(i)
y = longest + 1 
x = len(sep_list)
graph = [[' ']*x for _ in range(y)]
sep_list = list(map(lambda x: (map(lambda y: str(y),x)), sep_list))
 	#sep_listは[int, int]の形なのでstrに変換
graph[y-1] = list(map(lambda x:'~'.join(x), sep_list))
	#グラフのリスト末尾に軸を追加
for i in range(x):
    volume = len(separated[i])
    for k in range(volume,0,-1): #後ろから■で埋めてく
        graph[y-1-k][i] = "■"
    

最後にグラフの縦軸を作成する。
まず、グラフの縦軸の数字を含むリスト用意する。縦軸は、グラフを含むリストの一次元目の要素数~1までをとる。ここでは次のステップの都合上0まで含むリストを「要素数」、「要素数-1」... 「0」といった形で作成する。
その後、このリストをグラフリストの一次元目の要素(リスト)の先頭に入れる形で結合する。
最後にグラフリストの[0][0]を半角スペースに置換する。


#graphはグラフを格納しているリスト
num = [[i] for i in range(len(graph)-1,-1,-1)]
for i in range(len(graph)):
	graph[i] = num[i] + graph[i]
self.graph[len(graph)-1][0] = ' '
最後にこれまでのコードをまとめてクラス化するとこう
class histogram:
    """docstring-
        data_array -> ヒストグラムにしたいデータ(一次元リスト)
        sep -> ヒストグラムの階級    
        drawメソッドで実行
        output_to_csvメソッドでワーキングディレクトリにcsv書き出し
    """
    def __init__(self, data_array, sep):
        self.data = data_array
        self.s = min(data_array)
        self.e = max(data_array)
        self.sep = sep
        self.separated = []
        self.sep_list = []

    def draw(self):
        self.distribute_data()
        self.select_list()
        self.draw_a_histogram()
        self.draw_vertical_axis()

    def draw_a_histogram(self):
        longest = 0
        for i in self.separated:
            if len(i) > longest:
                longest = len(i)
        y = longest + 1
        x = len(self.sep_list)
        self.graph = [[' ']*x for _ in range(y)]
        self.sep_list = list(map(lambda x: (map(lambda y: str(y),x)), self.sep_list))
        self.graph[y-1] = list(map(lambda x:'~'.join(x), self.sep_list))
        for i in range(x):
            volume = len(self.separated[i])
            for k in range(volume,0,-1):
                self.graph[y-1-k][i] = "■"
        
    def distribute_data(self):
        n = (self.e - self.s+1)//self.sep
        if (self.e - self.s +1)%self.sep != 0:
            n += 1
        self.separated = [[] for _ in range(n)]
        for i in range(n):
            if i == 0:
                self.sep_list.append([self.s, self.s + self.sep -1])
            else:
                previous = self.sep_list[i-1][1]
                nxt = previous + self.sep
                if nxt >= self.e:
                    nxt = self.e
                if previous + 1 == nxt:
                    self.sep_list.append([previous+1])
                else:
                    self.sep_list.append([previous+1, nxt])

    def select_list(self):
        for i in self.data:
            counter = (i - self.s) // self.sep
            self.separated[counter].append(i)

    def draw_vertical_axis(self):
        num = [[i] for i in range(len(self.graph)-1,-1,-1)]
        for i in range(len(self.graph)):
            self.graph[i] = num[i] + self.graph[i]
        self.graph[len(self.graph)-1][0] = ' '
        
    def output_to_csv(self):
        import csv
        with open("./histogram.csv",encoding = "utf-8",newline='',mode="w") as f:
            writer = csv.writer(f)
            writer.writerows(self.graph)