【问题标题】:sorting a ruby array of objects by an attribute that could be nil按可能为 nil 的属性对对象的 ruby​​ 数组进行排序
【发布时间】:2010-10-22 22:33:50
【问题描述】:

我有一个对象数组,我需要按可以是整数或 nil 的位置属性对其进行排序,并且我需要具有 nil 位置的对象位于数组的末尾。现在,我可以强制该位置返回某个值而不是 nil,这样 array.sort 就不会失败,但是如果我使用 0 作为默认值,那么它会将这些对象放在排序的前面。进行这种排序的最佳方法是什么?我是否应该将 nil 值设置为“几乎”总是保证在最后的一些可笑的高数字?还是有其他方法可以使 array.sort 方法将 nil 属性对象放在数组的末尾?代码如下所示:

class Parent
  def sorted_children
     children.sort{|a, b| a.position <=> b.position}
  end
end

class Child
  def position
    category ? category.position : #what should the else be??
  end
end

现在,如果我将“else”设为 1000000000,那么很可能会将它们放在数组的末尾,但我不喜欢这个解决方案,因为它是任意的

【问题讨论】:

    标签: ruby arrays sorting


    【解决方案1】:

    我会调整您的排序以将 nil 项目放在最后。试试这样的。

    foo = [nil, -3, 100, 4, 6, nil, 4, nil, 23]
    
    foo.sort { |a,b| a && b ? a <=> b : a ? -1 : 1 }
    
    => [-3, 4, 4, 6, 23, 100, nil, nil, nil]
    

    也就是说:如果 a 和 b 都不是 nil,则正常对它们进行排序,但如果其中一个为 nil,则返回一个排序较大的状态。

    【讨论】:

    • 同意,这应该是公认的答案。如果您乐于依赖真值和假值,则可以缩短一点:foo.sort { |a,b| a &lt;=&gt; b || (b &amp;&amp; 1) || -1 }
    • 我不想覆盖整个模型上的宇宙飞船 (!) 运算符,只是在一个特定的操作中,所以这对我的需要更好。
    【解决方案2】:

    如果category 存在,则在Child 中将&lt;=&gt; 定义为基于category.position,并且对没有category 的项目进行排序总是大于具有category 的项目?

    class Child
      # Not strictly necessary, but will define other comparisons based on <=>
      include Comparable   
      def <=> other
        return 0 if !category && !other.category
        return 1 if !category
        return -1 if !other.category
        category.position <=> other.category.position
      end
    end
    

    然后在Parent 中,您可以拨打children.sort

    【讨论】:

    • 我对此有点怀疑,因为它限制了您在不不一致的情况下对其他任何内容进行排序的能力。话虽如此,如果 position 是 parent 的自然排序顺序,那么这样做是合理的。
    • @RHSeeger 如果您检查编辑历史记录,我最初添加了一个警告,说明如果您需要按不同的标准进行排序该怎么办,但它的措辞令人困惑,我认为它没有增加太多.基本上,您可以添加其他比较方法,可以随意命名,如果需要对它们进行排序,请像原始示例一样在块中进行。
    【解决方案3】:

    我会这样处理这些事情:

     children.sort_by {|child| [child.position ? 0 : 1,child.position || 0]}
    

    【讨论】:

    • 你能再解释一下吗?当 position 为 nil 时,您似乎使用 0 作为幻数,这可能无效,因为 position 未定义为大于 0(这是一个合理的假设,但不一定是这种情况)。
    • 不,最后的 0 只是为了避免在 nil 上调用 。数组的第一个元素已经确保所有的 nil 都在所有有效位置之后。第二个元素在具有位置的事物中进行子排序。你可以在里面放一些东西来用 nils 对那些进行子排序,但是这个例子没有要求任何东西,所以我只使用了 0。它可以是 42 或“wombat”,没关系。
    • 啊,好吧,我的困惑是由于我缺乏 Ruby 知识。我是否理解正确,因为您实际上是在创建一个包含两个值的数组/列表,如果值为 nil ... 则为 1 和值,如果值为非 nil 则为 0 和值 ... 然后使用排序那对作为“排序的价值”?好主意,我只是不清楚,因为我不认识语法。
    • 对,我用两个元素的子数组表示每个孩子:如果位置不为零,则为 [0,position],如果位置为零,则为 [1,0]。 Ruby 的数组比较会按顺序比较键,因此所有以 0 开头的子数组(非零位置)都先排序,再按位置进行子排序。然后是所有具有 1 的子数组(零位置)。从技术上讲,这些 nil 然后按那些“0”进行子排序,这就是为什么 0 可能是 42 或“pie”或其他什么的原因。如果你有另一个标准来对 nil 进行子排序,它会去那里。不能把 nil 留在那里,因为它没有 方法。
    • 至少在 Ruby 1.9.3p327 中,nil &lt;=&gt; nil 返回 0。在 nil 和其他任何东西之间使用太空飞船通常返回 nil,但 nil 和它本身返回 0。结果,[1, nil, 2] &lt;=&gt; [1, nil, 3] 返回 @987654324 @。因此,只要数组中有一个较早的值(即非零与零的 0 / 1)将零值与非零值相除,那么使用零应该是安全的.
    【解决方案4】:

    老实说,我对 Ruby 不是很熟悉,所以将其更多地视为一种算法思想,而不是代码......并将 ?: 运算符重写为 Ruby 所具有的任何更简洁的操作符。

    你不能在比较中检查 nil:

    class Parent
      def sorted_children
         children.sort{|a,b|( a and b ) ? a <=> b : ( a ? -1 : 1 ) }
      end
    end
    

    编辑使用 Glenra 的代码,它实现了与我相同的东西,但代码量更小(可能更容易阅读)。

    【讨论】:

      【解决方案5】:

      对我来说最简单的解决方案是

      def sorted_children(children)
        children.sort_by { |child| child.position || -1}
      end
      

      【讨论】:

      • 这仅在数组具有正整数时有效。
      【解决方案6】:

      我已经有一段时间没有使用 Ruby 了,但是您可以将 null 检查与排序分开(只允许 Child#position 返回 null):

      def sorted_children
        children.reject{|c| c.position.nil?}.sort_by(&:position) +
          children.select{|c| c.position.nil?}
      end
      

      诚然,这不是最有效的解决方案,但它没有任何神奇的数字。

      【讨论】:

      • ".nil?"是多余的,因为如果 c.position 不为零,则 c.position 为真,但您可以将其保留以提高可读性。我喜欢!
      • 我想在这种情况下是正确的,因为“位置”可能是一个数字,但是由于 Ruby 有 2 个“假”值(nil 和 false),我尽量不要假设非 nil 意味着真的。我以前也被那个咬过。 :-)
      【解决方案7】:

      您可以通过定义一个新的比较方法来做到这一点,而无需重写 spaceship 运算符。

      class Child
        include Comparable   
        def compare_by_category(other)
          return 0 if !category && !other.category
          return 1 if !category
          return -1 if !other.category
          category.position <=> other.category.position
        end
      end
      

      sort 方法可以占用一个块,因此您可以使用这个新方法进行排序:

      children.sort {|a,b| a.compare_by_category(b) }
      

      【讨论】:

        猜你喜欢
        • 2010-10-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-06
        • 2012-02-20
        • 2010-11-02
        相关资源
        最近更新 更多