Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a sort method to Dictionary and HashMap #77213

Merged
merged 1 commit into from
Oct 4, 2024

Conversation

aaronfranke
Copy link
Member

@aaronfranke aaronfranke commented May 18, 2023

Implements this proposal and closes godotengine/godot-proposals#7060

This PR adds a method to Dictionary, and internally in HashMap, to sort in-place by key. This can be used to ensure dictionaries with the same contents produce equivalent results when getting the keys, getting the values, and converting to a string. This is also useful when wanting a JSON representation consistent with what is in memory, and useful for storing on a database that requires dictionaries to be sorted.

func _ready():
	var dict = {
		"key4": "value4",
		"key2": "value2",
		"key1": "value1",
		"key3": "value3",
	}
	dict.sort()
	print(dict) # { "key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4" }

I used insertion sort because we want this operation to be fast for the common case where the input is already sorted or nearly sorted. The worst case is O(n²) but the best case is O(n).

I am planning to use this method in The Mirror, and while I could just implement in GDScript or keep it to our engine fork, this seems like it would be broadly useful to have in upstream core.

@aaronfranke aaronfranke added this to the 4.x milestone May 18, 2023
@aaronfranke aaronfranke requested review from a team as code owners May 18, 2023 18:58

#include "core/variant/variant.h"

bool _hashmap_variant_less_than(const Variant &p_left, const Variant &p_right) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Since hash_map.h does not have the complete definition of Variant, we have to put this in a .cpp file. And because as far as I can tell it's impossible to define a template class's method outside of the header, we can't put sort in the .cpp or put _hashmap_variant_less_than inside of HashMap::.

@dalexeev
Copy link
Member

Perhaps we also need an insert() method? Right now there is no way to move a key in the dictionary other than to remove it and all the keys after it and then reinsert them all.

# Probably fast (to find before_key by hash).
insert_before_key(key: Variant, value: Variant, before_key: Variant)
# Probably slow (double linked list).
insert_at_index(key: Variant, value: Variant, at_index: int)

@aaronfranke
Copy link
Member Author

aaronfranke commented May 23, 2023

@dalexeev Yeah that makes sense. It might also be sensible to have an insert without any specified before_key or at_index that would assume the Dictionary was sorted and just insert a new value.

@Riteo
Copy link
Contributor

Riteo commented May 25, 2023

@dalexeev

Perhaps we also need an insert() method?

This would be great and might be useful for my RC client too.

It might also be sensible to have an insert without any specified before_key or at_index that would assume the Dictionary was sorted and just insert a new value.

What difference would it make to Dictionary's operator []? I guess though that just having a method equivalent for it might be useful by itself, insert() would also pair up nicely with get() ig.

Copy link

@zf-moth zf-moth left a comment

Choose a reason for hiding this comment

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

I tested it and looked over the code and this looks good to me.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

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

Tested locally (rebased on top of master 9adb7c7), it works as expected. The rationale for adding a sort() method to Dictionary makes sense to me.

@AThousandShips AThousandShips self-requested a review February 7, 2024 09:10
@reduz
Copy link
Member

reduz commented Feb 7, 2024

Dictionaries use insertion order for determinism, so for the vast majority of cases, you don't need this.
If you really wanted something like this, you can do a function that takes the keys, sorts them and rebuilds the dictionary, but it seems like a very corner case functionality. I am more on the side of leaving it out.

@KoBeWi
Copy link
Member

KoBeWi commented Feb 7, 2024

Dictionaries are already auto-sorted when you serialize them to string. While it's supper inefficient, you should be able to do str_to_var(var_to_str(dict)) to sort a Dictionary.

Copy link
Member

@AThousandShips AThousandShips left a comment

Choose a reason for hiding this comment

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

Code looks good

@aaronfranke
Copy link
Member Author

aaronfranke commented Feb 7, 2024

@reduz I understand the determinism of having keys in the inserted order, but I need this for a different kind of determinism:

  • Identical data should be stored in a consistent order, so I want to sort it to ensure this.
  • Inserting in a different order and then sorting should result in the same final data.
  • Serializing to a JSON file reading back should keep the data consistent.
  • Saving to a database and reading it back should keep the data consistent.

I really don't think that this is an unreasonable use case. Perhaps Godot hasn't had this before because hobbyist projects either haven't done much advanced operations on Dictionaries or have used hacks like str_to_var(var_to_str(dict)).

@akien-mga akien-mga merged commit e7d79f2 into godotengine:master Oct 4, 2024
19 checks passed
@akien-mga
Copy link
Member

Thanks!

@akien-mga akien-mga modified the milestones: 4.x, 4.4 Oct 4, 2024
@aaronfranke aaronfranke deleted the sort-dict branch October 4, 2024 22:45
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.

Add a sort method to Dictionary
9 participants