2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ペントミノ・ソルバーをいろんな言語で書いてみる

Last updated at Posted at 2024-04-29

概要

プログラム言語の文法はいろいろなので Hello World だけではその言語の特徴は把握できないですよね。
そこで、基本的な文法の理解&比較&備忘録として、ペントミノのソルバーを20種類のプログラミング言語で書いてみました。

どの言語も200~300行程度のコードなので全体を把握するのは難しくないと思います。

output.png Ruby Python Lua Squirrel Julia JavaScript TypeScript Groovy Perl AWK PHP C C++ C# Java Kotlin Go Rust Swift LISP Crystal Elixir F#

(2024/12/9: Crystal、Elixir を追加、実行速度グラフの修正)

比較のポイント

  • 関数の宣言方法:引数、返り値
  • 変数の宣言方法:型指定、ローカル、グローバル、mutable/immutable
  • 配列:作り方、操作メソッド、多次元
  • 文字列:操作メソッド、変数埋込、複数行文字列
  • 連想配列
  • NULL値: null, nil, nothing, ....
  • print文: 書式指定方法
  • コメント文
  • ブロックの書式: {} 、~ end
  • for, foreach, if の構文
  • 三項演算子
  • lambda、closure、アロー関数
  • クラス:メンバ変数、メソッド、コンストラクタ
  • コマンドライン引数
  • 実行時間

プログラム

ソースコード

https://github.com/sense-n-react/PentominoSolvers

プログラムの特徴としては、ひたすら配列アクセスや関数呼び出しを行うものです。これだけで言語の良し悪しを評価することはできませんが、素(す)の言語の使い勝手を感じることはできると思います。

プログラム全体の処理の流れ、構成(見た目)、クラス名、関数名、変数名はできるだけ同じにしています。オブジェクト指向的な作りを心掛けましたが、AWK、Perl、LISP はベタな書き方で妥協しています。

実行方法

ソルバープログラムは個別に実行できます。表示はキャラクターベースです。

普通に実行
$ ruby solver.rb
+---+---------------+---+
|   |               |   |
|   +-------+   +---+   |
|           |   |       |
+-------+   +---+   +---+
|       |   |       |   |
|       +---+-------+   |
|       |           |   |
+---+   +-------+   |   |
|   |   |       |   |   |
|   +---+---+   |   |   |
|           |   |   |   |
|   +---+---+   +---+   |
|   |   |       |   |   |
+---+   +---+---+   +---+
|           |   |       |
+---+   +---+   +---+   |
|   |   |           |   |
|   +---+-------+   |   |
|               |   |   |
+---------------+---+---+ 
                            2339

盤面のサイズ指定

デフォルトでは 6x10 の盤面で解を求めます。コマンドラインに 8x8 のように盤面のサイズを指定できます。指定可能なサイズは 3x20 4x15 5x12 6x10 10x6 12x5 14x5 20x3 8x8 4x16 16x4 のいずれかです。これ以外だと 6x10 として実行します。
8x84x16 では中央に 4x4 のスペースを配置して解を求めます。

8x8を指定して実行
$ ruby solver.rb 8x8
+---+-----------+---------------+
|   |           |               |
|   +-------+   |   +-----------+
|           |   |   |           |
+-------+   |   +---+   +-------+
|       |   |   |       |       |
+---+   +---+---+---+---+   +---+
|   |       |       |       |   |
|   +---+   |       +---+   |   |
|       |   |       |   |   |   |
|   +---+---+---+---+   +---+   |
|   |   |       |           |   |
|   |   |       +---+   +---+   |
|   |   |       |   |   |   |   |
+---+   +---+   |   +---+   |   |
|           |   |           |   |
+-----------+---+-----------+---+
                                    65

run-solver

言語が20種類もあると言語のコマンドをいちいち覚えてられない(特にコンパイル系)ので、run-solver というRubyスクリプトを作りました。
ファイルの拡張子を指定すれば対応する言語のプログラムを実行します。
コンパイル系言語ではコンパイルしてから実行します。

拡張子(言語)を指定して実行
$ ./run-solver rb                # ruby solver.rb を実行
$ ./run-solver rb js cpp         # 複数指定可
$ ./run-solver ruby              # 言語名でもOK
$ ./run-solver --all             # 全ての言語で実行
$ ./run-solver py --size 5x12    # 5x12 を python で実行

感想

以下は、各言語に対する私の感想です。理解不足や偏見がありますのでご注意ください。

  1. Ruby
    シンプルで一番好きです。慣れてることもあり「考えていることをそのまま書く」ことができます。特に、メソッドチェーンを使ってシンプルに書けるところが良いです。

  2. Python
    Rubyと同程度にシンプルに書けます。が、メソッドチェーンで不自由を感じました。実行速度は意外に遅いです。内包表記や3項演算子などが他の言語と比べて独特で取っ付きにくいです。インデントも賛否両論・・・。ユーザの多さや豊富なライブラリはメリットと言えますが、言語として好みではありません。

  3. JavaScript
    実行速度が非常に速いので驚きました。JITの効果を体感できます。言語仕様もいい感じで癖がないと思います。let と var の使い分けは少し億劫です。solver.js では使っていませんが、JavaScript の非同期処理の書き方は直感的でなくて好きではありません。

  4. TypeScript
    型チェック、、、頑張ってるなぁと思います。もちろんパフォーマンスは JavaScript と同じです。tsc 自体が JavaScript で書かれていることにも驚きです。
    オプション指定は tsconfig.json を使うのが作法のようですが、ファイル1個を動かすだけだしコマンドラインオプションで対応しました。結構試行錯誤しました。

  5. Julia
    着実に進歩してますね。数値計算に向いてるらしいのでオブジェクト指向でガンガン...というのではないようです。配列が [1] から始まるのが...

  6. Lua
    コンパクトなのにパフォーマンスが良くて、面白い言語です。 メンバ変数は '.' なのに メソッドは ':' とか、配列が [1] から始まるのが残念です。玄人向けだと思います。

  7. Squirrel
    Lua と同じくコンパクトで、C++ を知ってれば使えるという感じです。拡張子が nut なのがおしゃれです。
    グローバル変数の扱いに戸惑いました。ローカル変数につけないといけない local が目につきます。エラーメッセージが判りにくいのと REPLが使いにくいのが残念です。

  8. Groovy
    ググったら JAVA VM で動く動的言語が見つかったので味見してみました。型があるけど Ruby に似てる感じがあって抵抗なく使えました。
    ファイル名と同じ変数名が使えなくて Java のニオイがしました。solver_ = new Solver( 6, 10 ) と _ を付けているのはエラー回避のためです。

  9. Perl
    ローカル変数が my って・・・・イケてないと思います。配列変数が @$ だったり、参照渡しの場合は \[] が必要だったり、、、戸惑うし理解が難しいです。$Pieces{"F"} = [@{$Pieces{"F"}}[0..1]] なんて変態です。
    間違っていても中途半端に動いてしまうことがあり、初学者には難しいだけでなく危険だと思います。

  10. AWK
    ペントミノは AWK にとって可哀そうだけど頑張りましたって感じです。 AWK の連想配列はすばらしいです。配列が [1]から始まるのは AWK だから許します。
    mawk, nawk, gawk などの派生がまだ使われていて AWK の歴史を感じるけれど、プログラミング言語としては過去の言語だと思います。

  11. PHP
    WEB開発用の言語でしょ?ということぐらいしか知らず、ペントミノをPHPでやるもんじゃないと思いながらもやってみたらできました。言語としてはシンプルで理解しやすいし、パフォーマンスも悪くないです。
    やたらと $-> が目に付くのでそこは好きになれないです。行末の ; を忘れて怒られることが多いです。

  12. C
    C言語を使うことはホントに少なくなりました。メモリを意識しないといけないし、文字列の扱いが不自由だし、、、不便な言語です。実行速度はもちろん速いです。使い勝手としてはセンブリ言語みたいな感じがします。

  13. C++
    STL を使えば使い勝手はスクリプト言語に(少し)近づきます。STLの多用は実行速度に影響するので、書きやすさとパフォーマンスはトレードオフです。

  14. C#
    あまり親しみがないのでこんなものなのかな・・・という感じです。Java や C++ を知っていれば使えるのでしょう。Linuxでも動くのでもう少し使ってみようかな・・・

  15. Java
    面倒くさい印象があり、昔から使い易い(書き易い)とは思っていません。食わず嫌いです。ファイル名を小文字の solver.java にできなかったのが悔しいです。
    ubuntu の java はバージョンが古くて Text Block に対応してないので、それに合わせて PIECE_DEF_DOC はベタな書き方にしています(残念)。

  16. Kotlin
    Java よりも格段に良いです。Java の面倒くささを隠蔽してくれて、メソッドチェーンも良い感じに書けて使いやすいです。これで Java を使わずに VM のアプリが作れます。実際に私は Android アプリを Kotlin で作りました。

  17. Rust
    私には無茶苦茶とっつき難いです。コンパイルエラーを消すのに試行錯誤・・・どうしても unsafe を取ることもできず・・・。腰を落ち着けて勉強しないとだめですね。ポインタをガチャガチャいじるようなアプリには向いてないのかもしれません。
    エラーメッセジーはとても親切です。

  18. Go
    文法に独特感があって、触ってないと忘れてしまいます。このソルバープログラムを備忘録にします。

  19. Swift
    Objective-C を試そうと思ったけれど Mac以外で環境を作るのが面倒そうだったので諦めて、代わりに Swift を選びました。
    引数の型宣言が面倒でしたがあまり癖は感じなかったです。Mac以外で使うことあるのかなぁ・・・

  20. LISP
    今まで避けていましたが、興味があったので使ってみました。なんとなくLISPの心が解った気がします。でも、独特過ぎて書き易いとも見易いとも思いません。emacs の 設定が読めるようになった(たぶん)のでトライして良かったです。

  21. Crystal
    存在を知りませんでした。 ほぼ Rubyのように書けます。頑張ってると思います。C や C++ と同じとは行きませんが実行速度は速いです。頑張ってると思います。

  22. Elixir
    関数型言語の特徴を体験できます。表記方法に Ruby のニオイがします。すべてのデータが imutable なので、List、Map の要素を書き換えるという基本的な操作に手間取ったり、グローバル変数という概念がないので面食らいます。従来のプログラムではいかに副作用に依存していたかということを痛感します。パイプ演算子(|>)は面白いです。

  23. F#
    Elixir 以外の関数型言語もと思い試してみました。実行速度は速いです。表記方法は Python のニオイがするし、他にもクセの強さを感じました。また、match ~ with が難しい、エラーメッセージが解りにくい、など難しさも感じました。

動作環境

Linux ubuntu:22.04、Mac Sonoma:14.4、Cygwin(Windows)で動作確認しました。

tsc (TypeScript Compiler) では、npm i --save-dev @types/node を一度だけ実行しておく必要があります。 実行していないとエラーがでます。

TypeScript の初回実行
$ tsc -t es2019 -m commonjs --strict solver-ts.ts
solver-ts.ts(202,18): error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.

$ npm i --save-dev @types/node  # 指示通りに npm i ...を実行
$ ls node_modules              # node_modules ディレクトリが作られる
@types/   undici-types/
$ tsc -t es2019 -m commonjs --strict solver-ts.ts   # エラーにならずに実行できる

ubuntu 22.04

Mac と Windows(WSL2) でDocker コンテナに全ての言語をインストールして動作確認しました。Dockerfile で x86_64 でなければ arm64 の Julia と Swift をダウンロードするように設定しています。

Ruby
3.0.2
Python
3.10.12
Lua
5.1.5
Squirrel
3.2
Julia
1.10.2
JavaScript
node 12.22.9
TypeScript
tsc 4.9.5
Groovy
2.4.21
Perl
5.34.0
AWK
mawk 1.3.4
PHP
8.1.2
C
gcc 11.4.0
clang 15.0
C++
g++ 11.4.0
clang++ 15.0
C#
mcs 6.8.0.105
Java
javac 11.0.22
Kotlin
kotlinc-jvm 1.3
Go
1.18.1
Rust
rustc 1.75.0
Swift
5.10
LISP
sbcl 2.1.11
clisp 2.49.93
Crystal
1.14.0
LLVM: 18.1.6
Elixir
1.12.2
F# 6.0
fsi 12.0.0.0
dotnet 6.0.135

apt-get でインストールしましたが、Julia, Swift, Squirrel はパッケージが見つかりませんでした。Julia と Swift はバイナリイメージがあったのでそれをダウンロードし、Squirrel はソースからビルドしています。
Crystal は Dcokerfile に反映していないですが、x86_64版 tarball で動作確認しました。

Mac Sonoma

brew install でインストールしました。
(追記)F# は Microsoftの.NETのダウンロードサイトからダウンロードしました。

(2024/12 時点)

Ruby
3.2.0
Python
3.13.0
Lua
5.4.7
Squirrel
3.2
Julia
1.11.1
JavaScript
node 23.1
TypeScript
tsc 4.9.5
Groovy
4.0.24
Perl
5.34.1
AWK
gawk 5.3.1
nawk 20200816
PHP
8.3.13
C
clang 15.0.0
C++
clang++ 15.0.0
C#
mcs 6.12.0.182
csc 3.9.0
Java
javac 23.0.1
Kotlin
kotlinc-jvm 2.0.21
Go
1.23.2
Rust
rustc 1.76.0
Swiftc
1.90.1
LISP
sbcl 2.4.10
clisp 2.49.92
Crystal
1.14.0
Elixir
1.17.3
* F# 9.0
fsi 12.9.100.0
dotnet 9.0.101

Cygwin(Windows)

Cygwin Ports にないものは Windows版をインストールし(*印)、 Windows版もないものはソースからビルド(**印)しました。

Ruby
3.2.2
Python
3.9.16
Lua
5.3.6
** Squirrel
3.2
* Julia
1.8.2
* JavaScript
node 20.12.2
* TypeScript
tsc 4.9.5
* Groovy
2.5.23
perl
5.36
AWK
gawk 5.3.0
PHP
7.3.7
C
gcc 11.4.0
clang 8.0.1
C++
g++ 11.4.0
clang++ 8.0.1
C#
csc 4.8.4084.0
*Java
javac 21.0.2
* Kotlin
kotlinc-jvm 1.7.20
* Go
1.19.4
* Rust
rustc 1.70.0
* Swift
5.10
LISP
*sbcl 2.3.2
clisp 2.49
* Crystal
1.13.3
LLVM: 18.1.1
* Elixir
1.17.3
* F# 9.0
fsi 12.9.100.0

実行時間

やはり気になる実行時間をグラフにしてみました。MacBook M2 Sonoma の環境で、6x10 の全解(2,339通り)を求めるのにかかった時間をプロットしました。
スクリプト系とコンパイル系とでは桁が違ってくるので対数表示にしています。
202412.png

なお、アルゴリズムを改良すれば全ての言語において10倍以上の高速化が可能ですが、ここでは言語間の比較が目的なので最適化には触れません。

参考記事

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?