Help us understand the problem. What is going on with this article?

Go言語でバックスラッ シュの次の文字は小文字化しない

やりたいこと

文字列中の バックスラッシュの次の文字 以外を小文字にします。
バックスラッシュ次の文字 は小文字は小文字のまま、大文字は大文字のままにします。
要はバックスラッシュをエスケープシーケンスと見立てて、バックスラッシュの次の文字以外に対して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()という名前にしましたが、AllExcept ってなんでしょうね。

「すべて以外」とは...(哲学)

テストがこちら。

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away