このブログではもう既に何度も書いてる話だが、非常に大事な事なんで繰り返して書いていこうと思う。
「プログラミング言語なんてどれも極論変わらない。」
と言いたがる人が多いけど、確実に「考え方」が違う場合があるんだ。
特に、低レベル操作を行うのと目的としたプログラミング言語(C言語等)とモダンな言語(Python等)の間には考え方に隔たりがある。
と言うより「隔たりがなければならない」。
いつも言ってるけど、PythonをC言語のように書く、ってのは端的に言って間違ってるんだ。
教えて!gooから次のような問題を拾ってきた。
言語はjava,Pythonなんでも構いません。
データは以下のようになっている。
[
{2,3,4},
{1,7,8},
{3,4,5}
]
配列の各それぞれの要素1つ目(2,1,3)をページ数として出力したい。
出力結果イメージは以下の通り。
234
234
178
345
345
345
計6ページとなる
目的は良く分からんが、こういう質問があがっていた。
この質問だと、質問者が意図してるかどうかは分からんが、あたかもJavaとPythonが「同レベルの言語」と言う雰囲気になっている。
しかし、恐らく、この問題はC言語系の質問だろう。
ここを見れば分かる。
配列の各それぞれの要素1つ目(2,1,3)をページ数として出力したい。
問題のオチで出力云々、と問うのはC言語系の問題の特徴だ。
逆に言うと、モダンな言語では出力にこだわらない。どっちかと言うとデータ生成をしたがる。
Javaを初めとするC言語系のプログラミング言語だと、そもそもコンテナ系のデータ型を扱うのがメンド臭い。
まず、静的言語ならデータ型の宣言が必要だ。そしてコンストラクタを用いてのそのデータ型の生成。そしてその生成したデータ型に値を改めて設定しなければならない。
つまり、「何らかのコンテナ系データ型を使用したい」となったら最低でも3行以上を消費したりする。「使い始める」までの準備が多すぎて面倒臭いわけだ。
従って、JavaのようなC言語系プログラミング言語を使う事に慣れると、
- どんな問題だろうと、なるたけコンテナ系データ型を用いずに直接出力したい
と考えるようになるだろう。
そりゃ当然だ。だって使うのが面倒臭いんだもん。
使うのが面倒臭い、って事は、そのコンテナ系データ型を使う際には、一種「思い切り」が必要になってくる。気負い、っつーか気合か(笑)。そしてそんな事はなるたけやりたくないんで、やらないように考えるだろう。
よって、JavaのようなC言語系プログラミング言語だと、コンテナ系データを使う、って事は高度で、かつ奥義的な扱いになっちまう。
上の問題をJavaで書くと自然、こんなカンジで書くハメになる。
public class Main {public static void main(String[] args) {int[][] data = {{2, 3, 4},{1, 7, 8},{3, 4, 5}};for (int[] v: data) {for (int i = 0; i < v[0]; i++) {System.out.println(String.valueOf(v[0]) + String.valueOf(v[1]) + String.valueOf(v[2]));}}}}
もうとにかく処理して出力、の繰り返しだ。ホント結果、出力しか目的にしていない。
ここでは、仮に、このコードを応用して、何らかの使いまわしをしようとしても無理だ、って事になっている。出力はされればそれで終了、だからな。
結局、「コンテナ型のデータを使うのが面倒臭い」ってのはそれだけ「思考法」に対して足枷を嵌めるんだ、って事だ。面倒臭い某を人間は自然と避けようとする。
結果、考える筋道は、いつも「ラクするならこっち」と言う事で一定方向へ鍛え上げられてしまう。
これがC言語脳の正体だ。そしてプログラミング初学者にとって、「使うのが不自由な言語」が、如何に思考に制限をかけて歪に矯正していくのか、ってのが分かるんじゃないか、って思う。
一方、Pythonのようなモダンな言語の場合、データ生成自体が簡単だ。ハナクソをほじってる間にデータ生成が出来てしまう。
同じ問題をPythonで書くなら次のようにして書くのが望ましい。
[i[0] * ''.join(map(str, i + ['\n'])) for i in [[2, 3, 4], [1, 7, 8], [3, 4, 5]]]
たった一行で書けてしまう。
そもそもリスト内包表記は「リストと言うデータを生成する」書式なんで、まさしくデータ生成をしてる。
ここで生成されるデータは何だろうか。
実は次のようになる。
['234\n234\n', '178\n', '345\n345\n345\n']
文字列を3要素、としたリストだ。要素をprintに与えればそのまま印字出来る。
しかもPythonの文字列クラスに用意されてるメソッドはJavaのそれより強力だ。「同じ文字列を複数連結させる」のは掛け算をオーバーライドしてる効果だ。
従って、問題の要請により、データの要素であるリストの先頭がページ数だとすると、それを生成した文字列に掛けるだけで良い。
つまり、Java版のようにforを重ねがける、ような真似をしなくても良くなるんだ。
上のPythonの例だとワンライナーでこの問題に特化してるが、当然もうちょっと一般化した関数に直す事も出来る。
def foo(data):
return [i[0] * ''.join(map(str, i + ['\n'])) for i in data]
整数要素を持つ二次元リストに特化してるが、いずれにせよ、どんな二次元リストを与えようと文字列のリストにしてしまう。
そして、「データを返す」と言う事は「別のトコでこの形式を使い回しが可能だ」と言う事だ。モダンな言語では、「出力」は所詮その中のOne of themでしかない。
JavaのようなC言語系の「出力にこだわったが故にそれしか出来ない」可能性を狭めた方式とは全く違うわけだ。
Pythonは、結果、もうちょっと一般化したプログラムを書こうとすれば、次のように修正する程度で良くなる。
#!/usr/bin/env python3
import sys
def foo(data):
return [i[0] * ''.join(map(str, i + ['\n'])) for i in data]
if __name__ == '__main__':
data = [list(map(int, i)) for i in sys.argv[1:]]
[print(i, end = '') for i in foo(data)]
こうすれば、端末から受け取ったいくつかの数字の列をロジックに従った形式へ変換して出力する。
こういう「柔軟性のあるプログラミング」は出力にこだわるC言語系のプログラミングでは決して得られないモノだ。
Pythonを学ぶなら、是非ともこういう「簡単にデータ生成が出来る」と言う特徴を良く学んで欲しい。
繰り返すが、「出力にこだわる」必要性は、モダンな言語ではまったくないんだ。
有名な問題でFizzBuzz問題ってのがある。
「1から100までの数字を画面に表示する。ただし、3の倍数のときは数字の代わりにFizzと表示し、5の倍数のときは数字の代わりにBuzzと表示し、15の倍数のときは数字の代わりにFizzBuzzと表示する」
これは元々は「プログラミングの学科を卒業してもプログラミングが出来ない」ヤツを発見する為に考案された問題だ。
しかし、同時に、モダンな言語を扱い慣れてる人とそうじゃない人を発見するにも使えると思う。
モダンな言語を扱い慣れてる人も、これを「データ生成」の問題だと捉えるだろう。
Pythonだったら以下のように書くのが望ましい。
これもリスト表記でのワンライナーかどうか、ってのは関係ない。やはり
- 出力は関係ない
- データ生成で問題を解く
ってのがポイントだ。
Java辺りのC言語系プログラミング言語だと、やっぱり問題のオチである"表示"を重要視して、出力系関数をどうやって被せるか、と言う捉え方がフツーになるだろう。
public class Main {
static public void main(String[] args){
for (int i = 1; i <= 100; i++) {
if (i % 15 == 0) {
System.out.printf("%s ", "FizzBuzz");
} else if (i % 5 == 0) {
System.out.printf("%s ", "Buzz");
} else if (i % 3 == 0) {
System.out.printf("%s ", "Fizz");
} else {
System.out.printf("%d ", i);
}
}
System.out.println("");
}
}
これも結局、JavaなんかのC言語系プログラミング言語だと、コンテナ系のデータ型を扱うのがメンド臭いので、コンテナ系のデータ型を使ったトコでコードが短くならない、からだ。同じモノを書いてもコードが短い方が正義の法則だと、Javaだとコンテナ系を使うより直接出力した方が結果早い、って事になるわけだ。
従って、これはJavaみたいなC言語系のプログラミング言語の縛りであって、「より自由な」PythonではC言語系みたいなプログラムを書いてはいけない。
つまり、Javaっぽく
for i in range(1, 101):
if i % 15 == 0:
print("FizzBuzz ", end='')
elif i % 5 == 0:
print("Buzz ", end='')
elif i % 3 == 0:
print("Fizz ", end='')
else:
print("%d " % i, end='')
なんて書けばダメなんだ。
とにかく、モダンな言語では、まずはいつも「どのようなデータを生成しようか」考えるようにしよう。
何度も言うが、その方が再利用出来る可能性があるコードを記述出来るし、出力自体はあとでどうにでも弄れるんだ。