【问题标题】:Ruby: how to sort array of string parsing the contentRuby:如何对解析内容的字符串数组进行排序
【发布时间】:2015-07-03 09:23:27
【问题描述】:

这是我的问题:我有一个字符串数组,其中包含这样的数据:

array = ["{109}{08} OK",
         "{98} Thx",
         "{108}{0.8}{908} aa",
         "{8}{51} lorem ipsum"]

我想对扫描“内部数据”的数组进行排序:这里是大括号中的整数。所以,最终的数组应该是这样的:

array.custom_sort! => ["{8}{51} lorem ipsum",
                       "{98} Thx",
                       "{108}{0.8}{908} aa",
                       "{109}{08} OK"]

在 Ruby 中有一个很好的解决方案吗?或者我应该重新创建一个插入每个解析元素的新数组?

编辑:

我没有提到排序优先级: 一、按大括号内的数字排序,最多3组,但不能缺。

["{5}something",
 "{61}{64}could",
 "{}be",                  #raise an error or ignore it
 "{54}{31.24}{0.2}write",
 "{11}{21}{87}{65}here",  #raise an error or ignore it
 "[]or",                  #raise an error or ignore it
 "{31}not"]

如果第一个数字相等,则应比较第二个数字。 一些例子:

"{15}" < "{151}" < "{151}{32}" < "{152}"
"{1}" < "{012}" < "{12}{-1}{0}" < "{12.0}{0.2}"
"{5}" < "{5}{0}" < "{5}{0}{1}"

但是如果每个数字都相等,那么字符串就是比较的。唯一有问题的字符是空格,它必须在每个其他“可见”字符之后。 例子:

"{1}a" < "{1}aa" < "{1} a" < "{1}  a"
"{1}" < "{1}a " < "{1}a  " < "{1}a  a"
"{1}a" < "{1}ba" < "{1}b "

我可以让它在自定义类中做这样的事情:

class CustomArray
  attr_accessor :one
  attr_accessor :two
  attr_accessor :three
  attr_accessor :text 

  def <=>(other)
    if self.one.to_f < other.one.to_f
      return -1
    elsif self.one.to_f > other.one.to_f
      return 1
    elsif self.two.nil?
      if other.two.nil?
        min = [self.text, other.text].min
        i = 0
        until i == min
          if self.text[i].chr == ' ' #.chr is for compatibility with Ruby 1.8.x
            if other.text[i].chr != ' '
              return 1
            end
          else
            if other.text[i].chr == ' '
              return -1

          #...

    self.text <=> other.text
  end
end

它工作正常,但我对 Ruby 中的编码非常沮丧,就像我在 C++ 项目中编码一样。这就是为什么我想知道如何使用“foreach 方法中的自定义排序”,其排序方式比基于内容属性的简单排序方式更复杂(需要解析、扫描、正则表达式)。

【问题讨论】:

    标签: ruby arrays string parsing sorting


    【解决方案1】:

    您可以传递Array#sort 一个块来定义它应该如何对元素进行排序。

    【讨论】:

      【解决方案2】:

      应该这样做:

      array.sort_by do |s| 
        # regex match the digits within the first pair of curly braces
        s.match(/^\{(\d+)\}/)[1].to_i # convert to an int in order to sort
      end
      
      # => ["{8}{51} lorem ipsum", "{98} Thx", "{108}{0.8}{908} aa", "{109}{08} OK"]
      

      【讨论】:

        【解决方案3】:
        array.sort_by { |v| (v =~ /(\d+)/) && $1.to_i }
        

        交替

        array.sort_by { |v| /(\d+)/.match(v)[1].to_i }
        

        【讨论】:

          【解决方案4】:

          [编辑:我在此编辑之后的初始解决方案不适用于修改后的问题陈述。但是,我会留下它,因为无论如何它可能会引起人们的兴趣。

          据我了解,以下是根据修改后的规则执行排序的方法。如果我误解了规则,我预计修复将是次要的。

          使用正则表达式

          让我们从我将使用的正则表达式开始:

          R = /
              \{       # match char
              (        # begin capture group
              \d+      # match one or more digits
              (?:      # begin non-capture group
              \.       # match decimal
              \d+      # match one or more digits
              )        # end non-capture group
              |        # or
              \d*      # match zero or more digits
              )        # match end capture group
              \}       # match char
              /x
          

          例子:

          a = ["{5}something", "{61}{64}could", "{}be", "{54}{31.24}{0.2}write",
               "{11}{21}{87}{65}here", "[]or", "{31}not", "{31} cat"]
          a.each_with_object({}) { |s,h| h[s] = s.scan(R).flatten }
            # => {"{5}something"        =>["5"],
            #    "{61}{64}could"        =>["61", "64"],
            #    "{}be"                 =>[""],
            #    "{54}{31.24}{0.2}write"=>["54", "31.24", "0.2"],
            #    "{11}{21}{87}{65}here" =>["11", "21", "87", "65"],
            #    "[]or"                 =>[],
            #    "{31}not"              =>["31"]
            #    "{31} cat"             =>["31"]} 
          

          custom_sort 方法

          我们可以将方法custom_sort写成如下(将sort_by改为sort_by!custom_sort!):

          class Array
            def custom_sort
              sort_by do |s|
                a = s.scan(R).flatten
                raise SyntaxError,
                  "'#{s}' contains empty braces" if a.any?(&:empty?)
                raise SyntaxError,
                  "'#{s}' contains zero or > 3 pair of braces" if a.size.zero?||a.size > 3
                a.map(&:to_f) << s[a.join.size+2*a.size..-1].tr(' ', 255.chr)
              end
            end
          end
          

          示例

          让我们试试吧:

          a.custom_sort
            #=> SyntaxError: '{}be' contains empty braces
          

          a 中删除"{}be"

          a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write",
               "{11}{21}{87}{65}here", "[]or", "{31}not", "{31} cat"]
          a.custom_sort
            #SyntaxError: '{11}{21}{87}{65}here' contains > 3 pair of braces
          

          删除"{11}{21}{87}{65}here"

          a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write",
               "[]or", "{31}not", "{31} cat"]
          a.custom_sort
            #=> SyntaxError: '[]or' contains zero or > 3 pair of braces
          

          删除"[]or"

          a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write",
               "{31}not", "{31} cat"]
          a.custom_sort
            #=> ["{5}something",
            #    "{31}not",
            #    "{31} cat",
            #    "{54}{31.24}{0.2}write", "{61}{64}could"] 
          

          说明

          假设要排序的字符串之一是:

          s = "{54}{31.24}{0.2}write a letter"
          

          然后在sort_by 块中,我们将计算:

          a = s.scan(R).flatten
            #=> ["54", "31.24", "0.2"]
          raise SyntaxError, "..." if a.any?(&:empty?)
            #=> raise SyntaxError, "..." if false 
          raise SyntaxError, "..." if a.size.zero?||a.size > 3
            #=> SyntaxError, "..." if false || false
          b = a.map(&:to_f)
            #=> [54.0, 31.24, 0.2] 
          t = a.join
            #=> "5431.240.2" 
          n = t.size + 2*a.size
            #=> 16 
          u = s[n..-1]
            #=> "wr i te" 
          v = u.tr(' ', 255.chr)
            #=> "wr\xFFi\xFFte" 
          b << v
            #=> [54.0, 31.24, 0.2, "wr\xFFi\xFFte"] 
          

          请注意,使用String#tr(或者您可以使用String#gsub)会在ASCII 字符排序顺序的末尾放置空格:

          255.times.all? { |i| i.chr < 255.chr }
            #=> true
          

          潮]

          我假设在排序中,字符串对的比较方式类似于Array#<=>。第一个比较考虑每个字符串中第一对大括号内的数字字符串(转换为浮点数之后)。通过比较第二对大括号(转换为浮点数)中的数字字符串来打破平局。如果仍然平局,则比较括在大括号中的第三对数字,依此类推。如果一个字符串具有 n 大括号对,另一个具有 m &gt; n 对,并且大括号内的值与第一个 @ 相同987654347@ 对,我假设第一个字符串在排序中位于第二个字符串之前。

          代码

          R = /
              \{    # match char
              (\d+) # capture digits
              \}    # match char
              +     # capture one or more times
              /x
          
          class Array
            def custom_sort!
              sort_by! { |s| s.scan(R).map { |e| e.first.to_f } }
            end
          end
          

          示例

          array = ["{109}{08} OK",
                   "{109}{07} OK",
                   "{98} Thx",
                   "{108}{0.8}{908} aa",
                   "{108}{0.8}{907} aa",
                   "{8}{51} lorem ipsum"]
          
          a = array.custom_sort!
            #=> ["{8}{51} lorem ipsum",
            #    "{98} Thx",
            #    "{108}{0.8}{907} aa",
            #    "{108}{0.8}{908} aa",
            #    "{109}{07} OK",
            #    "{109}{08} OK"]
          
          array == a
            #=> true
          

          说明

          现在让我们计算Array#sort_by!的块中array的第一个元素的值

          s = "{109}{08} OK"
          
          a = s.scan(R)
            #=> [["109"], ["08"]] 
          b = a.map { |e| e.first.to_f }
            #=> [109.0, 8.0] 
          

          现在让我们对其他字符串做同样的事情并将结果放入一个数组中:

          c = array.map { |s| [s, s.scan(R).map { |e| e.first.to_f }] }
            #=> [["{8}{51} lorem ipsum", [8.0, 51.0]],
            #    ["{98} Thx",            [98.0]],
            #    ["{108}{0.8}{907} aa",  [108.0, 907.0]],
            #    ["{108}{0.8}{908} aa",  [108.0, 908.0]],
            #    ["{109}{07} OK",        [109.0, 7.0]],
            #    ["{109}{08} OK",        [109.0, 8.0]]] 
          

          custom_sort! 中的sort_by 因此等价于:

          c.sort_by(&:last).map(&:first)
            #=> ["{8}{51} lorem ipsum",
            #    "{98} Thx",
            #    "{108}{0.8}{907} aa",
            #    "{108}{0.8}{908} aa",
            #    "{109}{07} OK",
            #    "{109}{08} OK"]
          

          【讨论】:

          • 这是非常有用的解释,非常感谢!但是对于字符串比较,我忘了提一些东西。假设:["{42}foo", "{42}bar","{42} world","{42} hello"]。诀窍是比较字符串,空格字符 ' ' 应该在 字母数字之后,但在 ASCII 表中,它不是。我在另一个线程 (stackoverflow.com/questions/29808574/ruby-custom-string-sort) 中看到我可以设置自己的“字符数组”顺序。是否可以将此功能集成到您的优雅解决方案中?
          • 今天晚些时候我会看看。同时,我建议您编辑您的问题以在您的评论中添加信息(如果需要,还可以更正 108109 的排序)。因为附加信息会改变问题,所以您需要在编辑中明确说明。这通常是通过写“编辑:我没有提到...”来完成的,也许在问题的结尾。
          • 您必须更准确地解释排序标准。我假设排序顺序仅取决于大括号对中包含的值。其他给出答案的人也做出了类似的假设。那么,您希望实现哪些排序规则?
          猜你喜欢
          • 2013-05-14
          • 2014-04-23
          • 2011-03-11
          • 1970-01-01
          • 2021-07-06
          • 1970-01-01
          • 1970-01-01
          • 2021-07-19
          • 1970-01-01
          相关资源
          最近更新 更多