Sorting a list alphabetic then numeric

Scenario
I have a list I’m getting back where I have a mixture of letters and numbers as strings. I need the list to be sorted so it’s alphabetic first THEN numeric.

# Sample data

[
    {"1",[]},
    {"A",[]},
    {"B",[]},
    {"C",[]},
    {"D",[]},
    {"E",[]},
    {"F",[]},
    {"3",[]},
    {"4",[]},
    {"5",[]},
    {"6",[]}
]

# But I want this as the final result

[
  {"A", []},
  {"B", []},
  {"C", []},
  {"D", []},
  {"E", []},
  {"F", []},
  {"1", []},
  {"3", []},
  {"4", []}
]

My solution works
This is what I came up with and it seems to work

defmodule Foo do

  @list [
    {"1",[]},
    {"A",[]},
    {"B",[]},
    {"C",[]},
    {"D",[]},
    {"E",[]},
    {"F",[]},
    {"3",[]},
    {"4",[]},
    {"5",[]},
    {"6",[]}
  ]

  def list do
    @list
  end

  def sort_alpha_then_numeric(list) do

    numbers_alpabetic_list =
      Enum.split_with(list, fn item ->
        {key, _value} = item
      case Integer.parse(key) do
        {_, ""} -> true
        :error -> false
      end
    end)

    {number_list, alpha_list} = numbers_alpabetic_list

    # We now switch it so that the order is alpabetic then numeric
    List.flatten(alpha_list, number_list)

  end
end

So if I try this out

Foo.sort_alpha_then_numeric Foo.list
# returns
[
  {"A", []},
  {"B", []},
  {"C", []},
  {"D", []},
  {"E", []},
  {"F", []},
  {"1", []},
  {"3", []},
  {"4", []}
]

Question
It works, but would like to know if my approach can be improved and learn something about sorting. Thanks.

1 Like

Take a look at Enum.sort_by.

Using function proposed by @LostKobrakai:

def sort(list), do: Enum.sort_by(list, &comparator/2)

defp comparator(<<a, _::binary>> = ax, <<b, _::binary>> = bx) when a in ?0..?9 and b in ?0..9, do: ax <= bx
defp comparator(<<a, _::binary>>, _) when a in ?0..?9, do: true
defp comparator(ax, bx), do: ax <= bx

However it is not clear how "0a" should compare with "01". I assumed that only first character decides. Otherwise you need to expand comparator/2 function a little.

EDIT: Fixed snippet, the in operator was missing.

1 Like

I’m getting this error when I copy and paste this into my Foo module. It looks like

missing :do option in "defp"

Also this syntax is foreign to me. Is this just saying that ‘b’ is expected to be a binary type?

<<b, _::binary>>

No, this is pattern matching on binaries. We are getting out first byte and then ignoring the rest _ has type binary. More info Kernel.SpecialForms.<<>>

1 Like