はじめに
1回目の記事ではバブルソートのトレースを、2回目の記事では線形探索・二分探索のトレースを、それぞれC言語で確認した。3回目の記事では、1回目で書いたバブルソートをGo・Kotlinに移植して書き比べた。4回目となる今回は、2回目で書いた線形探索・二分探索を、GoとKotlinに移植した。
バブルソートのときと同様、アルゴリズムのロジックは3言語ともまったく同じである。ただ今回は「探索」というテーマ上、配列の走査の仕方や分岐の書き方に、言語ごとの個性がよりはっきり出てくる。
3言語のコード
C言語版(おさらい)
int linear_search(int arr[], int size, int target) {
int comparisons = 0;
for (int i = 0; i < size; i++) {
comparisons++;
printf(" 比較%d回目: arr[%d] = %d と %d を比較\n", comparisons, i, arr[i], target);
if (arr[i] == target) {
printf(" → 見つかりました(比較回数: %d回)\n", comparisons);
return i;
}
}
printf(" → 見つかりませんでした(比較回数: %d回)\n", comparisons);
return -1;
}
int binary_search(int arr[], int size, int target) {
int low = 0;
int high = size - 1;
int comparisons = 0;
while (low <= high) {
int mid = (low + high) / 2;
comparisons++;
printf(" 比較%d回目: low=%d, high=%d, mid=%d (arr[mid]=%d) と %d を比較\n",
comparisons, low, high, mid, arr[mid], target);
if (arr[mid] == target) {
printf(" → 見つかりました(比較回数: %d回)\n", comparisons);
return mid;
} else if (arr[mid] < target) {
low = mid + 1;
} else {
high = mid - 1;
}
}
printf(" → 見つかりませんでした(比較回数: %d回)\n", comparisons);
return -1;
}Go版
func linearSearch(arr []int, target int) int {
comparisons := 0
for i, v := range arr {
comparisons++
fmt.Printf(" 比較%d回目: arr[%d] = %d と %d を比較\n", comparisons, i, v, target)
if v == target {
fmt.Printf(" → 見つかりました(比較回数: %d回)\n", comparisons)
return i
}
}
fmt.Printf(" → 見つかりませんでした(比較回数: %d回)\n", comparisons)
return -1
}
func binarySearch(arr []int, target int) int {
low := 0
high := len(arr) - 1
comparisons := 0
for low <= high {
mid := (low + high) / 2
comparisons++
fmt.Printf(" 比較%d回目: low=%d, high=%d, mid=%d (arr[mid]=%d) と %d を比較\n",
comparisons, low, high, mid, arr[mid], target)
if arr[mid] == target {
fmt.Printf(" → 見つかりました(比較回数: %d回)\n", comparisons)
return mid
} else if arr[mid] < target {
low = mid + 1
} else {
high = mid - 1
}
}
fmt.Printf(" → 見つかりませんでした(比較回数: %d回)\n", comparisons)
return -1
}Kotlin版
fun linearSearch(arr: IntArray, target: Int): Int {
var comparisons = 0
for (i in arr.indices) {
comparisons++
println(" 比較${comparisons}回目: arr[$i] = ${arr[i]} と $target を比較")
if (arr[i] == target) {
println(" → 見つかりました(比較回数: ${comparisons}回)")
return i
}
}
println(" → 見つかりませんでした(比較回数: ${comparisons}回)")
return -1
}
fun binarySearch(arr: IntArray, target: Int): Int {
var low = 0
var high = arr.size - 1
var comparisons = 0
while (low <= high) {
val mid = (low + high) / 2
comparisons++
println(" 比較${comparisons}回目: low=$low, high=$high, mid=$mid (arr[mid]=${arr[mid]}) と $target を比較")
when {
arr[mid] == target -> {
println(" → 見つかりました(比較回数: ${comparisons}回)")
return mid
}
arr[mid] < target -> low = mid + 1
else -> high = mid - 1
}
}
println(" → 見つかりませんでした(比較回数: ${comparisons}回)")
return -1
}比較ポイント①:配列を「なめる」書き方
線形探索は配列の先頭から1つずつ見ていくだけの単純な処理だが、その「1つずつ見ていく」ループの書き方に言語ごとの個性が出る。
| 言語 | 書き方 | 特徴 |
|---|---|---|
| C | for (int i = 0; i < size; i++) + arr[i] | 添字を自分で管理し、都度 arr[i] で値を取り出す |
| Go | for i, v := range arr | 添字 i と値 v を同時に取得できる。arr[i] の書き直しが不要 |
| Kotlin | for (i in arr.indices) | arr.indices で「有効な添字の範囲」だけを表現できる |
バブルソート編でも触れたとおり、Cのように自分で添字を管理する書き方は基本に忠実な分、記述量は増える。Go/Kotlinはどちらも「添字管理をどう楽にするか」という方向性は同じだが、Goは値まで一緒に取り出せる range、Kotlinは添字の範囲だけを取り出す indices と、アプローチが少し異なるのが面白いところである。
比較ポイント②:二分探索の分岐の書き方
二分探索は「一致」「対象より小さい」「対象より大きい」の3パターンに分岐する処理である。ここは3言語で最も書き味の違いが出る部分だ。
- C:
if (arr[mid] == target) { ... } else if (arr[mid] < target) { ... } else { ... }という素直なif-elseの連なり - Go:Cとほぼ同じ
if-else if-elseの形。Cが読めればそのまま読める - Kotlin:
when { }という式で3パターンをまとめて書ける。if-elseの連なりよりも「複数条件のうちどれか」という意図が伝わりやすい
二分探索のように分岐条件が3つ以上並ぶ処理では、Kotlinの when のような「多分岐専用の構文」があるとコードの見通しがよくなる、という一つの例になっている。
比較ポイント③:探索の様子を表示する書き方
今回のコードは、比較回数や探索範囲(low・high・mid)を毎回出力して、探索の過程を目に見える形にしている。この「値を文章に埋め込んで表示する」処理も、言語ごとに書き方が異なる。
- C:
printf("...%d...", comparisons, i, arr[i], target)のように、フォーマット指定子と変数の対応を自分で数えながら書く - Go:
fmt.Printf("...%d...", ...)とCとほぼ同じ書き方。関数名がPrintf(先頭大文字)になる点だけ違う - Kotlin:
" 比較${comparisons}回目: ..."のように、文字列の中に直接変数を埋め込む文字列テンプレートを使う
C・Goはどちらも「フォーマット文字列+変数リスト」という同じ発想だが、Kotlinは変数を文字列の中に直接書けるため、「どの値がどこに入るか」を目で追いやすいという違いがある。特に今回のように出力する値の数が多い処理では、この違いが体感しやすいはずだ。
まとめ
- アルゴリズムのロジック(線形探索・二分探索とも)は3言語で完全に同じ
- 配列を舐めるループの書き方は、Go(
range)・Kotlin(indices)でそれぞれ異なる楽さがある - 多分岐の処理は、Kotlinの
whenのような専用構文があると見通しがよくなる - 値の出力方法も、C/Goの「フォーマット文字列型」とKotlinの「文字列テンプレート型」で発想が異なる
バブルソート編・線形探索/二分探索編と2つのアルゴリズムを3言語で書き比べてみて、「ロジックは同じでも、言語が用意している道具(構文)によって書き味が変わる」という感覚がよりはっきりとしてきた。今後も、Cで学んだ他のアルゴリズムの多言語比較を続けていく予定である。
この記事で扱ったコードは、GitHubの c-language-studies リポジトリ(FE-algorithm/02_search/go/、FE-algorithm/02_search/kotlin/)で公開している。


コメント