Rust標準入力マクロ置いとく
めちゃくちゃ投稿に間が空いてしまった、、、まぁQiitaの記事にするほどでもない内容を置くという方針なのでこっちの方にめったに投稿がないのは別に問題ないはず...ないはず
namniumと申します。はい。今回は競プロでRustのコードを書いていたら、標準入力用のマクロを副産物で作成してしまったので、ここに置いておこうと考えた次第です。
ガバコードなので「こういうのもあるんだ」程度にお願いします。
macro_rules! scan { ( $( $x:ident ),*; $t:ty ) => { let mut buf = String::new(); let mut itr = { while buf.trim().len() == 0 { buf = String::new(); std::io::stdin().read_line(&mut buf).unwrap(); } buf.split_whitespace().map(|n| n.parse::<$t>().unwrap()) }; $( let $x = itr.next().unwrap(); )* }; ( $( $x:ident ),*; String ) => { let mut buf = String::new(); let mut itr = { while buf.trim().len() == 0 { buf = String::new(); std::io::stdin().read_line(&mut buf).unwrap(); } buf.split_whitespace() }; $( let $x = String::from(itr.next().unwrap()); )* }; } use std::io::prelude::*; fn print(message: &str) { print!("{}", message); std::io::stdout().flush().unwrap(); } fn main() { print("a b? "); scan!(a, b; i32); println!("{} + {} = {}", a, b, a+b); print("What's your name(fisrtname familyname) ? "); scan!(first, family; String); println!("Your name is {} {} in Japan.", family, first); }
実行結果例
$ ./main a b? 10 20 10 + 20 = 30 What's your name(fisrtname familyname) ? Taro Yamada Your name is Yamada Taro in Japan.
あー、解説とか面倒くさいのでしません。この前作ったRustテトリスでも置いておいてお茶を濁します...(記事とは全く無関係)
C言語でPython風input関数
C言語のscanf
関数は、競技プログラミングなど入力が正確な場合は非常に便利ですが、人間による入力が絡む場合や、違った型で複数回の入力をする時にはなるべく避けたいものです。
というわけで、Pythonに存在する便利な関数input
をC言語で再現してみました。
Pythonのinput関数
Pythonにあるinput
関数は以下のように使います。
a = int(input("a : ")) b = int(input("b : ")) print(f"{a} + {b} = {a+b}")
実行例
a : 5 b : 10 5 + 10 = 15
input
関数は受け取った引数をプロンプトとして表示し、改行までの入力を受け取って文字列として返すというものです。
上記のプログラムでは受け取った文字列をint
型にキャストして計算し、表示しています。
C言語で真似てみる
C言語でも、プロンプト内容(ディスクリプションとし、引数名disc
としています。)を受け取り、標準入力で受け取った文字列のポインタを返す関数として実装すればいいわけです。
input.c
#include <stdio.h> #include <string.h> #include <stdarg.h> #ifndef _MEM_ #define _MEM_ 256 #endif char _input_str[_MEM_][_MEM_]; int _input_str_index = 0; char* input(char *disc, ...) { if (_input_str_index == _MEM_) { printf("!!coution!! : buffers are lacked!\n"); // return NULL; _input_str_index = 0; // 古いものから上書き } va_list args; va_start(args, disc); vprintf(disc, args); va_end(args); fgets(_input_str[_input_str_index], _MEM_, stdin); // '\n'削除 int len = strlen(_input_str[_input_str_index]); if (_input_str[_input_str_index][len-1] == '\n') { _input_str[_input_str_index][len-1] = '\0'; } return _input_str[_input_str_index++]; }
予め、_MEM_
個分、_MEM_
文字配列を二次配列_input_str
として確保しています。1
本当はmalloc
関数で逐次メモリを確保するようにしたかったのですが、free
関数を外側で呼ぶ必要があるのは嫌だったので、予め確保するというスタイルを取ってみました。
_input_str_index
によって入力された文字列の連番を管理しています。_MEM_
個以上の入力があった場合の例外処理ですが、とりあえず思いつかなかったので警告を出したうえで上書きする仕様としました。
肝心の標準入力はfgets
関数で行っています。fgets
関数は第一引数に保存用の文字列のポインタ、第二引数に確保されている文字列長(要は最大文字数)、第三引数に読み込むファイルポインタ(今回は標準入力なのでstdin
)を指定する関数です。
scanf
と異なり、最大文字数以上は切り捨てられるのでバッファオーバーランの危険性がありませんし、改行まできっちり受け取るのでストリーム上(標準入力上)に変なものが残らず安全です。
逆に前述の通り改行まで受け取ってしまっているので、その後ろの部分にて改行は削除しています。その後、受け取った文字列が入ってるポインタを返り値として返しています。
stdarg.h
周りのva_list
などは、printf
関数のようにフォーマット機能を使用するために使っています。stdarg.h
を読み込むと、引数に...
と指定することで可変長引数を受け取ることができるようになります。その後、vprintf
関数を使用することで、disc
文字列にフォーマット機能で可変長引数を代入して表示できるようにしました。この辺りは蛇足的機能なので理解できなくても大丈夫です。(僕もよくわかってない...(^ ^; )
さらに、input.c
を読み込むためにinput.h
も用意しておきます。
#ifndef _INCLUDE_input_h_ #define _INCLUDE_input_h_ extern char* input(char *disc, ...); #endif
input.c
を使用するときは、例えばmain
関数が入ったソースコードと同じ階層にinput.h
、input.c
を置き、$ gcc test.c input.c
としてコンパイルしましょう。
使用例
例えば最初のPythonプログラムはC言語で以下のように書けます。
#include <stdio.h> #include <stdlib.h> #include "input.h" int main(void) { int a = atoi(input("a : ")), b = atoi(input("b : ")); printf("%d + %d = %d\n", a, b, a+b); return 0; }
a : 5 b : 10 5 + 10 = 15
Pythonとほとんど変わらない書き方で同じ機能を実現できました!よきよき
さらにせっかく実装したフォーマット文字列も使用してみましょう。
#include <stdio.h> #include <stdlib.h> #include "input.h" int main(void) { int sum = 0, tmp = 1; while (tmp != 0) { tmp = atoi(input("%2d += ", sum)); sum += tmp; } return 0; }
実行例
0 += 1 1 += 2 3 += 3 6 += 4 10 += 5 15 += 6 21 += 7 28 += 8 36 += 9 45 += 10 55 += 0
フォーマット文字列機能によりprintf
関数みたいにプロンプト内容を自由に指定することができます。2
これで忌々しいscanf
関数を使わずに楽に標準入力を受け取れますね!
この記事が参考になれば幸いです。ではまたいつか
自作行列簡約化ツール
この記事はUEC Advent Calendar 2018の12日目の記事です!
id:puman03君とともにNimを布教しようとたくらむnamniumと申します。m(_ _)m
今回は勉学に役立つものを書こう(作ろう)と思い、行列簡約化ツールを作成しました!
↓こちらから利用できます!
↓全体のソースコードはこちら
anotherhollow1125/kanyaku_matrix
コンセプト
すでにネット上にはいくつか転がっていたので、少しこだわりを持って作成いたしました。
- enmtパパ曰く「除算の結果、0に近い数なのか0なのかの判定が大変となるため、簡約化するツールを作成するのは難しい」とのこと
なるべく表示を大きくしスマホでも扱えるようにした
- 車輪の再開発にあたるので、自分が現在勉強している言語であるNimでコードを作成した
- NimをJavaScriptにトランスパイルし、HTMLに埋め込むことでみんなに使ってもらえるようにした
技術解説・ソースコード
ちょっとずつNimの布教を兼ねて技術的説明をしていこうと思います!何かの役に立てば幸いですw
分数で数値を保持する
ソースコードの頭では必要なモジュール(strutils
とsequtils
)と型
(type
)を宣言しています。Nimにはオブジェクト指向の概念がなくクラスにメソッドを記述していくという考えはなく(OOP自体の考え方はあります)、型
だけが存在します!
型
にあった関数を定義する、またはオーバーライドしていく、というのがNimの基本的な流れとなります1。
大規模になってくるとつらいところもありますが、記述は確かにOOPより楽になるところが多いようにも感じます。
何より型があるのって結構いいですね、、コンパイル時に記述ミスが見つかるのはPythonista的には結構うれしかったりします。 C言語よりお手軽に扱えるコンパイル言語としてNimはうってつけです!
import strutils, sequtils type Ratio = ref object nume : int # 分子 deno : int # 分母 Matrix = seq[seq[Ratio]]
C言語で言う構造体として定義したRatio
(分数)型はnume
(Numerator, 分子)、deno
(denominator, 分母)の二つを持っています。またMatrix
型をここでRatio
型の要素を持つ二次元配列として定義しております。
proc gcd(a, b: int): int = var x = a y = b while true: if x > y: x = x mod y if x == 0: return y else: y = y mod x if y == 0: return x proc red(p: Ratio): Ratio = var n = p.nume d = p.deno g = gcd(n.abs, d.abs) return Ratio(nume: int(n/g), deno: int(d/g))
お次の上記のコードでは、分数を約分させています。約分の方法は基礎プヨの丸パクリですw 数弱なので説明は省きます()
Nimの残念なところともいえるのはmod演算に%
を使用できないことなんですよね...実装することもできそうですがとりあえずデフォルトのmod
を使用しています
Nimは静的型付け言語なのですが、上記の例を見ればわかるように、初期化時に値を指定すると型を省略できます!ですが途中で型を変更することはできません。
proc `$`(p: Ratio): string = if p.deno != 1: return "\"" & $p.nume & "/" & $p.deno & "\"" else: return "\"" & $p.nume & "\""
お次のNimの面白い機能がこちら。RubyやPythonにもありますが、演算子を自前で設定できます2!
Nimでは$
演算子はto string
という意味をもつという慣例がありますので、それに従ってここでRatio
型に対して関数を設定しております。ですからオーバーライドに当たる部分ともいえます。
proc ratioNew(a, b: int): Ratio = var n = a d = b # if d == 0: raise newException(ValueError, "0 division") if d == 0: return Ratio(nume: 1, deno: 0) if n == 0: return Ratio(nume: 0, deno: 1) if d < 0: n = -n d = -d result = Ratio(nume: n, deno: d) result = result.red proc `+`(p: Ratio, q: Ratio): Ratio = return ratioNew(p.nume*q.deno+p.deno*q.nume, p.deno*q.deno) proc `-`(p: Ratio, q: Ratio): Ratio = return ratioNew(p.nume*q.deno-p.deno*q.nume, p.deno*q.deno) proc `*`(p: Ratio, q: Ratio): Ratio = return ratioNew(p.nume*q.nume, p.deno*q.deno) proc `/`(p: Ratio, q: Ratio): Ratio = return ratioNew(p.nume*q.deno, p.deno*q.nume) proc parseRatio(s: string): Ratio = var sp = s.split("/") n, d = 1 n = sp[0].parseInt if sp.len == 2: d = sp[1].parseInt return ratioNew(n, d)
Ratio
型を初期化する関数とその四則演算、キャスト用関数になります。ここでも演算子を定義していますね。内容はほぼ基礎プヨのパクリですが()
整数のときには分母を1
にする、といった点をここで工夫を施しています
行列の簡約化
ようやく?行列の簡約化のコードです。文字列を行列に変換する関数と、そのあとに簡約化関数echelonize
を定義しています
proc parseMatrix(mat_s: seq[seq[cstring]]): Matrix = result = @[] for row_s in mat_s: var row: seq[Ratio] = @[] for elm in row_s: row.add(($elm).parseRatio) result.add(row) # 肝心の簡約化はここから proc echelonize(mat: Matrix): Matrix = result = mat let row_num = len(result) col_num = len(result[0]) var i, j = 0 while j < col_num and i < row_num: for k in i..<row_num: var a = result[k][j] if a.nume == 0: if k < row_num-1: continue else: j += 1 # 列カウンタのみ進める break result[k] = result[k].map(proc(r: Ratio): Ratio = r / a) for p in 0..<row_num: if p != k: var t = result[p][j] for q in j..<col_num: result[p][q] = result[p][q] - result[k][q] * t (result[i], result[k]) = (result[k], result[i]) i += 1 j += 1 break
i
、j
を現在操作中の基本的な行と列の位置を記憶しておく変数とし、変数k
で行を変えながら簡約化を行う、という形になっています。
Nimでは返り値を表す特別な変数result
を使うことで、return
を省略できたりします。繰り返しと組み合わせて使うとすっきりしてとてもうれしいものです。
またRubyやPythonでもできるスワップを(result[i], result[k]) = (result[k], result[i])
という形で行っております。tmp
なんていうゴミ変数を用意しなくて済むのはやっぱり重要です
proc main(mat_s: seq[seq[cstring]]): cstring {. exportc .} = var mat = mat_s.parseMatrix res = mat.echelonize return ($res).replace("@", "")
最後にHTML側3から実行する、文字列の二次配列を引数に取り、最後に結果を文字列として返す(返した文字列はJS側にてeval
で変換)関数main
を定義しております。
一見mat.echelonize
のような記述はオブジェクト指向に見えますがこれはシンタックスシュガー4で、実際はechelonize(mat)
を実行しています。まさに先ほど定義した関数です
この場面ではあまりこのシンタックスシュガーは役立ちそうにないですが、文字列などに対して"string".replace("s", "t")
みたいに書けるというのはやっぱありがたいですね。。
実はPythonのインスタンスメソッドはこのシンタックスシュガーに近い構造を持っていたりします。証拠に、以下のコードは等価です。
Hoge.func(hoge, val) hoge.func(val)
話がNimからそれてしまいましたが、要はこのシンタックスシュガーが可読性に結構効いてきているということです。う~ん、、深い!
終わりに
とても読みにくい文章で、さらに公開も遅くなって申し訳ありませんでした(^ ^;
記事は徹夜明けの脳みそで書いていて、ツール自体も徹夜で作成したので、ビシバシとバグを見つけて僕まで報告くださるととても喜びます() 実際に役立ててくれればもっと喜びます!
まだまだNimにはいっぱい面白い(時には頭にくるような)発見がありましたが、とりあえず今回はこれでしめたいと思います!
Nimに限らず様々な言語に触れてみるってやっぱり重要ですね、、Rustとかもいつかやってみたいなと思います。
明日は Cambaroides_JPさんの「通信制高校から大学進学を目指す人に向けた何か」だそうです。
電通大も通信制になってくれたら実家でぬくぬくしながら勉学に励めるんですけどねw まぁサークルとか楽しいので今のままで良いですが()
ここまで読んでいただきありがとうございました!m(_ _)m
Visual Studioのcl.exeでつぼった話+α
お初にお目にかかります。namniumと申します。
普段はteratailで回答したりqiitaで記事書いたりしていますが、はてブロには記事にならないレベルのことを記事にしていこうと思っています。。。って書くと矛盾していますね苦笑、備忘録群だと思っていただけると幸いです。
長くなってしまいましたが、本題の前置きに入ろうと思います。(本題ではないというw) 初回1はVisual Studioをインストールして、C言語をコンパイルするときにうまくいかなかった原因を探った話です。
前置き
いままでmacユーザーだったのですが、大学進学を機にWinマシンを買いまして、「WinといえばVisual Studioだろう」という安直な理由と、大学でEnterprise版を無償提供していたという理由より、IDEとしてVisual Studio Enterpriseを早速インストールしました。Enterpriseのバージョンは2017 15.7.1
でした。
また、macではエディタとしてAtomを使っていたので、VSCodeも別にインストールしました。。VSCodeだけで十分な気もしましたが、UnityがやりたいんでVSがあったっていいでしょう2。役割も違いますからね。
事件発生
とりあえずまずはHelloWorldかなと思い、C言語をコンパイルする段階で事件発生。google先生に聞いてたどり着いたこのページ曰く「開発者コマンド プロンプト for VS 20XX」みたいな"cl.exe"のパスが通ったVSのコマンドプロンプトを開いて~とあったのですが、、そもそも
ってなったんですね。これもう詰みじゃない?。。。(^ ^;
原因探索
心当たりはまったくなく、、"VsDevCmd.bat"をダブルクリックしたりするもすぐに閉じるし、、あれやこれや試すもうまくいかず、、、調べるとC++で似たような症状にあっている人に遭遇。っていうかこれ答えだ!
そう、単にC++のツールがまだインストールされていなかっただけだったみたいです。
試しにVisual C++のプロジェクトを作成しようとすると、「Visual C++ Tools 2017 うんたらこんたら インストール」なる項目が。。。。
う~ん、、なぜインストールされてなかったんですかね、、最初にインストールする項目を選べるんですが、容量を減らしたくていくつかチェックを外したのが原因でしょうか...?結局はっきりしないまま、無事C++のツールをインストール。
解決
改めてスタートメニューで検索をかけると、、、あった!、ラピュタは本当にあったんだ!!
僕の場合はなんとかこれで解決できました。ε-(‘∀ ` ;)
おまけ+α
実行結果
ということで、さっそくコンパイルしてみました。実行結果は以下に示す通りです。3
********************************************************************** ** Visual Studio 2017 Developer Command Prompt v15.7.1 ** Copyright (c) 2017 Microsoft Corporation ********************************************************************** C:¥Program Files (x86)¥Microsoft Visual Studio¥2017¥Enterprise>cd C:¥Users¥namni¥Desktop C:¥Users¥namni¥Desktop>cl hello.c Microsoft(R) C/C++ Optimizing Compiler Version 19.14.26428.1 for x86 Copyright (C) Microsoft Corporation. All rights reserved. hello.c Microsoft (R) Incremental Linker Version 14.14.26428.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:hello.exe hello.obj C:¥Users¥namni¥Desktop>hello hello,world! C:¥Users¥namni¥Desktop>
無事にhello,world!
を表示できました。個人的なものかもしれませんが、僕はデスクトップで実行するのが好きです。
立ち上げ時のディレクトリでは書き込みでpermission error
になってしまう可能性があるので、管理のしやすさも考えると、ディレクトリは変更しておいたほうがいいと思います。
デスクトップ以外のおすすめはドキュメント(Documents)ですね。自分にとってわかりやすい場所ならばどこでもいいでしょう。
ここまで読んでいただきありがとうございます。またいつかお会いしましょう
ではでは~(^_^)ノシ
-
初回からなんかネガティブな内容ですみません苦笑、、まぁ誰かの役に立てばという思いです。↩
-
UnityのエディターにはほかにMonoDevelopというのもあるそうで、Unityではこっちが使いやすいなんて話もありますが、とりあえずVSを使ってみて、使いにくかったらMonoDevelopに替えるでいいかなと今は考えています。↩