Escape from the Memories Planet

思い出星職人をめざし、かつ、とどまらないブログ

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テトリスでも置いておいてお茶を濁します...(記事とは全く無関係)

https://tetris-by-rust-wasm.firebaseapp.com/

C言語でPython風input関数

C言語scanf関数は、競技プログラミングなど入力が正確な場合は非常に便利ですが、人間による入力が絡む場合や、違った型で複数回の入力をする時にはなるべく避けたいものです。

というわけで、Pythonに存在する便利な関数inputC言語で再現してみました。

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.hinput.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関数を使わずに楽に標準入力を受け取れますね!

この記事が参考になれば幸いです。ではまたいつか


  1. 変数名に関して、ヘッダーファイルにてグローバル変数として宣言しているわけではないので、本当は_(アンダーバー)とかつける意味はないのですが、気分です。気分。突っ込んじゃダメ。

  2. ちなみにPythonでもprint(f"{sum:2} += ")と指定すれば同じように表示できますのでご安心?ください。

自作行列簡約化ツール

この記事はUEC Advent Calendar 2018の12日目の記事です!

id:puman03君とともにNimを布教しようとたくらむnamniumと申します。m(_ _)m

今回は勉学に役立つものを書こう(作ろう)と思い、行列簡約化ツールを作成しました!

↓こちらから利用できます!

行列 簡約化

↓全体のソースコードはこちら

anotherhollow1125/kanyaku_matrix

コンセプト

すでにネット上にはいくつか転がっていたので、少しこだわりを持って作成いたしました。

  • enmtパパ曰く「除算の結果、0に近い数なのか0なのかの判定が大変となるため、簡約化するツールを作成するのは難しい」とのこと
  • ↑ 基礎プヨ第9回の「有理数クラス」のアイデアを拝借して、分数で値を保存した

  • なるべく表示を大きくしスマホでも扱えるようにした

  • 車輪の再開発にあたるので、自分が現在勉強している言語であるNimでコードを作成した
  • NimをJavaScriptにトランスパイルし、HTMLに埋め込むことでみんなに使ってもらえるようにした

技術解説・ソースコード

ちょっとずつNimの布教を兼ねて技術的説明をしていこうと思います!何かの役に立てば幸いですw

分数で数値を保持する

ソースコードの頭では必要なモジュール(strutilssequtils)と(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の面白い機能がこちら。RubyPythonにもありますが、演算子を自前で設定できます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

ijを現在操作中の基本的な行と列の位置を記憶しておく変数とし、変数kで行を変えながら簡約化を行う、という形になっています。

Nimでは返り値を表す特別な変数resultを使うことで、returnを省略できたりします。繰り返しと組み合わせて使うとすっきりしてとてもうれしいものです。

またRubyPythonでもできるスワップ(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


  1. 要出典。テンプレートの機能など僕も使いこなせていないNimの機能はまだまだたくさんあるので、この流れが正しいと断言はできないです。

  2. 演算子には様々な記号と組み合わせが使えるので、やろうと思えば面白い記述を作成することも可能です。

  3. 一部JSを利用しております。

  4. 後から知ったのですが、このシンタックスシュガーを UFCS (Unified Function Call Syntax) というらしいです。

Visual Studioのcl.exeでつぼった話+α

お初にお目にかかります。namniumと申します。

普段はteratailで回答したりqiitaで記事書いたりしていますが、はてブロには記事にならないレベルのことを記事にしていこうと思っています。。。って書くと矛盾していますね苦笑、備忘録群だと思っていただけると幸いです。

長くなってしまいましたが、本題の前置きに入ろうと思います。(本題ではないというw) 初回1Visual 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++のツールをインストール。

解決

改めてスタートメニューで検索をかけると、、、あった!、ラピュタは本当にあったんだ!!

f:id:namnium1125:20180512214446p:plain

僕の場合はなんとかこれで解決できました。ε-(‘∀ ` ;)

結論 : 早とちりせず、インストールが必要なものは確実に入れましょう。

おまけ+α

実行結果

ということで、さっそくコンパイルしてみました。実行結果は以下に示す通りです。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)ですね。自分にとってわかりやすい場所ならばどこでもいいでしょう。

ここまで読んでいただきありがとうございます。またいつかお会いしましょう

ではでは~(^_^)ノシ


  1. 初回からなんかネガティブな内容ですみません苦笑、、まぁ誰かの役に立てばという思いです。

  2. UnityのエディターにはほかにMonoDevelopというのもあるそうで、Unityではこっちが使いやすいなんて話もありますが、とりあえずVSを使ってみて、使いにくかったらMonoDevelopに替えるでいいかなと今は考えています。

  3. \機種依存文字¥に置き換えています。そのためコピペして実行してもうまくいきません。ご注意ください。