如何以更清洁的方式重写这个Ruby循环

时间:2011-08-12 19:23:13

标签: ruby arrays

我正在Ruby中实现一个循环,但它看起来很难看,我想知道是否有更简洁,更像Ruby的编写方式:

def get_all_items
  items = []; page = 1; page_items = nil
  while page_items != [] # Loop runs until no more items are received
    items += (page_items = get_page_items(page))
    page += 1
  end
  items
end

请注意,get_page_items方法运行HTTP请求以获取页面的项目,并且无法知道页面的数量,项目的总数或任何项目的数量在按顺序实际执行请求之前的页面,直到其中一个返回空项目集。

想象一下翻阅目录并写下所有产品,而不事先知道它有多少页面,或者有多少产品。

8 个答案:

答案 0 :(得分:3)

我认为这个特殊问题是复杂的,因为A)没有用于获取项目总数的API,而B)来自get_page_items的响应总是真实的。此外,迭代地调用一个方法肯定会向数据库发出具有任意限制的单个请求,只是将它们连接在一起是没有意义的。您应该冒着重复自己的风险,实施此方法来提示数据库查询(即model.all)。

通常,在定义空集合,迭代和转换集合,然后返回结果时,您应该使用reduce(a.k.a inject):

array.reduce(0) { |result, item| result + item } # a quick sum

你需要在同一个过程中进行一种流式传输,这一点很难实现,而不需要使用Enumerable。我发现这是一个更好的折衷方案,即使在过多地理解这个items变量时有点令人反感:

items = []
begin
  items << page_items = get_page_items(page ||= 1)
  page += 1
end until page_items.empty?
items.flatten

答案 1 :(得分:1)

这是我写的方式。你会发现它实际上是更多的线条,但它更容易阅读和更多的Rubyish。

def get_all_items
  items       = []
  page        = 1
  page_items  = get_page_items page

  until page_items.empty? # Loop runs until no more items are received
    items += page_items
    page  += 1
    page_items = get_page_items page
  end

  items
end

您还可以将get_page_items实施为Enumerator,这样可以消除尴尬的page += 1模式,但这可能有点过分。

答案 2 :(得分:1)

我不知道这更好,但它确实有几个Ruby-isms:

def get_all_items
  items = []; n = 0; page = 1
  while items.push(*get_page_items(page)).length > n
    page += 1
    n = items.length
  end
end

答案 3 :(得分:1)

我会使用这个解决方案,这是可读性和长度之间的良好折衷:

def get_all_items
  [].tap do |items|
     page = 0
     until (page_items = get_page_items(page)).empty?
       items << page_items
       page += 1
     end
  end
end

答案 4 :(得分:1)

我想编写一个与您想要实现的任务非常相似的功能性解决方案。

我会说你的解决方案归结为:

  

对于从1开始的所有页码,您将获得相应的列表   项目;在列表不空的情况下获取列表,并将它们加入到列表中   单阵列。

听起来不错?

现在让我们尝试将这几乎从字面上翻译成Ruby:

(1..Float::INFINITY).                # For all page numbers from 1 on
  map{|page| get_page_items page}.   # get the corresponding list of items
  take_while{|items| !items.empty?}. # Take lists while they are not empty
  inject(&:+)                        # and join them into a single array.

不幸的是,上面的代码不会马上工作,因为Ruby的map并不是懒惰的,即它会先尝试在无限范围内的所有成员上进行评估,然后我们才会take_while有机会窥探价值观。

然而,实现一个懒惰的地图并不是那么难,它可能对其他东西有用。 Here's一个简单的实现,以及博客文章中的好例子。

module Enumerable
  def lazy_map
    Enumerator.new do |yielder|
      self.each do |value|
        yielder.yield(yield value)
      end
    end
  end
end

与实际HTTP调用的模型一起,返回0到4之间随机长度的数组:

# This simulates actual HTTP call, sometimes returning an empty array
def get_page_items page
  (1..rand(5)).to_a
end

现在我们拥有了解决问题所需的所有部分:

(1..Float::INFINITY).                   # For all page numbers from 1 on
  lazy_map{|page| get_page_items page}. # get the corresponding list of items
  take_while{|items| !items.empty?}.    # Take lists while they are not empty
  inject(&:+)                           # and join them into a single array.
#=> [1, 1, 2, 3, 1]

答案 5 :(得分:1)

简短版本,只是为了好玩; - )

i=[]; p=0; loop { i+=get_page_items(p+=1).tap { |r| return i if r.empty? } }

答案 6 :(得分:0)

这是一个小的(几乎完全是化妆品)调整,但一个选项是用while page_items != []替换until page_items.empty?。在我看来,这更像是“Ruby-ish”,这就是你所要求的。

答案 7 :(得分:0)

def get_all_items
  items = []; page = 0
  items << page_items while (page_items = get_page_items(page += 1))
  items
end