やりたいこと
文字列中の バックスラッシュの次の文字 以外を小文字にします。
バックスラッシュ次の文字 は小文字は小文字のまま、大文字は大文字のままにします。
要はバックスラッシュをエスケープシーケンスと見立てて、バックスラッシュの次の文字以外に対してToLower()
を適用する関数を作りたいのです。
やり方1: runeのインデックスを取得
Goのruneを理解するためのUnicode知識を参考にバックスラッシュが入るruneのインデックスを取得し、そのインデックス以外にunicode.ToLower(r)
をすることで バックスラッシュの次の文字 以外の小文字化に成功しました。
package main
import (
"strings"
"unicode"
)
func runeIndex(s string, r rune) int {
bi := strings.IndexRune(s, r)
return len([]rune(s[0:bi]))
}
// ToLowerExcept : ToLower string Except specific rune
func ToLowerExcept(s string, r rune) (string, int) {
bspos := runeIndex(s, r) + 1 // Next position of `r`
runes := []rune(s)
for i, r := range runes {
if i != bspos { // to LOWER except next position of `r`
runes[i] = unicode.ToLower(r)
}
}
return string(runes), bspos
}
しかしながら、このやり方ではsにバックスラッシュが複数入る場合、最初の バックスラッシュの次の文字 にしか対応しておらず、2つ目以降の バックスラッシュの次の文字 は小文字化されません。
やり方2: バックスラッシュで分割する
最初はやり方1で作ったToLowerExcept()
を使って再帰的に処理しようかと思ったのですが、頭が煮詰まってしまったので断念しました。
プログラミングで煮詰まったときは一度寝ることが最速に解にたどり着く方法です。一度眠ってから起き抜けに思いついた方法として、 バックスラッシュで区切り、最初の一文字以外を小文字化 する方法をひらめきました。
strings.Split(s, "\\")
でバックスラッシュで区切り、返された[]string
の最初の文字以外をToLowerしていく関数がこちら。
package main
import (
"strings"
"unicode"
)
// ToLowerExceptFirst : To lower except first of runes
func ToLowerExceptFirst(s string) string {
runes := []rune(s)
for i, r := range runes { // to LOWER except first position
if i != 0 {
runes[i] = unicode.ToLower(r)
}
}
return string(runes)
}
// ToLowerExceptAll : ToLower string Except specific rune for whole words
func ToLowerExceptAll(s string, r rune) string {
st := strings.Split(s, "\\")
for i, si := range st {
st[i] = ToLowerExceptFirst(si)
}
return strings.Join(st, "\\")
}
余談ですが、ToLowerExcept()
関数をすべてのバックスラッシュに適用するからstringsパッケージやregexpパッケージの命名法に習ってToLowerExceptAll()
という名前にしましたが、All の Except ってなんでしょうね。
「すべて以外」とは...(哲学)
テストがこちら。
package main
import "testing"
func TestRuneIndex(t *testing.T) {
s := "あtTの5\\UW"
actual := RuneIndex(s, '\\')
expect := 5
if expect != actual {
t.Fatalf("got: %v want: %v", actual, expect)
}
}
func TestToLowerExcept(t *testing.T) {
s := "あtTの5\\UW"
actual := ToLowerExcept(s, '\\')
expect := "あttの5\\Uw"
if expect != actual {
t.Fatalf("got: %v want: %v", actual, expect)
}
}
func TestToLowerExceptFirst(t *testing.T) {
s := "SあtT5\\Uほw\\dHo\\T"
actual := ToLowerExceptFirst(s)
expect := "Sあtt5\\uほw\\dho\\t"
if expect != actual {
t.Fatalf("got: %v want: %v", actual, expect)
}
}
func TestToLowerExceptAll(t *testing.T) {
s := "\\SあtT5\\Uほw\\dHo\\T"
actual := ToLowerExceptAll(s, '\\')
expect := "\\Sあtt5\\Uほw\\dho\\T"
if expect != actual {
t.Fatalf("got: %v want: %v", actual, expect)
}
}
手元での動作確認用go playgroundがこちら。
The Go Playground