Skip to content

Commit

Permalink
Add Array.sort_stable and Array.sort_custom_stable
Browse files Browse the repository at this point in the history
Implemented stable sorting for Arrays using merge sort.
  • Loading branch information
aaronp64 committed Aug 24, 2024
1 parent 568589c commit e9bc5b0
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 3 deletions.
36 changes: 36 additions & 0 deletions core/templates/sort_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,42 @@ class SortArray {
}
introselect(p_first, p_nth, p_last, p_array, bitlog(p_last - p_first) * 2);
}

inline void merge(T *p_src, int64_t p_mid, int64_t p_len, T *p_dst) {
int64_t i1 = 0, i2 = p_mid, idst = 0;
while (idst < p_len) {
if (i1 == p_mid) {
p_dst[idst++] = p_src[i2++];
} else if (i2 == p_len) {
p_dst[idst++] = p_src[i1++];
} else if (compare(p_src[i2], p_src[i1])) {
p_dst[idst++] = p_src[i2++];
} else {
p_dst[idst++] = p_src[i1++];
}
}
}

inline void merge_sort(T *p_src, T *p_dst, int64_t p_len) {
if (p_len > 1) {
int64_t len1 = p_len / 2;
int64_t len2 = p_len - len1;
merge_sort(p_dst, p_src, len1);
merge_sort(p_dst + len1, p_src + len1, len2);
merge(p_src, len1, p_len, p_dst);
}
}

inline void merge_sort(T *p_array, int64_t p_len) {
if (p_len > 1) {
T *copy = memnew_arr(T, p_len);
for (int i = 0; i < p_len; i++) {
copy[i] = p_array[i];
}
merge_sort(copy, p_array, p_len);
memdelete_arr(copy);
}
}
};

#endif // SORT_ARRAY_H
12 changes: 12 additions & 0 deletions core/variant/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -632,11 +632,23 @@ void Array::sort() {
_p->array.sort_custom<_ArrayVariantSort>();
}

void Array::sort_stable() {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
SortArray<Variant, _ArrayVariantSort> sort{ _ArrayVariantSort() };
sort.merge_sort(_p->array.ptrw(), size());
}

void Array::sort_custom(const Callable &p_callable) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
_p->array.sort_custom<CallableComparator, true>(p_callable);
}

void Array::sort_custom_stable(const Callable &p_callable) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
SortArray<Variant, CallableComparator> sort{ CallableComparator{ p_callable } };
sort.merge_sort(_p->array.ptrw(), size());
}

void Array::shuffle() {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
const int n = _p->array.size();
Expand Down
2 changes: 2 additions & 0 deletions core/variant/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ class Array {
Variant pick_random() const;

void sort();
void sort_stable();
void sort_custom(const Callable &p_callable);
void sort_custom_stable(const Callable &p_callable);
void shuffle();
int bsearch(const Variant &p_value, bool p_before = true) const;
int bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before = true) const;
Expand Down
2 changes: 2 additions & 0 deletions core/variant/variant_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2297,7 +2297,9 @@ static void _register_variant_builtin_methods_array() {
bind_method(Array, pop_front, sarray(), varray());
bind_method(Array, pop_at, sarray("position"), varray());
bind_method(Array, sort, sarray(), varray());
bind_method(Array, sort_stable, sarray(), varray());
bind_method(Array, sort_custom, sarray("func"), varray());
bind_method(Array, sort_custom_stable, sarray("func"), varray());
bind_method(Array, shuffle, sarray(), varray());
bind_method(Array, bsearch, sarray("value", "before"), varray(true));
bind_method(Array, bsearch_custom, sarray("value", "func", "before"), varray(true));
Expand Down
54 changes: 51 additions & 3 deletions doc/classes/Array.xml
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@
GD.Print(numbers); // Prints [2.5, 5, 8, 10]
[/csharp]
[/codeblocks]
[b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that equivalent elements (such as [code]2[/code] and [code]2.0[/code]) may have their order changed when calling [method sort].
[b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that equivalent elements (such as [code]2[/code] and [code]2.0[/code]) may have their order changed when calling [method sort]. See also [method sort_stable]
</description>
</method>
<method name="sort_custom">
Expand All @@ -728,7 +728,7 @@
print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9]]

# Sort descending, using a lambda function.
my_items.sort_custom(func(a, b): return a[0] &gt; b[0])
my_items.sort_custom(func(a, b): return a[1] &gt; b[1])
print(my_items) # Prints [["Apple", 9], ["Tomato", 5], ["Rice", 4]]
[/codeblock]
It may also be necessary to use this method to sort strings by natural order, with [method String.naturalnocasecmp_to], as in the following example:
Expand All @@ -738,10 +738,58 @@
print(files) # Prints ["newfile1", "newfile2", "newfile10", "newfile11"]
[/codeblock]
[b]Note:[/b] In C#, this method is not supported.
[b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when calling this method.
[b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when calling this method. See also [method sort_custom_stable]
[b]Note:[/b] You should not randomize the return value of [param func], as the heapsort algorithm expects a consistent result. Randomizing the return value will result in unexpected behavior.
</description>
</method>
<method name="sort_custom_stable">
<return type="void" />
<param index="0" name="func" type="Callable" />
<description>
Sorts the array using a [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url] sort algorithm with a custom [Callable]. Equal elements remain in the same order after sorting.
[param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]behind[/i] the second one, otherwise it should return [code]false[/code].
[codeblock]
func sort_ascending(a, b):
if a[1] &lt; b[1]:
return true
return false

func _ready():
var my_items = [["Tomato", 5], ["Apple", 9], ["Rice", 4], ["Orange", 9]]
my_items.sort_custom_stable(sort_ascending)
print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9], ["Orange", 9]]

# Sort descending, using a lambda function.
my_items.sort_custom_stable(func(a, b): return a[1] &gt; b[1])
print(my_items) # Prints [["Apple", 9], ["Orange", 9], ["Tomato", 5], ["Rice", 4]]
[/codeblock]
It may also be necessary to use this method to sort strings by natural order, with [method String.naturalnocasecmp_to], as in the following example:
[codeblock]
var files = ["newfile1", "newfile2", "newfile10", "newfile11"]
files.sort_custom_stable(func(a, b): return a.naturalnocasecmp_to(b) &lt; 0)
print(files) # Prints ["newfile1", "newfile2", "newfile10", "newfile11"]
[/codeblock]
[b]Note:[/b] In C#, this method is not supported.
</description>
</method>
<method name="sort_stable">
<return type="void" />
<description>
Sorts the array in ascending order using a [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url] sort algorithm. The final order is dependent on the "less than" ([code]&lt;[/code]) comparison between elements. Equal elements remain in the same order after sorting.
[codeblocks]
[gdscript]
var numbers = [10, 5, 2.5, 8, 8.0]
numbers.sort_stable()
print(numbers) # Prints [2.5, 5, 8, 8.0, 10]
[/gdscript]
[csharp]
var numbers = new Godot.Collections.Array { 10, 5, 2.5, 8, 8.0 };
numbers.SortStable();
GD.Print(numbers); // Prints [2.5, 5, 8, 8.0, 10]
[/csharp]
[/codeblocks]
</description>
</method>
</methods>
<operators>
<operator name="operator !=">
Expand Down

0 comments on commit e9bc5b0

Please sign in to comment.