【问题标题】:Split array in groups of n, m, o groups or more将数组拆分为 n、m、o 组或更多组
【发布时间】:2018-02-09 11:20:14
【问题描述】:

有没有像这样拆分数组的方法?

[1, 2, 3, 4, 5, 6, 7, 8, 9].split(3, 4, 2)
#=> [[1, 2, 3],[4, 5, 6, 7],[8, 9]]

【问题讨论】:

    标签: arrays ruby split


    【解决方案1】:

    不,没有,但是您可以轻松地自己写一个。

    class Array
      def in_groups_of_n(*sizes)
       sizes.map(&method(:shift))
      end
    end
    

    例子

    arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    arr.in_groups_of_n(3, 4, 2)
    # => [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
    

    Demonstration

    如果您想要无损版本,可以使用dup 方法:

    class Array
      def in_groups_of_n(*sizes)
       duplicate = dup
       sizes.map { |size| duplicate.shift(size) }
      end
    end
    
    arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    arr.in_groups_of_n(3,4,2)
    # => [[1, 2, 3], [4, 5, 6, 7], [8, 9]
    arr
    # => [1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    Demonstration

    【讨论】:

    • shift 修改接收者。
    • 只需在方法中添加a = dup并操作a即可。
    • 我建议你只给无损版本。一般来说,除非特别要求破坏性解决方案(这在这里没有意义),否则我认为我们应该假设需要非破坏性解决方案。此外,这里很明显如何更改非破坏性版本以使其具有破坏性。
    【解决方案2】:

    带有 λ 的不可变版本:

    ▶ splitter = ->(array, *parts) do 
        parts.reduce([[], 0]) do |acc, i|
          right = acc.last + i
          [acc.first << (acc.last...right), right]
        end.first.map { |r| array[r] }
      end
    #⇒ #<Proc:0x0055ae3d9ae7c8@(pry):18 (lambda)>
    ▶ splitter.((1..9).to_a, 3, 4, 2)
    #⇒ [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
    

    【讨论】:

      【解决方案3】:

      这是一个简单的 Array 实现:

      class Array
        def multi_split(*sizes)
          r = []
          e = self.each
          sizes.each do |size|
            t = []
            size.times do
              t << e.next
            end
            r << t
          end
          r
        end
      end
      
      p [1, 2, 3, 4, 5, 6, 7, 8, 9].multi_split(3, 4, 2)
      # [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
      

      @Stefan 提到在 Enumerable 上实现它可能是有意义的:

      module Enumerable
        def multi_split(*sizes)
          Enumerator.new do |yielder|
            e = self.each
            sizes.each do |size|
              yielder << Array.new(size){ e.next }
            end
          end
        end
      end
      
      p [1, 2, 3, 4, 5, 6, 7, 8, 9].multi_split(3, 4, 2).to_a
      # [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
      

      【讨论】:

      • 为了更通用的实现,您可以yield每个组并在Enumerable上定义方法。
      • @Stefan:谢谢。我试过yielder &lt;&lt; e.take(size),但它输出[[1, 2, 3], [1, 2, 3, 4], [1, 2]]。是否有任何 Enumerable 或 Enumerable 方法可以使 n 个元素和 Advanceelp 避免 size.times 循环?
      • 不是很漂亮,但yield Array.new(size) { e.next } 可以。
      • @Stefan:已经好多了,谢谢。我惊讶地发现take 没有将内部位置向前移动。
      【解决方案4】:

      另一个选项(在拆分不等于数组大小的情况下无损

      def split_at(arr,splits)
        rest = arr.last(arr.size - splits.reduce(:+))
        enum = arr.to_enum
        splits.map do |n| 
          n.times.map { enum.next } 
        end.concat(rest.empty? ? [] : [rest])
      end
      

      然后称为

      split_at (1..9), [3,4,2]
      #=> [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
      split_at (1..22), [3,4,2]
      #=> [[1, 2, 3], [4, 5, 6, 7], [8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]]
      

      Example

      【讨论】:

      • 这是我会使用的名称。
      【解决方案5】:
      class Array
        def split_by_number(*sizes)
          sizes.each_with_object([]) { |n,a| a << [a.empty? ? 0 : a.last.sum, n] }.
                map { |start, nbr| self[start, nbr] }
        end
      end
      
      [1, 2, 3, 4, 5, 6, 7, 8, 9].split_by_number 3, 4, 2
        #=> [[1, 2, 3], [4, 5, 6, 7], [8, 9]]
      

      注意

      [3, 4, 2].each_with_object([]) { |n,a| a << [a.empty? ? 0 : a.last.sum, n] }
        #=> [[0, 3], [3, 4], [7, 2]]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-03-03
        • 1970-01-01
        • 2013-02-06
        • 2012-01-01
        • 2013-03-12
        • 2016-11-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多