Skip to content

Commit 6686c4a

Browse files
committed
Merge pull request #1767 from pguyot/w29/replace-quicksort-with-mergesort
Replace quick_sort with merge_sort for lists These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 5dd7b91 + d589145 commit 6686c4a

File tree

3 files changed

+84
-10
lines changed

3 files changed

+84
-10
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- Added `lists:keysort/2`
12+
- Added `lists:merge/2,3`
1213

1314
### Fixed
1415

1516
- Fixed a bug where binary matching could fail due to a missing preservation of the matched binary.
1617

18+
### Changed
19+
20+
- lists sort function now use a stable merge sort implementation instead of quick sort
21+
1722
## [0.6.6] - 2025-06-23
1823

1924
### Added

libs/estdlib/src/lists.erl

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
join/2,
5959
seq/2, seq/3,
6060
sort/1, sort/2,
61+
merge/2, merge/3,
6162
split/2,
6263
usort/1, usort/2,
6364
duplicate/2,
@@ -650,8 +651,23 @@ sort(List) when is_list(List) ->
650651
%% @end
651652
%%-----------------------------------------------------------------------------
652653
-spec sort(Fun :: fun((T, T) -> boolean()), List :: [T]) -> [T].
653-
sort(Fun, List) when is_function(Fun), is_list(List) ->
654-
quick_sort(Fun, List).
654+
sort(Fun, List) when is_function(Fun, 2), is_list(List) ->
655+
merge_sort(Fun, List).
656+
657+
merge_sort(_Fun, []) ->
658+
[];
659+
merge_sort(_Fun, [_] = L) ->
660+
L;
661+
merge_sort(Fun, List) ->
662+
{H1, H2} = merge_sort_split(List, List, []),
663+
merge(Fun, merge_sort(Fun, H1), merge_sort(Fun, H2), []).
664+
665+
merge_sort_split([], Half1, Half2) ->
666+
{lists:reverse(Half2), Half1};
667+
merge_sort_split([_], Half1, Half2) ->
668+
{lists:reverse(Half2), Half1};
669+
merge_sort_split([_, _ | T], [H | Half1T], Half2) ->
670+
merge_sort_split(T, Half1T, [H | Half2]).
655671

656672
%%-----------------------------------------------------------------------------
657673
%% @param N elements non negative Integer
@@ -686,14 +702,39 @@ split(N, [H | T], R) ->
686702
split(_, [], _) ->
687703
badarg.
688704

689-
%% Attribution: https://erlang.org/doc/programming_examples/list_comprehensions.html#quick-sort
690-
%% @private
691-
quick_sort(Fun, [Pivot | T]) ->
692-
quick_sort(Fun, [X || X <- T, Fun(X, Pivot)]) ++
693-
[Pivot] ++
694-
quick_sort(Fun, [X || X <- T, not Fun(X, Pivot)]);
695-
quick_sort(_Fun, []) ->
696-
[].
705+
%%-----------------------------------------------------------------------------
706+
%% @param List1 first list to merge, previously sorted
707+
%% @param List2 second list to merge, previously sorted
708+
%% @returns Merged list of List1 and List2
709+
%% @doc Returns a list formed by merging List1 and List2, following natural
710+
%% order. If elements compare equal, element from List1 is picked first
711+
%% @end
712+
%%-----------------------------------------------------------------------------
713+
merge(List1, List2) ->
714+
merge(fun lt/2, List1, List2, []).
715+
716+
%%-----------------------------------------------------------------------------
717+
%% @param Fun ordering function
718+
%% @param List1 first list to merge, previously sorted
719+
%% @param List2 second list to merge, previously sorted
720+
%% @returns Merged list of List1 and List2
721+
%% @doc Returns a list formed by merging List1 and List2, following Fun
722+
%% order. If elements compare equal, element from List1 is picked first
723+
%% @end
724+
%%-----------------------------------------------------------------------------
725+
merge(Fun, List1, List2) ->
726+
merge(Fun, List1, List2, []).
727+
728+
merge(_Fun, [], Right, Acc) ->
729+
lists:reverse(Acc, Right);
730+
merge(_Fun, Left, [], Acc) ->
731+
lists:reverse(Acc, Left);
732+
merge(Fun, [A | As], [B | Bs], Acc) ->
733+
% keep sort stable, if B < A, put it first, otherwise keep A first
734+
case Fun(B, A) of
735+
true -> merge(Fun, [A | As], Bs, [B | Acc]);
736+
false -> merge(Fun, As, [B | Bs], [A | Acc])
737+
end.
697738

698739
%% @private
699740
lt(A, B) -> A < B.

tests/libs/estdlib/test_lists.erl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,34 @@ test_keysort() ->
131131
?ASSERT_MATCH(lists:keysort(2, [{foo, 2}, {bar, 1}]), [{bar, 1}, {foo, 2}]),
132132
?ASSERT_ERROR(lists:keysort(1, [1, 2])),
133133
?ASSERT_ERROR(lists:keysort(3, [{1, bar}, {2, foo}])),
134+
135+
% Our sort is always stable, but older versions of OTP only have
136+
% keysort/2 documented as stable
137+
?ASSERT_MATCH(
138+
lists:keysort(1, [
139+
{3, a}, {2, c}, {1, z}, {2, b}, {2, a}
140+
]),
141+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
142+
),
143+
?ASSERT_MATCH(
144+
lists:keysort(1, [
145+
{3, a}, {1, z}, {2, c}, {2, b}, {2, a}
146+
]),
147+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
148+
),
149+
?ASSERT_MATCH(
150+
lists:keysort(1, [
151+
{3, a}, {2, c}, {2, b}, {1, z}, {2, a}
152+
]),
153+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
154+
),
155+
?ASSERT_MATCH(
156+
lists:keysort(1, [
157+
{3, a}, {2, c}, {2, b}, {2, a}, {1, z}
158+
]),
159+
[{1, z}, {2, c}, {2, b}, {2, a}, {3, a}]
160+
),
161+
134162
ok.
135163

136164
test_keystore() ->

0 commit comments

Comments
 (0)