24

仕様によれば、ハッシュのキーとして使用される文字列は複製されて凍結されます。他の可変オブジェクトは、そのような特別な考慮事項を持っていないようです。たとえば、配列キーを使用すると、次のことが可能になります。

a = [0]
h = {a => :a}
h.keys.first[0] = 1
h # => {[1] => :a}
h[[1]] # => nil
h.rehash
h[[1]] # => :a

一方、文字列キーでは同様のことはできません。

s = "a"
h = {s => :s}
h.keys.first.upcase! # => RuntimeError: can't modify frozen String

ハッシュキーに関して、文字列が他の可変オブジェクトと異なるように設計されているのはなぜですか?この仕様が役立つユースケースはありますか?この仕様には他にどのような影響がありますか?


私は実際に、文字列に関するそのような特別な仕様がないことが役立つ場合があるユースケースを持っています。つまりyaml、ハッシュを記述した手動で記述されたYAMLファイルをgemで読み取りました。キーは文字列である可能性があり、元のYAMLファイルで大文字と小文字を区別しないようにしたいと思います。ファイルを読み取ると、次のようなハッシュが表示される場合があります。

h = {"foo" => :foo, "Bar" => :bar, "BAZ" => :baz}

そして、これを取得するために、キーを小文字に正規化したい:

h = {"foo" => :foo, "bar" => :bar, "baz" => :baz}

このようなことをすることによって:

h.keys.each(&:downcase!)

しかし、それは上記の理由でエラーを返します。

4

5 に答える 5

22

要するに、それはただRubyが素晴らしくしようとしているだけです。

キーがハッシュに入力されるhashと、キーの方法を使用して特別な番号が計算されます。Hashオブジェクトは、この番号を使用してキーを取得します。たとえば、の値を尋ねるとh['a']、ハッシュはhash文字列'a'のメソッドを呼び出し、その数値に値が格納されているかどうかを確認します。この問題は、誰か(あなた)が文字列オブジェクトを変更したときに発生するため、文字列「a」は別のものになります。たとえば、「aa」としましょう。ハッシュは「aa」のハッシュ番号を見つけられませんでした。

ハッシュの最も一般的なタイプのキーは、文字列、記号、整数です。記号と整数は不変ですが、文字列は不変ではありません。Rubyは、文字列キーを複製してフリーズすることにより、上記の混乱した動作からユーザーを保護しようとします。他のタイプでは、パフォーマンスに厄介な副作用が発生する可能性があるため、これは行われていないと思います(大きなアレイを考えてみてください)。

于 2012-10-24T10:34:27.930 に答える
4

説明については、ruby-coreメーリングリストのこのスレッドを参照してください(不思議なことに、メールアプリでメーリングリストを開いたときに偶然見つけた最初のメールでした!)。

あなたの質問の最初の部分についてはわかりませんが、h2番目の部分の実際的な答えは次のとおりです。

  new_hash = {}
  h.each_pair do |k,v|
   new_hash.merge!({k.downcase => v}) 
  end

  h.replace new_hash

この種のコードには多くの順列がありますが、

  Hash[ h.map{|k,v| [k.downcase, v] } ]

別のものであること(そしてあなたはおそらくこれらに気づいていますが、時には実用的なルートを取るのが最善です:)

于 2012-10-24T09:41:05.920 に答える
4

不変キーは、ハッシュコードが安定しているため、一般的に意味があります。

これが、MRIコードのこの部分で文字列が特別に変換される理由です。

if (RHASH(hash)->ntbl->type == &identhash || rb_obj_class(key) != rb_cString) {
  st_insert(RHASH(hash)->ntbl, key, val);
}
else {
  st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key);
}

一言で言えば、文字列キーの場合、st_insert2重複とフリーズをトリガーする関数へのポインタが渡されます。

したがって、理論的には不変のリストと不変のハッシュをハッシュキーとしてサポートしたい場合は、そのコードを次のように変更できます。

VALUE key_klass;
key_klass = rb_obj_class(key);
if (key_klass == rb_cArray || key_klass == rb_cHash) {
  st_insert2(RHASH(hash)->ntbl, key, val, freeze_obj);
}
else if (key_klass == rb_cString) {
  st_insert2(RHASH(hash)->ntbl, key, val, copy_str_key);
}
else {
  st_insert(RHASH(hash)->ntbl, key, val);
}

どこfreeze_objで定義されますか:

static st_data_t
freeze_obj(st_data_t obj)
{
    return (st_data_t)rb_obj_freeze((VALUE) obj);
}

これにより、配列キーが変更可能であるという、観察した特定の不整合が解決されます。ただし、実際に一貫性を保つには、より多くの種類のオブジェクトも不変にする必要があります。

ただし、すべてのタイプではありません。たとえば、各整数値に対応するFixnumのインスタンスは事実上1つしかないため、Fixnumのような即時オブジェクトをフリーズする意味はありません。Stringこれが、この方法で特別な場合にのみ必要な理由であり、ではFixnumありませんSymbol

文字列はハッシュキーとして非常に頻繁に使用されるため、文字列は、Rubyプログラマーの便宜のために特別な例外です。

逆に、他のオブジェクトタイプがこのようにフリーズされない理由は、明らかに一貫性のない動作につながりますが、ほとんどの場合、Matz&Companyがエッジケースをサポートしないのは便利な問題です。実際には、配列やハッシュなどのコンテナオブジェクトをハッシュキーとして使用する人は比較的少数です。したがって、そうする場合は、挿入する前にフリーズするのはあなた次第です。

非即時オブジェクトをフリーズする動作は、すべてのオブジェクトに存在するビットフィールドのFL_FREEZEビットを反転するだけなので、これは厳密にはパフォーマンスに関するものではないことに注意してください。basic.flagsもちろん、これは安価な操作です。

また、パフォーマンスについて言えば、文字列キーを使用する予定で、コードのパフォーマンスクリティカルセクションにいる場合は、挿入を行う前に文字列をフリーズすることをお勧めします。そうしないと、dupがトリガーされますが、これはより高価な操作です。

Update @sawaは、配列キーを単純にフリーズしたままにしておくと、元の配列がキー使用コンテキストの外で予期せず不変になる可能性があることを指摘しました。これは、不快な驚きでもあります(ただし、配列をハッシュキー、本当に)。したがって、dup +freezeがそれを回避する方法であると推測した場合、実際には、顕著なパフォーマンスコストが発生する可能性があります。第三に、それを完全に凍結しないままにしておくと、OPの元の奇妙さを手に入れることができます。いたるところに奇妙さ。Matzらがこれらのエッジケースをプログラマーに委ねるもう1つの理由。

于 2012-10-24T23:34:20.257 に答える
2

あなたは2つの異なる質問をします:理論的および実用的です。レインが最初に答えましたが、私があなたの実際的な質問に対する適切で怠惰な解決策と考えるものを提供したいと思います。

Hash.new { |hsh, key| # this block get's called only if a key is absent
  downcased = key.to_s.downcase
  unless downcased == key # if downcasing makes a difference
    hsh[key] = hsh[downcased] if hsh.has_key? downcased # define a new hash pair
  end # (otherways just return nil)
}

コンストラクターで使用されるブロックHash.newは、実際に要求されている欠落しているキーに対してのみ呼び出されます。上記のソリューションはシンボルも受け入れます。

于 2012-10-24T10:18:08.210 に答える
0

非常に古い質問ですが、他の誰かが質問の「ハッシュキーがフリーズする文字列を回避するにはどうすればよいですか」という質問に答えようとしている場合は...

文字列の特殊なケースを解決するために実行できる簡単なトリックは次のとおりです。

class MutableString < String
end

s = MutableString.new("a")
h = {s => :s}
h.keys.first.upcase! # => RuntimeError: can't modify frozen String
puts h.inspect

キーを作成していない限り機能しません。また、クラスが正確に「文字列」であることを厳密に要求するもので問題が発生しないように注意しない限り、機能しません。

于 2021-12-16T03:25:21.603 に答える