Hi,
we are trying to sort things naturally in ExDoc.
Can anybody recommend a natural sorting algorithm implementation in Elixir?
I have a a few but I am looking for options,
Thank you.
Hi,
we are trying to sort things naturally in ExDoc.
Can anybody recommend a natural sorting algorithm implementation in Elixir?
I have a a few but I am looking for options,
Thank you.
How do you define “natural”?
Things that come to mind:
2 < 10
Mostly. Case should not be ignored, but order should be: A, a, B, b
, and not A, B, a, b
as sorting by ASCII code does.
I have this hacky solution which should work but probably isn’t optimal:
Enum.sort_by(~w(A B a b), &{String.downcase(&1), &1})
FWIW, I have proposed the custom optimized sorter: Sort items "naturally" in menu, summary, function list, etc. · Issue #1440 · elixir-lang/ex_doc · GitHub
EDIT: the implementation is correct, I had the wrong input
The problem with this approach is that it returns different results depending on the initial order for the same list shuffled.
iex(16)> Enum.sort_by(~w(A B a b), &{String.downcase(&1), &1})
["A", "a", "B", "b"]
iex(17)> Enum.sort_by(~w(a B B b), &{String.downcase(&1), &1})
["a", "B", "B", "b"]
~~Actually I have reported this very same issue to this library just a few hours ago: Issues · BinaryNoggin/natural_order · GitHub
Thank you Aleksei for your contribution to the discussion, later one with more time, I will look in detail and see how I can apply those to the NaturalOrder library. There are a few lessons to take from the discussion.
Well, this is obvious implication of the fact that Enum.sort*
functions are using stable sort algorithms.
I’m sorry if I misunderstood your comment, but this example is not using the same list shuffled, these are different lists. I believe the result provided is the expected one?
If I reuse the example from your issue it seems fine too:
iex(1)> ~W(a A b) |> Enum.sort_by(&{String.downcase(&1), &1})
["A", "a", "b"]
iex(2)> ~W(A b a) |> Enum.sort_by(&{String.downcase(&1), &1})
["A", "a", "b"]
You are right. I messed up with the input. Your implementation.
Just for reference, here is the same approach with a custom comparator. I didn’t bench these but since it doesn’t build tuples it is probably faster:
defmodule Natural do
def natural_sort(enumerable) do
Enum.sort(enumerable, __MODULE__)
end
def compare(x, y) do
case do_compare(String.upcase(x), String.upcase(y)) do
:eq -> do_compare(x, y)
lt_or_gt -> lt_or_gt
end
end
defp do_compare(x, x), do: :eq
defp do_compare(x, y) when x > y, do: :gt
defp do_compare(_, _), do: :lt
end