【问题标题】:Sorting strings with regard to unicode根据 unicode 对字符串进行排序
【发布时间】:2018-08-06 15:13:37
【问题描述】:

我有一个列表,我想按字母顺序排序,但关于 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 中实现这一点?使用一些 Hex 包是可以接受的。

【问题讨论】:

  • “关于 Unicode”到底是什么意思?是由代码单元(哪个字符集?)、代码点、一些特定于语言的映射,什么?

标签: string unicode elixir string-comparison


【解决方案1】:

处理排序的正确方法是将所有字符带到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"]

现在它可以处理任何输入,包括组合和分解。

【讨论】:

    【解决方案2】:

    远非完美,但有效。

    这对我不起作用:

    我的.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 是有序列表,其中地图有

    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,它实际上是使用默认的第三个参数 &amp;&lt;=/2 调用 sort_by/3。

    【讨论】:

    • 复制粘贴 ["lubelskie", "łódzkie"] 并感到惊讶。人们无法避免处理组合变音符号。
    【解决方案3】:

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

      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"])))
    

    远非完美,但有效。

    【讨论】:

    • 它不起作用。你完全忽略了组合的变音符号。
    • @mudasobwa 我不知道你的意思是什么
    • 如果输入是组合变音符号,即 o 后跟一个重音符号,您的排序器将失败。
    • 但它可以工作...paste.org/94426 在 100% 的情况下可能不会工作 - 你是这个意思吗?
    • 组合变音符号有 8 个字符,将我的评论复制粘贴到另一个答案。
    猜你喜欢
    • 1970-01-01
    • 2015-08-13
    • 2011-12-18
    • 1970-01-01
    • 2016-10-24
    • 1970-01-01
    • 1970-01-01
    • 2021-11-09
    • 1970-01-01
    相关资源
    最近更新 更多