20210726のGoに関する記事は3件です。

ブラウザをターミナル代わりにするrttyを作った

初めに 以前にブラウザからターミナルを操作したくて、周りの方に聞いたらSecure Shell Appを教えていただいたんですが、なぜか自分の環境ではうまく動作しませんでした。 他にも似たようなことができるツールはありましたが、メンテナンスがされていなかったりビルドもインストールできなかったりして使えなかったので、自作しました。 しくみ自体はイメージできていたので、そんなに難しくないだろうと思っていましたが、意外と罠があってそれを解決するのに時間がかかってしまいました。 rttyの概要と使い方 rttyはプロセスの入出力をwebsocketを通して、ブラウザでターミナルのような操作を可能にするCLIです。 簡単にいうとブラウザがターミナル代わりになる、という感じです。 +---------+ http +------+ stdin +------+ | browser | <==========> | rtty | <=======> | bash | +---------+ websocket +------+ stdout +------+ 普段、ぼくはVivaldiというブラウザを使用しています。このブラウザはタブタイリングという機能があり、複数のタブを1画面にまとめられます。次のスクショのようにコーディングしつつブラウジングするにはもってこいです。 使い方はシンプルでrtty run {実行したいコマンド} -vするだけです。詳細なオプションはREADMEを参照していただければと思います。 実装 もう少し詳細を説明するとrttyはPTYを使って、プロセス入出力を読み書きしています。 +---------+ http +------+ +------+ +-----+ stdin +------+ | browser | <==========> | rtty | <===> | ptmx | <===> | pts | <=======> | bash | +---------+ websocket +------+ +------+ +-----+ stdout +------+ PTYやptmxなどについてはこちらの記事がわかりやすいので、わからない方は一度そちらを読むと良いと思います。 簡単に言うと、rttyはターミナル(ptmxとpts)の入出力をwebsocketでブラウザとつないでいるというイメージです。 ptmxを作成する部分はgithub.com/creack/ptyというパッケージを使用しています。2行でptmx取れるので便利です。 // Create arbitrary command. c := exec.Command("bash") // Start the command with a pty. ptmx, err := pty.Start(c) あとはio.Copyを使って、入出力をよしなに処理しています。 // Make sure to close the pty at the end. defer func() { _ = ptmx.Close() _ = c.Process.Kill() _, _ = c.Process.Wait() }() // Best effort. go func() { _, _ = io.Copy(ptmx, ws) }() w := &wsConn{ conn: ws, } _, _ = io.Copy(w, ptmx) ブラウザ側はxterm.jsを使用しています。VSCodeのターミナル機能もこれを使って実装されています。 これだけでブラウザ側でいつもの黒い画面になり、入力を受け取りWebsocketに送信でき、受信したデータをDOMに書き込めます。エスケープシーケンスもすべてxterm.jsが処理して変換してくれるので、ほぼ何もしていないといって良いレベルです。 const terminal = new Terminal(option); const fitAddon = new FitAddon.FitAddon(); terminal.loadAddon(fitAddon); terminal.open(document.getElementById('terminal')) fitAddon.fit(); terminal.onData(data => { socket.send(data); }) socket.onmessage = (e) => { terminal.write(e.data); } 問題 こんな感じで処理自体はとてもシンプルですが、実はVimなどのTUIを使うとたまにメッセージをデコードできなくて、Websocketがcloseされてしまうという問題がありました。 メッセージを読むとUTF-8として正しくないデータなのでデコードできない、と言っています。 最初は検討もつかなかったんですが、デバッグしていくうちにio.Copyの部分の実装が良くないことに気付きました。 先程載せたコードは修正版ですが、初期の実装は以下になっていました。 _, _ = io.Copy(ws, ptmx) これだと、プロセスの出力がio.Copyのバッファサイズを越えた場合、データが途中Websocketに送られてしまいます。それによって、UTF-8として正しくないテキストデータが送られてしまい、ブラウザ側でデコード失敗してしまいます。 簡単にいうと、あは切断されるとinvalidなUTF-8になります。このようなことが発生していました。 a := []byte{0xe3, 0x81, 0x82} // あ utf8.Valid(a[:2]) // false そのため、読み取ったデータがUTF-8としてinvalidならバッファリングして、バッファの中身がvalidならWebsocketに送るという処理が必要になります。 rttyはx/net/websocketを使用していますが、残念ながらWriteはそれを考慮した実装になっていないため、今回の問題が起きました。 対策としてwebsocket.Connとバッファを持った構造体を用意して、それにio.Writerを実装して問題を解決しました。 func (ws *wsConn) Write(b []byte) (i int, err error) { if !utf8.Valid(b) { buflen := len(ws.buf) blen := len(b) ws.buf = append(ws.buf, b...)[:buflen+blen] if utf8.Valid(ws.buf) { _, e := ws.conn.Write(ws.buf) ws.buf = ws.buf[:0] return blen, e } return blen, nil } if len(ws.buf) > 0 { n, err := ws.conn.Write(ws.buf) ws.buf = ws.buf[:0] if err != nil { return n, err } } n, e := ws.conn.Write(b) return n, e } 最後に ひとまず最低限使えるレベルになってきたので、使いながら改善していこうと思っています。 ちなみに、この記事はrttyを使ってプレビューしながら書きました。便利やなって思いました。 みなさんも良ければ使ってみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語を簡単に勉強して思う、言語設計の違いについて

GOを簡単に勉強して思う、言語設計の違いについて はじめに この記事は、かる〜〜くGOに触り始めたpython使いが思ったことを書いた記事です。 歴戦のGO使いの方々がご覧になられ「それ、お前が知らないだけだわ」と思った場合、コメントいただけますと大変助かります(できれば、石と匙は投げないでね) Goを触る動機 CとPythonに対するほんの小さな不満 CとPythonは言語として使える(≠使いこなしている)状態の僕ですが、以下の様なほんの小さな不満がありました。 C: 手が遅い僕からすると、開発工数がちょっと重い Python: さっとかけるけど、他の人・環境へのリリースしにくい ①バイナリ形式でリリースしたい(ソースコード出したくない) ②リリース後の環境影響を受けたくない Pythonの方だけ補足すると、①②ともにPyinstallerで一応は解決出来ます。 が、Pyinstaller はバイナリからソースコードへの逆変換自体が出来てしまいます。厳密にいうと、①で本当にやりたい「ソースコードを隠したい」という要望は満たしていません。 Goを触ってみた動機 Goは、言語レベル?でマルチOSへのクロスコンパイルが対応されており、いろいろな環境へのリリースが簡単そう!!という理由です。 (他にもたくさん良いところあると思いますけど、個人的には一番ここに惹かれました!) Goに触ってみた感想 主にPythonやCのライトユーザー視点での感想です。 1. unused系はエラーになる unused.go package main import "fmt" func main(){ test := 1 fmt.Println("hello world") } result(unused.go) ./unused.go:5:2: test declared but not used 端的に書くと、これ 変数の他にもimportしたPackageにしろ、変数の戻り値にしても使っていないと怒られます。(PythonなどではPEP8などで Warningで怒られるレベル) この仕様があると、動作確認前に強制的にレファクタリングが実施され、コード品質は高まります。が、書き捨てるような暫定コードに対しては「ちょっと今テスト中なんだよ……あとに回させろよ……」と思うこともしばしば (おそらく、このコーディング作業自体に問題があると考えた言語設計なのだと思いますが、余計なお世話感は若干あるんですよね^^;) 2. 標準関数の守備範囲が少ない list_match.py manager = ["Alice", "Bob"] if "Bob" in manager: print("Bob is Manager") Pythonでは、List内部に特定の値が含まれているか探す関数は標準関数であり、1行です。それに対して、Goに上記のような標準関数はありません。自作する必要性が出てきます。 その他にリスト内部の最大値を出す、重複削除など、、、 調べると、皆さん困っている様で、いろいろな場所で議論され、様々な関数例は出てきます。が、これだとこういう一般的な関数自体がメンテ対象になってしまいますので、個人的にはしっかりとしたpackageを見つけて置きたいなぁと思いました。 3. Classという概念 GOにもオブジェクトに相当するある様ですが、他のオブジェクト指向言語とはだいぶ概念が違うようです。最たる例としては、継承という概念がないらしいです(継承自体がシステムににおける問題になる可能性を秘めており、その問題を避けるための言語設計らしい) 人によっては、アレルギー症状を発生させる気もしますね。個人的には、Classの中に関数を書いていきたいと思っていたので、構造体の中に関数宣言できないのが若干ムズムズします。 可読性を保つためには、1ファイルの中に1構造体と周辺関数を定義する様なコーディングが強制的に求められている感じがします。 この違和感は悪いことなのか?? 3点出しましたが、GOが悪いと思っているわけではなく、私が悪いのだと考えます。 例えばですが、「unusedあとで消すから」は絶対消しません。GitにCommitしたあとでCI/CDかなにかで指摘が出て「やべ、忘れてた」とかっこ悪い修正Commit飛ばすことになります。それがGOではテスト実行時に前倒しで発見されるので、未然に防がれます。 GOという言語は、私のような「暫定大好き」で「設計をカッチリしない」人間が使っても最低限のコード品質保証が出来るように、一見すると面倒ともとれる安全装置的な言語設計をしているのだと思います。 まとめ 残念ながら、現段階でGOを手持ち言語の最有力とする気にはなれませんでした。気軽に書けるという部分でやはりPythonに魅力を感じてしまいます。 ですが、GO修行は継続しつつ、「GOであったとしても大丈夫なコーディング癖」をつけておこうかなぁと思います。GOの言語思想自体は数十年に渡ってエンジニアの先達がハマってきたコード品質の改良についてのヒントをくれている素晴らしいものと感じました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Regular Contest 124 解説

感想 50分遅れて参加したら、WAが出るわ焦るわで散々な目に遭いました。南無三。 下記ではC問題までを解説しています。D問題以降は解くつもりがありません。 A問題 (以下では、簡略化のために「最も左」の条件があるマスをマスL、「最も右」の条件があるマスをマスR、とします。) マスの間に依存関係はないので、それぞれのマスでありうる数の個数をかけ算すれば答え(ansを998244353で割った余り)が求まります。 まず、マスLまたはマスRとなるマスを考えます。この時、複数の条件が被っているマスは、どの数をそのマスに選んでもいずれかの条件を満たさないので0通りです。よって、それぞれのマスに高々一つの条件がある場合を以降では考えます。 まず、条件のあるマスは自明に1通りなので、ansに1をかけます。次に、条件のないマスがそれぞれ何通りの数を選べるかを考えます。この時、問題の条件より、「そのマスよりも左側にマスLがある」or「そのマスよりも右側にマスRがある」のいずれかを満たす数を選ぶことができます。 ここで、左右が条件に関わるので、左側から順にマスの数をそれぞれ決めます(数え上げの方向を左右のどちらかに統一したい)。この元で、選べる数をnowに保存します。nowの初期化の際には、マスRの個数を格納します。そして、マスLが出てきた場合は選べる数が増えるのでnow++、マスRが出てきた場合は選べる数が減るのでnow--、マスLとマスRのいずれでもない場合はnowをansにかけます。 以上の操作を行ない、nowをansにかける際に998244353で割るようにすることで答えは求まります。 mod=998244353 n,k=map(int,input().split()) ok=set() # rかl check=[0]*n now= 0 for i in range(k): c,_k=input().split() _k=int(_k) ok.add(_k) check[_k-1]=1 if c=="R" else -1 if c=="R": now+=1 if len(ok)!=k: print(0) exit() ans=1 for i in range(n): if check[i]==1: now-=1 elif check[i]==-1: now+=1 else: ans*=now ans%=mod print(ans) B問題 実装を間違えてWAを出しました。実装自体は難しくないとは思います。 数列$b$を並び替えることができるので、$a_0$に固定して考えると$x$の値は$a_0 \oplus b_0$から$a_0 \oplus b_{n-1}$の高々$n$通りになります。$x$の値がわかっている時、XORの性質である「$a \oplus b=c \leftrightarrow a \oplus c=b$」(対称差と呼ぶらしい)を利用すれば、$a_i \oplus x=b^{'}_i$として$x$を良い数とする$b^{'}_i$を求めることができます。また、$b_i=b^{'}_i$が任意の$i$で成り立つときに$x$は良い数となるため、$b_i$,$b^{'}_i$をそれぞれ昇順ソートして一致するかを確かめます。 また、以上を行なって良い数$x$を昇順で列挙しますが、同じ数が複数含まれないように注意が必要です。Goにはsetがないので、WAを出しました。なぜPythonを使わなかったんでしょうか。 // 22:00~22:15 package main import ( "bufio" "fmt" "os" "sort" ) func main() { scin := bufio.NewReader(os.Stdin) prout := bufio.NewWriter(os.Stdout) defer prout.Flush() // code var n int fmt.Fscan(scin, &n) var a []int = make([]int, n) var b []int = make([]int, n) for i := 0; i < n; i++ { fmt.Fscan(scin, &a[i]) } for i := 0; i < n; i++ { fmt.Fscan(scin, &b[i]) } var y []int for i := 0; i < n; i++ { y = append(y, a[0]^b[i]) } sort.Slice(y, func(i, j int) bool { return y[i] < y[j] }) var ans []int for _, x := range y { var nowa []int = make([]int, n) var nowb []int = make([]int, n) copy(nowa, a) copy(nowb, b) for i := 0; i < n; i++ { nowa[i] ^= x } sort.Slice(nowa, func(i, j int) bool { return nowa[i] < nowa[j] }) sort.Slice(nowb, func(i, j int) bool { return nowb[i] < nowb[j] }) f := true for i := 0; i < n; i++ { if nowa[i] != nowb[i] { f = false break } } if f { if len(ans) == 0 { ans = append(ans, x) } else { if ans[len(ans)-1] != x { ans = append(ans, x) } } } } fmt.Fprintln(prout, len(ans)) if len(ans) != 0 { for i := 0; i < len(ans); i++ { fmt.Fprintln(prout, ans[i]) } } } C問題 コンテスト中は、初めの考察段階で「約数を利用しよう」と決めていたのに、YouTubeを見始めて帰ってきたら謎の愚直な嘘解法を生み出していました。もったいない。 本解と同様の方針が良いです。最大公約数はそこまで候補がないだろうという予測はつくので、$X$と$Y$の候補を列挙することをまずは考えます。また、最大公約数は数を選ぶにつれて広義単調減少するので、一番最大公約数が大きい状態である1つのみのカードパックを選んだ時を考えます(一番制約が緩い状態)。このとき、1つのカードパックである$a_0,b_0$において、$a_0$を赤袋に$b_0$を青袋に入れても一般性を失わないため、この仮定の元で考えます。 まず、$X$についてですが、赤袋に入れた任意の数の最大公約数であることから、先ほどの仮定により$a_0$の約数であることが必要条件です。また、$Y$についても$b_0$の約数であることが必要条件です。つまり、$X$と$Y$の組の候補を二重ループにより列挙することが可能です。それぞれのループにおいて、「$a_i$が$X$で割り切れ、$b_i$が$Y$で割り切れる」or「$a_i$が$Y$で割り切れ、$b_i$が$X$で割り切れる」、を任意の$i$で成り立つかを試せば、その$X$と$Y$の組が存在しうるかを確かめられます。この元で$X,Y$の最小公倍数を求めていき、その中での最大値を答えとして求めます。 下記では、こちらの記事の約数列挙のコードを使用しました。 from math import gcd def lcm(x,y): return x*y//gcd(x,y) def make_divisors(n): divisors=[] for i in range(1,int(n**0.5)+1): if n%i==0: divisors.append(i) if i!=n//i: divisors.append(n//i) #約数の小さい順にソートしたい場合 #divisors.sort() #約数の大きい順にソートしたい場合 #divisors.sort(reverse=True) return divisors n=int(input()) ab=[list(map(int,input().split())) for i in range(n)] ans=0 for i in make_divisors(ab[0][0]): for j in make_divisors(ab[0][1]): for k in range(1,n): if (ab[k][0]%i==0 and ab[k][1]%j==0)or(ab[k][0]%j==0 and ab[k][1]%i==0): pass else: break else: ans=max(ans,lcm(i,j)) print(ans)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む