我正在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请求以获取页面的项目,并且无法知道页面的数量,项目的总数或任何项目的数量在按顺序实际执行请求之前的页面,直到其中一个返回空项目集。
想象一下翻阅目录并写下所有产品,而不事先知道它有多少页面,或者有多少产品。
答案 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