画面にこう出れば正解
どの言語で書いても、同じ入力なら画面に出る文字は1文字も違わない、というのが正解の決め方です(例は Python で動かしたときのコマンドです)。
基本
$ python3 split.py 東京,大阪,名古屋
3項目
1: 東京
2: 大阪
3: 名古屋間が空
$ python3 split.py 東京,,名古屋
3項目
1: 東京
2: (空)
3: 名古屋最後が空
$ python3 split.py 東京,大阪,
3項目
1: 東京
2: 大阪
3: (空)最後に空が2つ
$ python3 split.py 東京,,,
4項目
1: 東京
2: (空)
3: (空)
4: (空)先頭が空
$ python3 split.py ,東京
2項目
1: (空)
2: 東京区切りなし
$ python3 split.py 東京
1項目
1: 東京空
$ python3 split.py ""
項目がありません空白は残す
$ python3 split.py "東京, 大阪"
2項目
1: 東京
2: 大阪10言語のプログラム
どれもそのまま保存して動かせます。毎朝、上の正解の例をすべて実際に動かして確かめています。
split.php8.4.26 で 8 / 8 件の出力が正解と一致
<?php
// カンマで区切って、項目の数と中身を出す。空の項目も1つと数える
$s = $argv[1];
if ($s === '') {
echo "項目がありません\n";
exit;
}
// explode は、間・先頭・最後の空の項目も残す(空の文字列だけは [""] になるので、先に調べた)
$items = explode(',', $s);
echo count($items) . "項目\n";
foreach ($items as $i => $item) {
if ($item === '') {
$item = '(空)';
}
echo ($i + 1) . ": " . $item . "\n";
}
動かし方(ファイルを保存したフォルダで) ・ PHP を入れる
$ php split.php 東京,大阪,名古屋
split.py3.14.7 で 8 / 8 件の出力が正解と一致
# カンマで区切って、項目の数と中身を出す。空の項目も1つと数える
import sys
s = sys.argv[1]
if s == "":
print("項目がありません")
sys.exit()
# split(",") は、間・先頭・最後の空の項目も残す(引数なしの split() は空白で区切り、空の項目を捨てるので使わない)
items = s.split(",")
print(f"{len(items)}項目")
for i, item in enumerate(items, start=1):
if item == "":
item = "(空)"
print(f"{i}: {item}")
動かし方(ファイルを保存したフォルダで) ・ Python を入れる
$ python3 split.py 東京,大阪,名古屋
split.go1.27.2 で 8 / 8 件の出力が正解と一致
// カンマで区切って、項目の数と中身を出す。空の項目も1つと数える
package main
import (
"fmt"
"os"
"strings"
)
func main() {
s := os.Args[1]
if s == "" {
fmt.Println("項目がありません")
return
}
// strings.Split は、間・先頭・最後の空の項目も残す(空の文字列だけは [""] になるので、先に調べた)
items := strings.Split(s, ",")
fmt.Printf("%d項目\n", len(items))
for i, item := range items {
if item == "" {
item = "(空)"
}
fmt.Printf("%d: %s\n", i+1, item)
}
}
動かし方(ファイルを保存したフォルダで) ・ Go を入れる
$ go run split.go 東京,大阪,名古屋
split.js24.15.0 で 8 / 8 件の出力が正解と一致
// カンマで区切って、項目の数と中身を出す。空の項目も1つと数える
const s = process.argv[2];
if (s === "") {
console.log("項目がありません");
process.exit();
}
// split(",") は、間・先頭・最後の空の項目も残す(空の文字列だけは [""] になるので、先に調べた)
const items = s.split(",");
console.log(`${items.length}項目`);
items.forEach((item, i) => {
console.log(`${i + 1}: ${item === "" ? "(空)" : item}`);
});
動かし方(ファイルを保存したフォルダで) ・ JavaScript を入れる
$ node split.js 東京,大阪,名古屋
Split.java21.0.12 で 8 / 8 件の出力が正解と一致
// 受け取った文字列をカンマで区切って、項目の数と中身を出す。空の項目も数える
public class Split {
public static void main(String[] args) {
String s = args[0];
if (s.isEmpty()) {
System.out.println("項目がありません");
return;
}
// split(",") は最後に続く空の項目を取り除くので、2つ目の引数に -1 を渡して残す
String[] items = s.split(",", -1);
System.out.println(items.length + "項目");
for (int i = 0; i < items.length; i++) {
String item = items[i].isEmpty() ? "(空)" : items[i];
System.out.println((i + 1) + ": " + item);
}
}
}
動かし方(ファイルを保存したフォルダで) ・ Java を入れる
$ java Split.java 東京,大阪,名古屋
split.cs10.0.402 で 8 / 8 件の出力が正解と一致
// 受け取った文字列をカンマで区切って、項目の数と中身を出す。空の項目も数える
string s = args[0];
// 空の文字列を Split すると「空の項目1つ」になるので、先に分けておく
if (s == "")
{
Console.WriteLine("項目がありません");
return;
}
// Split は空の項目も残す(取り除くのは StringSplitOptions.RemoveEmptyEntries を渡したときだけ)
string[] items = s.Split(',');
Console.WriteLine($"{items.Length}項目");
for (int i = 0; i < items.Length; i++)
{
string item = items[i] == "" ? "(空)" : items[i];
Console.WriteLine($"{i + 1}: {item}");
}
動かし方(ファイルを保存したフォルダで) ・ C# を入れる
$ dotnet run split.cs -- 東京,大阪,名古屋
split.rs1.99.0 で 8 / 8 件の出力が正解と一致
// 受け取った文字列をカンマで区切って、項目の数と中身を出す。空の項目も数える
fn main() {
let args: Vec<String> = std::env::args().collect();
let s = &args[1];
// 空の文字列を split すると「空の項目1つ」になるので、先に分けておく
if s.is_empty() {
println!("項目がありません");
return;
}
// split は空の項目も残す(最後の空だけを取り除くのは split_terminator)
let items: Vec<&str> = s.split(',').collect();
println!("{}項目", items.len());
for (i, item) in items.iter().enumerate() {
let item = if item.is_empty() { "(空)" } else { item };
println!("{}: {}", i + 1, item);
}
}
動かし方(ファイルを保存したフォルダで) ・ Rust を入れる
$ rustc split.rs && ./split 東京,大阪,名古屋
split.rb4.0.7 で 8 / 8 件の出力が正解と一致
# カンマで区切って、項目の数と中身を出す。空の項目も1つと数える
s = ARGV[0]
if s == ""
puts "項目がありません"
exit
end
# split(",") は最後の空の項目を捨てる。2つ目の引数に -1 を渡すと、最後の空の項目も残す
items = s.split(",", -1)
puts "#{items.length}項目"
items.each_with_index do |item, i|
item = "(空)" if item == ""
puts "#{i + 1}: #{item}"
end
動かし方(ファイルを保存したフォルダで) ・ Ruby を入れる
$ ruby split.rb 東京,大阪,名古屋
split.kt2.4.21 で 8 / 8 件の出力が正解と一致
// 受け取った文字列をカンマで区切って、項目の数と中身を出す。空の項目も数える
fun main(args: Array<String>) {
val s = args[0]
// 空の文字列を split すると「空の項目1つ」になるので、先に分けておく
if (s.isEmpty()) {
println("項目がありません")
return
}
// Kotlin の split は、Java の split と違って、最後に続く空の項目も残す
val items = s.split(",")
println("${items.size}項目")
for ((i, item) in items.withIndex()) {
println("${i + 1}: ${if (item.isEmpty()) "(空)" else item}")
}
}
動かし方(ファイルを保存したフォルダで) ・ Kotlin を入れる
$ kotlinc split.kt -include-runtime -d split.jar && java -jar split.jar 東京,大阪,名古屋
split.swift6.3.3 で 8 / 8 件の出力が正解と一致
// 受け取った文字列をカンマで区切って、項目の数と中身を出す。空の項目も数える
let s = CommandLine.arguments[1]
if s.isEmpty {
print("項目がありません")
} else {
// split(separator:) は空の項目をすべて取り除くので、omittingEmptySubsequences: false で残す
let items = s.split(separator: ",", omittingEmptySubsequences: false)
print("\(items.count)項目")
for (i, item) in items.enumerated() {
print("\(i + 1): \(item.isEmpty ? "(空)" : String(item))")
}
}
動かし方(ファイルを保存したフォルダで) ・ Swift を入れる
$ swift split.swift 東京,大阪,名古屋
言語でつまずきやすいところ
初心者がよく書く形(各言語で最初に習う書き方をそのまま使ったもの)を、同じ正解表で動かした結果です。言語を出した答えごとにまとめ、赤い字はその言語が実際に出した間違った行です。
| 入力 | 初心者がよく書く形の答え(10言語) |
|---|---|
基本東京,大阪,名古屋 | ✓10言語すべて正解 |
間が空東京,,名古屋 |
|
最後が空東京,大阪, |
|
最後に空が2つ東京,,, |
|
先頭が空,東京 |
|
区切りなし東京 | ✓10言語すべて正解 |
空"" | ✓10言語すべて正解 |
空白は残す"東京, 大阪" | ✓10言語すべて正解 |
この回でやること
- 文字列を、決まった文字(カンマ)で区切る
- 区切った項目に、番号を付けて出す
- 空の項目を見分ける
この回の決まり
- カンマとカンマの間が何もなければ、空の項目として数える(「(空)」と出す)。先頭や最後のカンマの外側も、空の項目として数える
- 入力が空の文字列なら「項目がありません」
- 項目の前後の空白は取らない(「東京, 大阪」の2つ目は「 大阪」)
解説
split は便利だが、空の項目の扱いが違う
どの言語にも、文字列を区切る関数(split・explode・Split・components など)があります。「東京,大阪,名古屋」のように空の項目がなければ、どれも同じ3つに分けます。
違いが出るのは、「東京,,名古屋」のように空の項目があるときや、「東京,大阪,」のように最後がカンマで終わるときです。各言語のいちばん普通の書き方で試した結果です。
| 書き方 | 空の項目 | 「東京,,,」を区切ると |
|---|---|---|
PHP explode・Python split(",")・Go strings.Split・JavaScript split(",")・C# Split(',')・Kotlin split(",")・Rust split(',') | すべて残す | 4項目 |
Java split(",")・Ruby split(",") | 最後に続く空の項目だけ取り除く | 1項目 |
Swift split(separator: ",") | すべて取り除く | 1項目 |
Java・Ruby は、最後の空の項目を残したいときに split(",", -1) と書きます。Swift は split(separator: ",", omittingEmptySubsequences: false) と書くか、components(separatedBy: ",") を使います。
なぜ大事か
CSV の1行や、フォームの「タグ(カンマ区切り)」の欄では、空の項目にも意味があることがあります。たとえば「名前,電話,メール」の電話が空の行で、空の項目が消えると、メールが2番目に繰り上がってしまいます。
練習
- 空の項目を取り除いて出すようにしてみましょう
- 項目の前後の空白を取り除いてから出すようにしてみましょう