关于unicode排序字符串

时间:2018-08-05 21:50:49

标签: string unicode elixir string-comparison

我有一个列表,我想按字母顺序排序,但要针对Unicode

iex(2)> ["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"] |> Enum.sort
["lubelskie", "mazowieckie", "zachodniopomorskie", "łódzkie"]
# the above is wrong, it should be:
["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"]

如何在Elixir中做到这一点?可以使用某些十六进制软件包。

3 个答案:

答案 0 :(得分:3)

处理排序的正确方法是将所有字符带到decomposed unicode form并进行排序。问题是由于某些原因"ł"不被认为是组合形式:

letters
|> Enum.map(&:unicode.characters_to_nfd_binary/1)
|> Enum.map(&String.codepoints/1)
#⇒ [
#  ["a"],
#  ["a", "̨"],
#  ["b"],
#  ["c"],
#  ["c", "́"],
#  ["d"],
#  ["e"],
#  ["e", "̨"],
#  ["f"],
#  ["g"],
#  ["h"],
#  ["i"],
#  ["j"],
#  ["k"],
#  ["l"],
#  ["ł"],
#  ["m"],
#  ["n"],
#  ["n", "́"],
#  ["o"],
#  ["o", "́"],
#  ["p"],
#  ["q"],
#  ["r"],
#  ["s"],
#  ["s", "́"],
#  ["t"],
#  ["u"],
#  ["w"],
#  ["y"],
#  ["z"],
#  ["z", "́"],
#  ["z", "̇"]
# ]

我不知道为什么"ł"不被声明为组合字母,我也认为这是联盟论文中的错误。无论如何,我们可能会愚弄分拣机:

["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"]
|> Enum.map(&:unicode.characters_to_nfd_binary/1)
|> Enum.map(&String.replace(&1, "ł", "l�"))
|> Enum.sort()
|> Enum.map(&String.replace(&1, "l�", "ł"))
#⇒ ["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"]

现在,它可以处理合成和分解的任何输入。

答案 1 :(得分:0)

  

远非完美,但行之有效。

这对我不起作用:

my.exs:

defmodule Stuff do
  def numeric_for_sort(string) do
    letters = ["a", "ą", "b", "c", "ć", "d", "e", "ę", "f", "g", "h", "i", "j", "k", "l", "ł",
               "m", "n", "ń", "o", "ó", "p", "q", "r", "s", "ś", "t", "u", "w", "y", "z", "ź", "ż"]
    String.graphemes(string)
    |> Enum.map(fn(x) -> Enum.find_index(letters, fn(y) -> x == y end) end)
  end
end

^C~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)>  Enum.sort(["lubelskie", "mazowieckie", "zachodniopomorskie", "łódzkie"], &(Stuff.numeric_for_sort(&1["name"]) <= Stuff.numeric_for_sort(&2["name"])))
** (FunctionClauseError) no function clause matching in Access.get/3    

    The following arguments were given to Access.get/3:

        # 1
        "lubelskie"

        # 2
        "name"

        # 3
        nil

    (elixir) lib/access.ex:306: Access.get/3
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (stdlib) erl_eval.erl:878: :erl_eval.expr_list/6
    (stdlib) erl_eval.erl:404: :erl_eval.expr/5
    (stdlib) erl_eval.erl:469: :erl_eval.expr/5
    (stdlib) lists.erl:969: :lists.sort/2


(FunctionClauseError) no function clause matching in Access.get/3`. 

而且,我不希望您使用字母列表,因为那样一来您就必须不断遍历列表以搜索字母。那就是地图的用途。 (编辑:嗯,我知道的是:small maps是地图上<= 31个条目的有序列表)所以,像这样:

letters = ["a", "ą", "b", "c", "ć", "d", "e", "ę", "f", "g", "h", "i", "j", "k", "l", "ł",
           "m", "n", "ń", "o", "ó", "p", "q", "r", "s", "ś", "t", "u", "w", "y", "z", "ź", "ż"]
letter_rank = Map.new Enum.with_index letters
String.graphemes(string)
|> Enum.map(fn(x) -> letter_rank[x] end)

然后:

names = ["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"] 
["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"]

iex(2)> Enum.sort_by names, &Stuff.numeric_for_sort/1
["lubelskie", "łódzkie", "mazowieckie", "zachodniopomorskie"]

iex(3)> 

根据Enum.sort_by/3文档:

  

sort_by / 3与sort / 2的不同之处在于,它仅计算一次可枚举中每个元素的比较值,而不是   每个比较中的每个元素一次。如果相同的功能是   在两个元素上都被调用,使用起来也更紧凑   sort_by / 3。

排序时进行了很多比较,对于排序算法进行的每次比较,一遍又一遍地计算每个名称的数字列表显然不理想。

请注意,即使这一行:

Enum.sort_by names, &Stuff.numeric_for_sort/1

看起来它在调用sort_by / 2,实际上是在使用默认的第三个参数&<=/2调用sort_by / 3。

答案 2 :(得分:-1)

到目前为止,由于使用的字母定义明确,我最终创建了自己的排序功能:

  defp numeric_for_sort(string) do
    letters = ["a", "ą", "b", "c", "ć", "d", "e", "ę", "f", "g", "h", "i", "j", "k", "l", "ł",
               "m", "n", "ń", "o", "ó", "p", "q", "r", "s", "ś", "t", "u", "w", "y", "z", "ź", "ż"]
    String.graphemes(string)
    |> Enum.map(fn(x) -> Enum.find_index(letters, fn(y) -> x == y end) end)
  end

然后

Enum.sort(["lubelskie", "mazowieckie", "zachodniopomorskie", "łódzkie"], &(numeric_for_sort(&1["name"]) <= numeric_for_sort(&2["name"])))

远非完美,但行之有效。