Skip to content

Add Hash#transform_keys!#16280

Merged
straight-shoota merged 7 commits intocrystal-lang:masterfrom
andrykonchin:ak/add-hash-transform-keys-bang
Nov 9, 2025
Merged

Add Hash#transform_keys!#16280
straight-shoota merged 7 commits intocrystal-lang:masterfrom
andrykonchin:ak/add-hash-transform-keys-bang

Conversation

@andrykonchin
Copy link
Contributor

Changes:

  • add new method Hash#transform_keys!

Related discussion - #16271 (comment).

@andrykonchin andrykonchin force-pushed the ak/add-hash-transform-keys-bang branch from 370f0d6 to f79ec79 Compare October 27, 2025 14:31
initialize_dup_entries(transformed_hash) # we need only to copy the buffer
initialize_copy_non_entries_vars(transformed_hash)
self
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably makes sense to move the copying logic into a new method #replace, similar to Hash#replace in Ruby.

@straight-shoota straight-shoota changed the title Add Hash#transform_keys! Add Hash#transform_keys! Nov 3, 2025
@andrykonchin
Copy link
Contributor Author

Does it make sense to update Set#map! (and use Hash#transform_keys!) in this PR as far as #16271 is merged? It will require rebasing on master/merging master into this branch.

@straight-shoota
Copy link
Member

Yeah, sounds good to update Set#map!. Just merge master, please 👍

@andrykonchin
Copy link
Contributor Author

@straight-shoota Updated the Set#map! method.

src/hash.cr Outdated
# hash # => {"a" => 1, "bb" => 2, "ccc" => 3}
# ```
def transform_keys!(&block : K, V -> K) : self
transformed_hash = self.transform_keys(&block)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably the block forwarding was chosen here because the yielding form wouldn't compile due to a mismatch between K2 and K. In this case I think it is acceptable to just inline #transform_keys's body here, since captured blocks have a non-negligible runtime cost:

Suggested change
transformed_hash = self.transform_keys(&block)
transformed_hash = Hash(K, V).new(initial_capacity: entries_capacity)
each_with_object(transformed_hash) do |(key, value), memo|
memo[yield(key, value)] = value
end

And then Set#map! could also use the yielding form.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean by "yielding form" this approach:

transformed_hash = self.transform_keys { |k, v| yield k, v }

then yes, it doesn't compile:

bin/crystal spec/std/hash_spec.cr
Using compiled compiler at .build/crystal
Showing last frame. Use --error-trace for full trace.

In src/pointer.cr:146:29

 146 | (self + offset).value = value
                               ^----
Error: type must be Hash::Entry(Int32 | String, String), not Hash::Entry(Int32, String)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thank you!

Copy link
Member

@straight-shoota straight-shoota Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should be fine with an explicit cast to K. No need to duplicate the implementation.

    transformed_hash = transform_keys{ |k, v| (yield k, v).as(K) }

Sorry for the back and forth Andrii, and thanks for staying around with us 🫶

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙂

Done.

@ysbaddaden ysbaddaden added this to the 1.19.0 milestone Nov 7, 2025
@straight-shoota straight-shoota merged commit 7cd3a54 into crystal-lang:master Nov 9, 2025
40 checks passed
@andrykonchin andrykonchin deleted the ak/add-hash-transform-keys-bang branch November 10, 2025 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants