【问题标题】:Reverse an array without using a loop in ruby在 ruby​​ 中不使用循环反转数组
【发布时间】:2014-09-25 20:10:00
【问题描述】:

我有一个编码挑战来反转一个包含 5 个元素的数组。如果不使用反向方法,我将如何做到这一点?

代码:

def reverse(array)
 array
end

p reverse(["a", 1, "apple", 8, 90])

【问题讨论】:

  • array.values_at( 4, 3, 2, 1, 0 )
  • 您能澄清一下您的问题吗?在问题文本中,您询问有关不使用Array#reverse 的反转,但在问题标题中您询问的是不使用循环的反转(但您接受了使用循环的答案)。
  • 如果我们有一个集合,我们需要循环或迭代。没有逃脱。
  • 那么 StackOverflow 为您回答编码挑战会得到什么?! :-/

标签: ruby arrays algorithm


【解决方案1】:

您可以将数组视为堆栈,pop 从末尾开始处理元素:

def reverse(array)
  rev = []
  rev << array.pop until array.empty?
  rev
end

或者如果您不喜欢修改对象,请使用更多类似函数的reduce

def reverse(array)
  array.reduce([]) {|acc, x| [x] + acc}
end

Cary 在关于表演的评论中提到。函数式方法可能不是最快的方法,所以如果您真的想快速完成,请创建一个缓冲区数组,然后将项目从末尾添加到开头:

def reverse(array)
  reversed = Array.new(array.count)
  array.each_with_index do |item, index|
    reversed[-(index + 1)] = item
  end
  reversed
end

【讨论】:

  • 后者的效率劣势是为每个x创建一个新数组。如果您查看acc.object_id,您会发现它每次迭代都会发生变化。
  • 是的,当然——这是一种纯粹的函数式方法,无需修改任何数据,因此每次迭代都必须创建新对象。
【解决方案2】:

这是另一种非破坏性方法:

arr = ["a", 1, "apple", 8, 90]

arr.size.times.with_object([]) { |_,a| a << arr.rotate!(-1).first }
  #=> [90, 8, "apple", 1, "a"]
arr
  #=> ["a", 1, "apple", 8, 90] 

另一种可以想象的最无趣的方法:

(arr.size-1).downto(0).with_object([]) { |i,a| a << arr[i] }
  #=> [90, 8, "apple", 1, "a"]
arr
  #=> ["a", 1, "apple", 8, 90]    

【讨论】:

    【解决方案3】:

    一个想法:-

    ary = ["a", 1, "apple", 8, 90]
    ary.values_at(*(ary.size-1).downto(0))
    # => [90, 8, "apple", 1, "a"]
    

    ary.size.downto(0) 给出#&lt;Enumerator: ...&gt;。而*#&lt;Enumerator: ...&gt; 只是一个Enumerable#to_a 方法调用,splatsEnumerator 转换为[4, 3, 2, 1, 0]。最后,Array#values_atdocumented 的身份工作。

    【讨论】:

    • Arup,有没有办法用臭名昭著的for 循环倒数?
    • @BorisStitnicky 当您使用for 循环时,它也会在内部调用#each 方法。所以你不能用for循环。
    • 我会更多地考虑for x in [*4..0] do ... end 之类的东西,但这不起作用。所以我只是想知道臭名昭著的for 循环是否真的可以向下计数。
    • 这意味着执行类似操作的唯一方法是使用 while / until 循环并手动递减...
    • 是的.. 但是你为什么不喜欢Fixnum#downto 方法呢? :-)
    【解决方案4】:

    显而易见的解决方案是使用递归:

    def reverse(array)
      return array if array.size < 2
      reverse(array.drop(1)) + array.first(1)
    end
    

    我们可以使用标准的累加器技巧使这个尾递归:

    def reverse(array, accum=[])
      return accum if array.empty?
      reverse(array.drop(1), array.first(1) + accum)
    end
    

    当然,尾递归与循环同构。

    我们可以使用折叠:

    def reverse(array)
      array.reduce([]) {|accum, el| [el] + accum }
    end
    

    但是折叠相当于一个循环。

    def reverse(array)
      array.each_with_object([]) {|el, accum| accum.unshift(el) }
    end
    

    真的,each_with_object 是一个迭代器,它是 fold 的副作用表亲,所以实际上有 两个 原因使其等同于循环。

    【讨论】:

      【解决方案5】:

      如果您不打算使用循环,递归确实是解决方案。 whileuntil 仍然是循环,使用不做递归的内置方法也可能在内部仍然使用循环。

      #!/usr/bin/env ruby
      
      a = [1, 2, 3]
      
      def reverse(array)
        t = array.pop
        reverse(array) if array.length > 0
        array.unshift t
      end
      
      puts reverse(Array.new(a)).inspect # [3, 2, 1]
      

      更新

      自然递归是有限制的,因为它取决于堆栈,但如果您不想使用循环,这是您可以拥有的最好的。在Cary Swoveland's post 之后,这是8500 元素的基准:

                               user     system      total        real
      @Grych               0.060000   0.010000   0.070000 (  0.073179)
      @ArupRakshit         0.000000   0.000000   0.000000 (  0.000836)
      @konsolebox          0.000000   0.000000   0.000000 (  0.001771)
      @JörgWMittag recursion  0.050000   0.000000   0.050000 (  0.053475)
      @Jörg        tail    0.210000   0.040000   0.250000 (  0.246849)
      @Jörg        fold    0.040000   0.010000   0.050000 (  0.045788)
      @Jörg        loop    0.000000   0.000000   0.000000 (  0.000924)
      Cary         rotate  0.060000   0.000000   0.060000 (  0.059954)
      Cary         boring  0.000000   0.000000   0.000000 (  0.001004)
      

      【讨论】:

      • 你可以有不同的实现。但是您不应该覆盖核心方法。
      • @ArupRakshit 本来是的,但我想我不想定义一个没有明确表示它会改变其参数数据的方法。
      • 一个明显的方法是在其中创建一个新的数组实例,但这会使ifs 的递归方法复杂化。另一个明显的方法是使用像__reverse 这样的子方法。
      【解决方案6】:

      先生们,启动你们的引擎!

      [编辑:添加了来自@Grych 的两个方法和n = 8_000 的结果。]

      @Grych、@ArupRakshit、@konsolebox 和 @JörgWMittag:请检查我是否正确编写了您的方法。

      方法

      def grych_reduce(array)
        array.reduce([]) {|acc, x| [x] + acc}
      end
      
      def grych_prebuild(array)
        reversed = Array.new(array.count)
        array.each_with_index do |item, index|
          reversed[-(index + 1)] = item
        end
        reversed
      end
      
      def arup(ary)
        ary.values_at(*(ary.size-1).downto(0))
      end
      
      def konsolebox(array)
        t = array.pop
        konsolebox(array) if array.length > 0
        array.unshift t
      end    
      
      def jorg_recurse(array)
        return array if array.size < 2
        reverse(array.drop(1)) + array.first(1)
      end
      
      def jorg_tail(array, accum=[])
        return accum if array.empty?
        reverse(array.drop(1), array.first(1) + accum)
      end
      
      def jorg_fold(array)
        array.reduce([]) {|accum, el| [el] + accum }
      end
      
      def jorg_loop(array)
        array.each_with_object([]) {|el, accum| accum.unshift(el) }
      end
      
      def cary_rotate(arr)
        arr.size.times.with_object([]) { |_,a| a << arr.rotate!(-1).first }
      end
      
      def cary_boring(arr)
        (arr.size-1).downto(0).with_object([]) { |i,a| a << arr[i] }
      end
      

      基准测试

      require 'benchmark'
      
      arr = [*(1..n)]
      puts "n = #{n}"    
      
      Benchmark.bm(16) do |bm|
        bm.report('grych_reduce')    { grych_reduce(arr) }
        bm.report('grych_prebuild')  { grych_prebuild(arr) }
        bm.report('arup')            { arup(arr)  }
        bm.report('konsolebox')      { konsolebox(arr) }
        bm.report('jorg_recurse')    { jorg_recurse(arr) }
        bm.report('jorg_tail')       { jorg_tail(arr)  }
        bm.report('jorg_fold')       { jorg_fold(arr)  }
        bm.report('jorg_loop')       { jorg_loop(arr)  }
        bm.report('cary_rotate')     { cary_rotate(arr)  }
        bm.report('cary_boring')     { cary_boring(arr) }
        bm.report('grych_destructo') { grych_destructo(arr) }
      end
      

      星期三:热身赛(n = 8_000)

                             user     system      total        real
      grych_reduce       0.060000   0.060000   0.120000 (  0.115510)
      grych_prebuild     0.000000   0.000000   0.000000 (  0.001150)
      arup               0.000000   0.000000   0.000000 (  0.000563)
      konsolebox         0.000000   0.000000   0.000000 (  0.001581)
      jorg_recurse       0.060000   0.040000   0.100000 (  0.096417)
      jorg_tail          0.210000   0.070000   0.280000 (  0.282729)
      jorg_fold          0.060000   0.080000   0.140000 (  0.138216)
      jorg_loop          0.000000   0.000000   0.000000 (  0.001174)
      cary_rotate        0.060000   0.000000   0.060000 (  0.056863)
      cary_boring        0.000000   0.000000   0.000000 (  0.000961)
      grych_destructo    0.000000   0.000000   0.000000 (  0.000524)
      

      星期四:试验 #1 (n = 10_000)

                             user     system      total        real
      grych_reduce       0.090000   0.080000   0.170000 (  0.163276)
      grych_prebuild     0.000000   0.000000   0.000000 (  0.001500)
      arup               0.000000   0.000000   0.000000 (  0.000706)
      jorg_fold          0.080000   0.060000   0.140000 (  0.139656)
      jorg_loop          0.000000   0.000000   0.000000 (  0.001388)
      cary_rotate        0.090000   0.000000   0.090000 (  0.087327)
      cary_boring        0.000000   0.000000   0.000000 (  0.001185)
      grych_destructo    0.000000   0.000000   0.000000 (  0.000694)
      

      konsoleboxjorg_recursejorg_tail 已消除(堆栈级别太深)。

      星期五:试验 #2 (n = 50_000)

                             user     system      total        real
      grych_reduce       2.430000   3.490000   5.920000 (  5.920393)
      grych_prebuild     0.010000   0.000000   0.010000 (  0.007000)
      arup               0.000000   0.000000   0.000000 (  0.003826)
      jorg_fold          2.430000   3.590000   6.020000 (  6.026433)
      jorg_loop          0.010000   0.010000   0.020000 (  0.008491)
      cary_rotate        2.680000   0.000000   2.680000 (  2.686009)
      cary_boring        0.010000   0.000000   0.010000 (  0.006122)
      grych_destructo    0.000000   0.000000   0.000000 (  0.003288)
      

      星期六:资格(n = 200_000)

                             user     system      total        real
      grych_reduce      43.720000  66.140000 109.860000 (109.901040)
      grych_prebuild     0.030000   0.000000   0.030000 (  0.028287)
      jorg_fold         43.700000  66.490000 110.190000 (110.252620)
      jorg_loop          0.030000   0.010000   0.040000 (  0.030409)
      cary_rotate       43.060000   0.050000  43.110000 ( 43.118151)
      cary_boring        0.020000   0.000000   0.020000 (  0.024570)
      grych_destructo    0.010000   0.000000   0.010000 (  0.013338)
      

      arup_verse 消除(堆栈级别太深); grych_reducejorg_foldcary_rotate 被淘汰(没有竞争力)。

      星期日:决赛(n = 10_000_000)

                             user     system      total        real
      grych_prebuild     1.450000   0.020000   1.470000 (  1.478903)
      jorg_loop          1.530000   0.040000   1.570000 (  1.649403)
      cary_boring        1.250000   0.040000   1.290000 (  1.288357)
      grych_destructo    0.640000   0.030000   0.670000 (  0.689819)
      

      【讨论】:

      • 你终于伤害了我! :-)
      • 一件事..我今天学到了..方法的参数数量有一个限制
      • 这是一个很好的基准,谢谢!您能否将我刚刚添加的方法添加到我的答案中(带有each_with_index 的方法)?我认为应该很快
      • 是的,我希望它不过递归是你可以拥有的唯一方法,如果你不想使用循环:)
      【解决方案7】:

      Konsolebox 是对的。如果他们要求没有循环的方法,那仅仅意味着您不能使用任何类型的循环,无论是 map、each、while、until 还是任何使用循环的内置方法,如长度、大小和计数等。

      一切都需要递归:

      def recursive_reversal(array)
      
        return array if array == [] # or array.empty?
        last_element = array.pop
        return [last_element, recursive_reversal(array)].flatten
      
      end
      

      Ruby 使用递归来展平,因此展平不会涉及任何类型的循环。

      【讨论】:

        【解决方案8】:
        def reverse(array)
            array.values_at(*((array.size-1).downto 0))
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-11-20
          • 2021-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-24
          • 2014-10-07
          • 1970-01-01
          相关资源
          最近更新 更多