【Golang】文字間の複数ホワイトスペースを削除して1つのスペースに置き換える関数（全角スペース含む）

> Go 言語（以下 Golang）で、文字列内の文字間にある複数スペースやタブを 1 つに変換したい。ついでに、全角スペースも。

つまり、**単語間の空白を詰めたい**のです。

```bash:こういうことがしたい（before-->after）
" Hello,    World  ! "			--> "Hello, World !"
"Hello,\tWorld ! "				--> "Hello, World !"
" \t\n\t Hello,\tWorld\n!\n\t"	--> "Hello, World !"
# 以下は全角スペース
"　　　　　Hello,　　World!　　"				--> "Hello, World!"
```

[「golang 文字間の複数スペースを削除する」でググって](https://www.google.com/search?q=golang+%E6%96%87%E5%AD%97%E9%96%93%E3%81%AE%E8%A4%87%E6%95%B0%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B)も、正規表現や `strings.ReplaseAll` を使った方法だったり、文字列の前後のトリムばかりだったので、自分のググラビリティとして。

## TL; DR <sub><sup><sub><sup>（今北産業）</sup></sub></sup></sub>

1. [strings.Fields](https://pkg.go.dev/strings#Fields) 関数を使う
1. `strings.Fields()` は [strings.Split](https://pkg.go.dev/strings#Split) 関数の親戚で、連続するホワイト・スペース（空白、タブや改行）は 1 つとして扱う 
1. `strings.Fields()` で分解し、`strings.Join()` で再度組み合わせると単語間の空白を詰められる

```go:単語間を詰める関数
// TrimWordGaps は、文字列内の連続した空白文字を 1 文字のスペースに変換します。
// 具体的な使い方・サンプルは TS; DR 参照
func TrimWordGaps(s string) string {
	return strings.Join(strings.Fields(s), " ")
}
```

## TS; DR

### `strings.Fields` とは

> ```go:func Fields
> func Fields(s string) []string
> ```
> Fields splits the string `s` around each instance of one or more consecutive white space characters, as defined by `unicode.IsSpace`, returning a slice of substrings of s or an empty slice if s contains only white space.
> <sub>（[Fields](https://pkg.go.dev/strings#Fields) | strings @ pkg.go.dev より）</sub>
>
> 【筆者訳】
> `Fields` は、1 つ以上の連続したホワイト・スペースで囲まれている文字の周りで文字列 `s` を分割し、その部分文字列のスライスを返します。また、`s` が空白のみを含む場合は空のスライスを返します。なお、ホワイト・スペースは [unicode.IsSpace](https://pkg.go.dev/unicode#IsSpace)で定義されている空白文字です。

つまり、`strings.Fields()` は `strings.Slice()` の変形バージョンです。

例えば `strings.Slice(s, " ")` は、`s` 内でヒットした "` `" ごとに分割するのに対し、`strings.Fields(s)` は連続している空白文字（ホワイトスペース）を 1 文字として扱い `s` を分割します。

### 動作サンプル

```go:サンプル（全角スペース含む）
package main

import (
	"fmt"
	"strings"
	"testing"
)

func TrimWordGaps(s string) string {
	return strings.Join(strings.Fields(s), " ")
}

func ExampleTrimWordGaps() {
	result := TrimWordGaps("    Hello,       World     !    ")

	fmt.Println(result)

	// Output: Hello, World !
}

func TestTrimWordGaps(t *testing.T) {
	for i, test := range []struct {
		input  string
		expect string
	}{
		{
			input:  " Hello,   World  ! ",
			expect: "Hello, World !",
		},
		{
			input:  "Hello,\tWorld ! ",
			expect: "Hello, World !",
		},
		{
			input:  " \t\n\t Hello,\tWorld\n!\n\t",
			expect: "Hello, World !",
		},
		{
			// 全角スペース入り
			input:  "　　　　　Hello,　　World!　　",
			expect: "Hello, World!",
		},
	} {
		nameTest := fmt.Sprintf("test #%d (Input:%#v)", i, test.input)

		t.Run(nameTest, func(t *testing.T) {
			actual := TrimWordGaps(test.input)
			if test.expect != actual {
				t.Fail()
			}
		})
	}
}
// Output:
// === RUN   TestTrimWordGaps
// === RUN   TestTrimWordGaps/test_#0_(Input:"_Hello,___World__!_")
// === RUN   TestTrimWordGaps/test_#1_(Input:"Hello,\tWorld_!_")
// === RUN   TestTrimWordGaps/test_#2_(Input:"_\t\n\t_Hello,\tWorld\n!\n\t")
// === RUN   TestTrimWordGaps/test_#3_(Input:"\u3000\u3000\u3000\u3000\u3000Hello,\u3000\u3000World!\u3000\u3000")
// --- PASS: TestTrimWordGaps (0.00s)
//     --- PASS: TestTrimWordGaps/test_#0_(Input:"_Hello,___World__!_") (0.00s)
//     --- PASS: TestTrimWordGaps/test_#1_(Input:"Hello,\tWorld_!_") (0.00s)
//     --- PASS: TestTrimWordGaps/test_#2_(Input:"_\t\n\t_Hello,\tWorld\n!\n\t") (0.00s)
//     --- PASS: TestTrimWordGaps/test_#3_(Input:"\u3000\u3000\u3000\u3000\u3000Hello,\u3000\u3000World!\u3000\u3000") (0.00s)
// === RUN   ExampleTrimWordGaps
// --- PASS: ExampleTrimWordGaps (0.00s)
// PASS
```

- [オンラインで動作をみる](https://go.dev/play/p/0XuGyAGEyUe) @ Go playground
- 動作確認: Go 1.18, 1.19, 1.22

## 参考文献

- [Fields](https://pkg.go.dev/strings#Fields) | strings @ pkg.go.dev
- [How to remove redundant spaces/whitespace from a string in Golang?](https://stackoverflow.com/a/42251527/18152508) @ Stackoverflow