Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16985] ファイルの概要

このコミットは、Go言語の標準ライブラリであるstringsパッケージにIndexByte関数を追加し、既存のIndex関数が単一バイトの検索を行う際に新しく追加されたIndexByte関数を利用するように変更するものです。これにより、bytesパッケージとの一貫性が向上し、開発者が文字列とバイトスライスの両方で同様の検索操作を行う際の利便性が高まります。

コミット

commit 9742003ffc7fd72ce2b433e9895ecbb6d9e4c720
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Aug 1 11:17:26 2013 -0700

    strings: add IndexByte, for consistency with bytes package
    
    I always forget which package has it.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12214044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/9742003ffc7fd72ce2b433e9895ecbb6d9e4c720

元コミット内容

strings: add IndexByte, for consistency with bytes package I always forget which package has it.

このコミットの目的は、stringsパッケージにIndexByte関数を追加することです。これは、bytesパッケージに既に同様の機能が存在するため、両パッケージ間での一貫性を保つためです。コミットメッセージにある「いつもどちらのパッケージにあるか忘れてしまう」という記述は、開発者にとっての使いやすさ、特にAPIの一貫性の重要性を示唆しています。

変更の背景

Go言語の標準ライブラリには、文字列操作を行うstringsパッケージと、バイトスライス操作を行うbytesパッケージがあります。これらのパッケージは、それぞれ異なるデータ型(string[]byte)を扱うものの、多くの類似した機能を提供しています。例えば、部分文字列やバイトの検索、置換、分割などです。

このコミット以前は、特定のバイト(byte型)がバイトスライス内に最初に現れるインデックスを検索する関数としてbytes.IndexByteが存在していました。しかし、文字列(string型)に対して同様の単一バイト検索を行う直接的な関数はstringsパッケージにはありませんでした。strings.Index関数は存在しましたが、これは部分文字列(string型)を検索するものであり、単一バイトを検索する場合にはstrings.Index(s, string(c))のように型変換が必要でした。

このような状況は、開発者にとって混乱の原因となることがありました。特に、文字列とバイトスライスの間で頻繁に変換を行うようなコードを書く場合、どちらのパッケージにどの関数があるのかを常に意識する必要がありました。コミットメッセージにある「I always forget which package has it.」という一文は、この開発者の不便さを端的に表しています。

この変更の背景には、Go言語の標準ライブラリ全体でAPIの一貫性を高め、開発者の認知負荷を軽減するという設計思想があります。bytes.IndexByteに対応するstrings.IndexByteを追加することで、文字列とバイトスライスの両方で、単一バイトの検索という共通の操作に対して統一されたインターフェースを提供することが可能になります。これにより、開発者はデータ型に応じて異なる関数名を覚える必要がなくなり、より直感的でエラーの少ないコードを書けるようになります。

前提知識の解説

Go言語のstring型と[]byte

Go言語において、string型は不変のバイトスライスとして扱われます。これはUTF-8エンコードされたテキストを表すために使用されます。一方、[]byte型は可変のバイトスライスであり、任意のバイナリデータを扱うのに適しています。

  • string: 文字列リテラルや、UTF-8エンコードされたテキストデータを扱う際に使用されます。不変であるため、一度作成された文字列の内容を変更することはできません。文字列操作を行う際には、stringsパッケージの関数がよく利用されます。
  • []byte: バイトのシーケンスであり、ファイルの内容、ネットワークからのデータ、ハッシュ値など、バイナリデータを扱う際に使用されます。可変であるため、要素の変更や追加が可能です。バイトスライス操作を行う際には、bytesパッケージの関数がよく利用されます。

stringsパッケージとbytesパッケージ

Go言語の標準ライブラリには、文字列とバイトスライスを操作するための専用パッケージが用意されています。

  • stringsパッケージ: 文字列(string型)を操作するためのユーティリティ関数を提供します。例としては、部分文字列の検索 (Index, Contains), 置換 (Replace), 分割 (Split), 大文字・小文字変換 (ToUpper, ToLower) などがあります。
  • bytesパッケージ: バイトスライス([]byte型)を操作するためのユーティリティ関数を提供します。stringsパッケージと類似した機能が多く、例えばバイトスライスの検索 (Index, IndexByte), 置換 (Replace), 分割 (Split) などがあります。

Index関数とIndexByte関数

  • Index(s, sep string) int: stringsパッケージの関数で、文字列s内に部分文字列sepが最初に現れるインデックスを返します。見つからない場合は-1を返します。
  • IndexByte(s []byte, c byte) int: bytesパッケージの関数で、バイトスライスs内にバイトcが最初に現れるインデックスを返します。見つからない場合は-1を返します。

このコミットの変更前は、strings.Indexは部分文字列(string)を引数にとるため、単一バイトを検索する場合でもstring(c)のように型変換が必要でした。このコミットは、stringsパッケージにもIndexByteという、単一バイトを直接引数にとる関数を追加することで、この不便さを解消しようとしています。

技術的詳細

このコミットの技術的なポイントは、以下の2点に集約されます。

  1. strings.IndexByte関数の新規追加: stringsパッケージにIndexByte(s string, c byte) intという新しい関数が追加されました。この関数は、与えられた文字列sの中から、指定されたバイトcが最初に現れるインデックスを返します。もしcが見つからない場合は-1を返します。 この関数の実装は非常にシンプルで、文字列sを先頭から1バイトずつ走査し、cと一致するバイトが見つかった時点でそのインデックスを返します。最後まで見つからなければ-1を返します。これは、既存のbytes.IndexByteと全く同じロジックです。

  2. strings.Index関数のリファクタリング: 既存のstrings.Index関数は、部分文字列sepの長さに応じて異なる処理を行っていました。特に、sepの長さが1の場合(つまり、単一バイトの検索の場合)には、内部でループを使って1バイトずつ比較する最適化されたロジックを持っていました。 このコミットでは、sepの長さが1の場合の処理を、新しく追加されたstrings.IndexByte関数に委譲するように変更されました。具体的には、sep[0]sepの最初のバイト)を引数としてIndexByteを呼び出す形になります。

    変更前:

    case n == 1:
        c := sep[0]
        // special case worth making fast
        for i := 0; i < len(s); i++ {
            if s[i] == c {
                return i
            }
        }
        return -1
    

    変更後:

    case n == 1:
        return IndexByte(s, sep[0])
    

この変更による主なメリットは以下の通りです。

  • APIの一貫性: bytes.IndexByteと対になるstrings.IndexByteが提供されることで、Go言語の標準ライブラリ全体でのAPI設計の一貫性が向上します。開発者は、文字列とバイトスライスのどちらを扱っているかにかかわらず、単一バイトの検索にはIndexByteを使うという統一された認識を持つことができます。
  • コードの重複排除と保守性の向上: strings.Index内の単一バイト検索ロジックがstrings.IndexByteとして独立した関数になったことで、コードの重複が排除され、保守性が向上します。もし将来的に単一バイト検索のアルゴリズムを改善する必要が生じた場合、IndexByte関数だけを変更すればよく、Index関数とIndexByte関数の両方を修正する必要がなくなります。
  • 可読性の向上: strings.Indexの内部でIndexByteを呼び出すことで、コードの意図がより明確になります。「部分文字列が単一バイトの場合は、単一バイト検索関数に処理を委譲する」というロジックが、より分かりやすくなります。
  • パフォーマンスへの影響: この変更は、既存の最適化された単一バイト検索ロジックを新しい関数に移動しただけであり、アルゴリズム自体は変更されていません。したがって、パフォーマンスへの大きな影響はほとんどありません。むしろ、関数呼び出しのオーバーヘッドはごくわずかであり、コードの整理によるメリットの方が大きいと考えられます。

コアとなるコードの変更箇所

変更はsrc/pkg/strings/strings.goファイルで行われています。

  1. IndexByte関数の追加:

    // IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
    func IndexByte(s string, c byte) int {
        for i := 0; i < len(s); i++ {
            if s[i] == c {
                return i
            }
        }
        return -1
    }
    
  2. Index関数の変更: Index関数の内部で、sepの長さが1の場合の処理が変更されています。

    --- a/src/pkg/strings/strings.go
    +++ b/src/pkg/strings/strings.go
    @@ -130,14 +130,7 @@ func Index(s, sep string) int {
     	case n == 0:
     		return 0
     	case n == 1:
    -		c := sep[0]
    -		// special case worth making fast
    -		for i := 0; i < len(s); i++ {
    -			if s[i] == c {
    -				return i
    -			}
    -		}
    -		return -1
    +		return IndexByte(s, sep[0])
     	case n == len(s):
     		if sep == s {
     			return 0
    

コアとなるコードの解説

IndexByte関数の実装

// IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
func IndexByte(s string, c byte) int {
    for i := 0; i < len(s); i++ { // 文字列sの各バイトをインデックスiでループ
        if s[i] == c { // 現在のバイトs[i]が検索対象のバイトcと一致するかチェック
            return i // 一致した場合、そのインデックスを返す
        }
    }
    return -1 // ループが終了しても見つからなかった場合、-1を返す
}

このIndexByte関数は、Go言語における基本的な文字列(バイトスライス)の走査と要素比較のパターンを示しています。len(s)で文字列の長さを取得し、forループで0からlen(s)-1までインデックスiを増やしながら各バイトにアクセスします。s[i]は文字列si番目のバイトを表します。これは非常に効率的な線形探索アルゴリズムです。

Index関数の変更点

case n == 1: // 検索対象の部分文字列sepの長さが1の場合
    return IndexByte(s, sep[0]) // 新しく追加されたIndexByte関数を呼び出す

変更前のIndex関数では、sepの長さが1の場合に、IndexByte関数と全く同じロジック(forループによる線形探索)がインラインで記述されていました。この変更により、その重複したロジックがIndexByte関数にカプセル化され、Index関数からはその関数を呼び出す形になりました。

sep[0]は、長さ1の文字列sepの最初の(そして唯一の)バイトを表します。Go言語では、文字列はバイトのシーケンスとして扱われるため、sep[0]のようにインデックスでアクセスすると、その位置のバイト値(byte型)が得られます。このバイト値がIndexByte関数の第2引数cとして渡されます。

この変更は、機能的には全く同じ動作をしますが、コードの構造と保守性を大幅に改善します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • GitHubのGoリポジトリのコミット履歴
  • Go言語の文字列とバイトスライスに関する一般的な知識
  • コミットメッセージに記載された情報
  • Go言語のコードレビューシステム (Gerrit) の変更セット (CL 12214044)

[インデックス 16985] ファイルの概要

このコミットは、Go言語の標準ライブラリであるstringsパッケージにIndexByte関数を追加し、既存のIndex関数が単一バイトの検索を行う際に新しく追加されたIndexByte関数を利用するように変更するものです。これにより、bytesパッケージとの一貫性が向上し、開発者が文字列とバイトスライスの両方で同様の検索操作を行う際の利便性が高まります。

コミット

commit 9742003ffc7fd72ce2b433e9895ecbb6d9e4c720
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Aug 1 11:17:26 2013 -0700

    strings: add IndexByte, for consistency with bytes package
    
    I always forget which package has it.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/12214044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/9742003ffc7fd72ce2b433e9895ecbb6d9e4c720

元コミット内容

strings: add IndexByte, for consistency with bytes package I always forget which package has it.

このコミットの目的は、stringsパッケージにIndexByte関数を追加することです。これは、bytesパッケージに既に同様の機能が存在するため、両パッケージ間での一貫性を保つためです。コミットメッセージにある「いつもどちらのパッケージにあるか忘れてしまう」という記述は、開発者にとっての使いやすさ、特にAPIの一貫性の重要性を示唆しています。

変更の背景

Go言語の標準ライブラリには、文字列操作を行うstringsパッケージと、バイトスライス操作を行うbytesパッケージがあります。これらのパッケージは、それぞれ異なるデータ型(string[]byte)を扱うものの、多くの類似した機能を提供しています。例えば、部分文字列やバイトの検索、置換、分割などです。

このコミット以前は、特定のバイト(byte型)がバイトスライス内に最初に現れるインデックスを検索する関数としてbytes.IndexByteが存在していました。しかし、文字列(string型)に対して同様の単一バイト検索を行う直接的な関数はstringsパッケージにはありませんでした。strings.Index関数は存在しましたが、これは部分文字列(string型)を検索するものであり、単一バイトを検索する場合にはstrings.Index(s, string(c))のように型変換が必要でした。

このような状況は、開発者にとって混乱の原因となることがありました。特に、文字列とバイトスライスの間で頻繁に変換を行うようなコードを書く場合、どちらのパッケージにどの関数があるのかを常に意識する必要がありました。コミットメッセージにある「I always forget which package has it.」という一文は、この開発者の不便さを端的に表しています。

この変更の背景には、Go言語の標準ライブラリ全体でAPIの一貫性を高め、開発者の認知負荷を軽減するという設計思想があります。bytes.IndexByteに対応するstrings.IndexByteを追加することで、文字列とバイトスライスの両方で、単一バイトの検索という共通の操作に対して統一されたインターフェースを提供することが可能になります。これにより、開発者はデータ型に応じて異なる関数名を覚える必要がなくなり、より直感的でエラーの少ないコードを書けるようになります。

前提知識の解説

Go言語のstring型と[]byte

Go言語において、string型は不変のバイトスライスとして扱われます。これはUTF-8エンコードされたテキストを表すために使用されます。一方、[]byte型は可変のバイトスライスであり、任意のバイナリデータを扱うのに適しています。

  • string: 文字列リテラルや、UTF-8エンコードされたテキストデータを扱う際に使用されます。不変であるため、一度作成された文字列の内容を変更することはできません。文字列操作を行う際には、stringsパッケージの関数がよく利用されます。
  • []byte: バイトのシーケンスであり、ファイルの内容、ネットワークからのデータ、ハッシュ値など、バイナリデータを扱う際に使用されます。可変であるため、要素の変更や追加が可能です。バイトスライス操作を行う際には、bytesパッケージの関数がよく利用されます。

stringsパッケージとbytesパッケージ

Go言語の標準ライブラリには、文字列とバイトスライスを操作するための専用パッケージが用意されています。

  • stringsパッケージ: 文字列(string型)を操作するためのユーティリティ関数を提供します。例としては、部分文字列の検索 (Index, Contains), 置換 (Replace), 分割 (Split), 大文字・小文字変換 (ToUpper, ToLower) などがあります。
  • bytesパッケージ: バイトスライス([]byte型)を操作するためのユーティリティ関数を提供します。stringsパッケージと類似した機能が多く、例えばバイトスライスの検索 (Index, IndexByte), 置換 (Replace), 分割 (Split`) などがあります。

Index関数とIndexByte関数

  • Index(s, sep string) int: stringsパッケージの関数で、文字列s内に部分文字列sepが最初に現れるインデックスを返します。見つからない場合は-1を返します。
  • IndexByte(s []byte, c byte) int: bytesパッケージの関数で、バイトスライスs内にバイトcが最初に現れるインデックスを返します。見つからない場合は-1を返します。

このコミットの変更前は、strings.Indexは部分文字列(string)を引数にとるため、単一バイトを検索する場合でもstring(c)のように型変換が必要でした。このコミットは、stringsパッケージにもIndexByteという、単一バイトを直接引数にとる関数を追加することで、この不便さを解消しようとしています。

技術的詳細

このコミットの技術的なポイントは、以下の2点に集約されます。

  1. strings.IndexByte関数の新規追加: stringsパッケージにIndexByte(s string, c byte) intという新しい関数が追加されました。この関数は、与えられた文字列sの中から、指定されたバイトcが最初に現れるインデックスを返します。もしcが見つからない場合は-1を返します。 この関数の実装は非常にシンプルで、文字列sを先頭から1バイトずつ走査し、cと一致するバイトが見つかった時点でそのインデックスを返します。最後まで見つからなければ-1を返します。これは、既存のbytes.IndexByteと全く同じロジックです。

  2. strings.Index関数のリファクタリング: 既存のstrings.Index関数は、部分文字列sepの長さに応じて異なる処理を行っていました。特に、sepの長さが1の場合(つまり、単一バイトの検索の場合)には、内部でループを使って1バイトずつ比較する最適化されたロジックを持っていました。 このコミットでは、sepの長さが1の場合の処理を、新しく追加されたstrings.IndexByte関数に委譲するように変更されました。具体的には、sep[0]sepの最初のバイト)を引数としてIndexByteを呼び出す形になります。

    変更前:

    case n == 1:
        c := sep[0]
        // special case worth making fast
        for i := 0; i < len(s); i++ {
            if s[i] == c {
                return i
            }
        }
        return -1
    

    変更後:

    case n == 1:
        return IndexByte(s, sep[0])
    

この変更による主なメリットは以下の通りです。

  • APIの一貫性: bytes.IndexByteと対になるstrings.IndexByteが提供されることで、Go言語の標準ライブラリ全体でのAPI設計の一貫性が向上します。開発者は、文字列とバイトスライスのどちらを扱っているかにかかわらず、単一バイトの検索にはIndexByteを使うという統一された認識を持つことができます。
  • コードの重複排除と保守性の向上: strings.Index内の単一バイト検索ロジックがstrings.IndexByteとして独立した関数になったことで、コードの重複が排除され、保守性が向上します。もし将来的に単一バイト検索のアルゴリズムを改善する必要が生じた場合、IndexByte関数だけを変更すればよく、Index関数とIndexByte関数の両方を修正する必要がなくなります。
  • 可読性の向上: strings.Indexの内部でIndexByteを呼び出すことで、コードの意図がより明確になります。「部分文字列が単一バイトの場合は、単一バイト検索関数に処理を委譲する」というロジックが、より分かりやすくなります。
  • パフォーマンスへの影響: この変更は、既存の最適化された単一バイト検索ロジックを新しい関数に移動しただけであり、アルゴリズム自体は変更されていません。したがって、パフォーマンスへの大きな影響はほとんどありません。むしろ、関数呼び出しのオーバーヘッドはごくわずかであり、コードの整理によるメリットの方が大きいと考えられます。

コアとなるコードの変更箇所

変更はsrc/pkg/strings/strings.goファイルで行われています。

  1. IndexByte関数の追加:

    // IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
    func IndexByte(s string, c byte) int {
        for i := 0; i < len(s); i++ {
            if s[i] == c {
                return i
            }
        }
        return -1
    }
    
  2. Index関数の変更: Index関数の内部で、sepの長さが1の場合の処理が変更されています。

    --- a/src/pkg/strings/strings.go
    +++ b/src/pkg/strings/strings.go
    @@ -130,14 +130,7 @@ func Index(s, sep string) int {
     	case n == 0:
     		return 0
     	case n == 1:
    -		c := sep[0]
    -		// special case worth making fast
    -		for i := 0; i < len(s); i++ {
    -			if s[i] == c {
    -				return i
    -			}
    -		}
    -		return -1
    +		return IndexByte(s, sep[0])
     	case n == len(s):
     		if sep == s {
     			return 0
    

コアとなるコードの解説

IndexByte関数の実装

// IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s.
func IndexByte(s string, c byte) int {
    for i := 0; i < len(s); i++ { // 文字列sの各バイトをインデックスiでループ
        if s[i] == c { // 現在のバイトs[i]が検索対象のバイトcと一致するかチェック
            return i // 一致した場合、そのインデックスを返す
        }
    }
    return -1 // ループが終了しても見つからなかった場合、-1を返す
}

このIndexByte関数は、Go言語における基本的な文字列(バイトスライス)の走査と要素比較のパターンを示しています。len(s)で文字列の長さを取得し、forループで0からlen(s)-1までインデックスiを増やしながら各バイトにアクセスします。s[i]は文字列si番目のバイトを表します。これは非常に効率的な線形探索アルゴリズムです。

Index関数の変更点

case n == 1: // 検索対象の部分文字列sepの長さが1の場合
    return IndexByte(s, sep[0]) // 新しく追加されたIndexByte関数を呼び出す

変更前のIndex関数では、sepの長さが1の場合に、IndexByte関数と全く同じロジック(forループによる線形探索)がインラインで記述されていました。この変更により、その重複したロジックがIndexByte関数にカプセル化され、Index関数からはその関数を呼び出す形になりました。

sep[0]は、長さ1の文字列sepの最初の(そして唯一の)バイトを表します。Go言語では、文字列はバイトのシーケンスとして扱われるため、sep[0]のようにインデックスでアクセスすると、その位置のバイト値(byte型)が得られます。このバイト値がIndexByte関数の第2引数cとして渡されます。

この変更は、機能的には全く同じ動作をしますが、コードの構造と保守性を大幅に改善します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • GitHubのGoリポジトリのコミット履歴
  • Go言語の文字列とバイトスライスに関する一般的な知識
  • コミットメッセージに記載された情報
  • Go言語のコードレビューシステム (Gerrit) の変更セット (CL 12214044)