How to rewrite this Ruby loop in a cleaner fashion - ruby

I'm implementing a loop in Ruby, but it looks ugly and I wonder if there's a neater, more Ruby-like way of writing it:
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
Note that the get_page_items method runs a HTTP request to get the items for the page, and there is no way of knowing the number of pages, or the total number of items, or the number of items for any page before actually executing the requests in order until one of them returns an empty item set.
Imagine leafing through a catalog and writing down all the products, without knowing in advance how many pages it has, or how many products there are.

I think that this particular problem is compounded because A) there's no API for getting the total number of items and B) the response from get_page_items is always truthy. Further, it doesn't make sense for you to iteratively call a method that is surely making individual requests to your DB with an arbitrary limit, only to concatenate them together. You should, at the risk of repeating yourself, implement this method to prompt a DB query (i.e. model.all).
Normally when you are defining an empty collection, iterating and transforming a set, and then returning a result, you should be using reduce (a.k.a inject):
array.reduce(0) { |result, item| result + item } # a quick sum
Your need to do a form of streaming in this same process makes this difficult without tapping into Enumerable. I find this to be a good compromise that is much more readable, even if a bit distasteful in fondling this items variable too much:
items = []
begin
items << page_items = get_page_items(page ||= 1)
page += 1
end until page_items.empty?
items.flatten

Here's how I'd have written it. You'll see it's actually more lines, but it's easier to read and more 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
You could also implement get_page_items as an Enumerator which would eliminate the awkward page += 1 pattern but that might be overkill.

I don't know that this is any better, but it does have a couple of Ruby-isms in it:
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

I would use this solution, which is a good compromise between readability and length:
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

The short version, just for fun ;-)
i=[]; p=0; loop { i+=get_page_items(p+=1).tap { |r| return i if r.empty? } }

I wanted to write a functional solution which would closely resemble the task you want to achieve.
I'd say that your solution comes down to this:
For all page numbers from 1 on, you get the corresponding list of
items; Take lists while they are not empty, and join them into a
single array.
Sounds ok?
Now let's try to translate this, almost literally, to 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.
Unfortunately, the above code won't work right away, as Ruby's map is not lazy, i.e. it would try to evaluate on all members of the infinite range first, before our take_while had the chance to peek at the values.
However, implementing a lazy map is not that hard at all, and it could be useful for other stuff. Here's one straightforward implementation, along with nice examples in the blog post.
module Enumerable
def lazy_map
Enumerator.new do |yielder|
self.each do |value|
yielder.yield(yield value)
end
end
end
end
Along with a mockup of your actual HTTP call, which returns arrays of random length between 0 and 4:
# This simulates actual HTTP call, sometimes returning an empty array
def get_page_items page
(1..rand(5)).to_a
end
Now we have all the needed parts to solve our problem easily:
(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]

It's a small (and almost entirely cosmetic) tweak, but one option would be to replace while page_items != [] with until page_items.empty?. It's a little more "Ruby-ish," in my opinion, which is what you're asking about.

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

Related

Ruby calling method in quick_sort gives wrong output

I am doing a quick sort in ruby and using quick_sort I am grabbing the last 3 elements of the array and multiplying it to get the value.
My quick sort works fine but only thing is when I call the my other method max_product_three in quick_search the sorted_array I pass into the method is only showing up as two random numbers like [-3,-2]. If I take out my method from quick_search it gives the correct output. What is happening that when I put my method the sorted array is wrong which is causing my max_product_three not to work.
quick_search.rb
def quick_search(array)
return array if array.length <= 1
len = array.length - 1
left = []
right = []
pivot = array.sample
array.delete_at(array.index(pivot))
array.each do |num|
if num < pivot
left << num
else
right << num
end
end
sorted = []
sorted << quick_search(left)
sorted << pivot
sorted << quick_search(right)
sorted_array = sorted.flatten
p sorted_array
max_product_three(sorted_array)
end
max_product_three.rb
def max_product_three(sorted_array)
len = sorted_array.length - 1
take_3 = len - 3
mulitple = sorted_array.drop(take_3)
p mulitple.inject(:*)
end
I am using this array as reference [-3,1,2,-2,5,6]
I assume you're writing this to learn how quick-sort works? Because ruby's got a #sort method right there... 😁
There are few things here. Is your intention to do two things: first, sort the array; second, multiply the three largest values in the sorted array? Because that's not what your code currently does. You call your max_product_three method from within your sort, which means it'll be called every time quick_sort is called.
Worse, it's the last line in the method. That means the result of calling max_product_three is what's returned each time you iterate, not the sorted array! So, for each sub-sort, what you get back is a single number instead of the sorted array.
Also, your max_product_three method multiplies the last 4 values, not the last 3 values. (You subtract 1 from its length and then subtract 3 from it, so you're dropping length - 4 values, leaving 4 values to multiply.)
You don't need to do p sorted_array at the end of your quick_search method (presumably, should be quick_sort!) but can just have sorted_array to return the array.
And, a smaller thing, your initial guard clause would be a bit better (and more ruby-ish) by not using an explicit operator, for example:
return array unless array.length.positive?
That's quite a lot, and I may have misinterpreted what you're trying to do here so let me know if I have!

How can I collect block values in Ruby without an iterator?

Here's some code:
i = 0
collection = []
loop do
i += 1
break if complicated_predicate_of(i)
collection << i
end
collection
I don't know in advance how many times I'll need to iterate; that depends on complicated_predicate_of(i). I could do something like 0.upto(Float::INFINITY).times.collect do ... end but that's pretty ugly.
I'd like to do this:
i = 0
collection = loop.collect do
i += 1
break if complicated_predicate_of(i)
i
end
But, though it's not a syntax error for some reason, loop.collect doesn't seem to collect anything. (Neither does loop.reduce). collection is nil at the end of the statement.
In other words, I want to collect the values of a loop statement without an explicit iterator. Is there some way to achieve this?
You could write
collection = 1.step.take_while do |i|
i <= 3 # this block needs to return *false* to stop the taking
end
Whatever solution you choose in the end, remember that you can always opt to introduce a helper method with a self-explanatory name. Especially if you need to collect numbers like this in many places in your source code.
Say you wanted to hide the intricate bowels of your solution above, then this could be your helper method:
def numbers_until(&block)
i = 0
collection = []
loop do
i += 1
break if yield i
collection << i
end
collection
end
collection = numbers_until do |i|
i > 3 # this block needs to return *true* to stop the taking
end
You could write
def complicated_predicate_of(i)
i > 3
end
1.step.with_object([]) { |i,collection| complicated_predicate_of(i) ?
(break collection) : collection << i }
#=> [1, 2, 3]

triangular_numbers enumerator yielder yield

triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
loop do
number += count
count += 1
yielder.yield number
end
end
5.times { print triangular_numbers.next, " " }
puts
I know you all have answers questions about this before.
I am trying to understand more about what going on
Am I right to say yielder is a parameter which is probably a hash or an array and
yielder.yield number is basically pushing whatever number it is on to that array.
Also I seen people use yielder << number, i assume that you can also use
yielder.push(number), will it do the same thing.
One other thing I like to know why the number is retaining its value.
No it's not a data structure. It's an object - an instance of Enumerator::Yielder which, if you click the link, you will see isn't documented very well! It is written in C, and the only methods it has are yield and the alias <<. You should leave it to the Ruby core to handle itself.
It's essentially like a pipe, which is used internally by the Enumerator and Enumerable methods to fetch items from an enumeration as required. Enumerator#next, for example, will fetch the next item from the sequence. The methods available in Enumerable are much more comprehensive, and are based on the basic operations provided by Enumerator.

Subtraction of two arrays with incremental indexes of the other array to a maximum limit

I have lots of math to do on lots of data but it's all based on a few base templates. So instead of say, when doing math between 2 arrays I do this:
results = [a[0]-b[1],a[1]-b[2],a[2]-b[3]]
I want to instead just put the base template: a[0]-b[1] and make it automatically fill say 50 places in the results array. So I don't always have to manually type it.
What would be the ways to do that? And would a good way be to create 1 method that does this automatically. And I just tell it the math and it fills out an array?
I have no clue, I'm really new to programming.
a = [2,3,4]
b = [1,2,3,4]
results = a.zip(b.drop(1)).take(50).map { |v,w| v - w }
Custom
a = [2,3,4..............,1000]
b = [1,2,3,4,.............900]
class Array
def self.calculate_difference(arr1,arr2,limit)
begin
result ||= Array.new
limit.send(:times) {|index| result << arr1[index]-arr2[index+=1]}
result
rescue
raise "Index/Limit Error"
end
end
end
Call by:
Array.calculate_difference(a,b,50)

handling data structures (hashes etc) gracefully in ruby

I recently did a class assignment where I made a really hacky data structure. I ended up using nested hashes, which seems like a good idea, but is really hard to iterate through and manage.
I was doing general stuff, like one tag maps to a hash of items that map to prices and stuff like that. But some of them were getting more complicated.
I know that rails uses a lot of more elegant seeming stuff with symbols and such (which I never use shameful face) and I was wondering how I could optimize this. For example if I had my nested hashes something like this
h["cool"][????][1.2]
is there a graceful way of pulling those values out? Maybe I'm just a total newbie in this regard but I wanted to set things straight before I started doing more things. Maybe I'm even looking for something different like a mix of array/hash or something. Please let me know!
It looks like you need to think about structuring your data more rigorously. Try creating a class for your items, which can contain prices among other things, and perhaps organising them in the way you need to access them. Think about what you want and place the information in structures in a way that makes sense to you. Anything else is a waste of time, both now and three months down the line when you need to extend the system and find you can't.
Yes, it'll be quite a bit of work, and yes, it'll be worth it.
Edit: Revised to provide the rough path to the item. It can't know the name of the variable though.
Try this:
def iterate_nested(array_or_hash, depth = [], &block)
case array_or_hash
when Array:
array_or_hash.each_with_index do |item, key|
if item.class == Array || item.class == Hash
iterate_nested(item, depth + [key], &block)
else
block.call(key, item, depth + [key])
end
end
when Hash:
array_or_hash.each do |key, item|
if item.class == Array || item.class == Hash
iterate_nested(item, depth + [key], &block)
else
block.call(key, item, depth + [key])
end
end
end
end
It should iterate to any depth necessary, limited by memory, etc, and return the key and item and depth of the returned item. Works with both hashes and arrays.
If you test with:
iterate_nested([[[1,2,3], [1,2,3]], [[1,2,3], [1,2,3]], [[1,2,3], [1,2,3]]]) do |key, item, depth|
puts "Element: <#{depth.join('/')}/#{key}> = #{item}"
end
It yields:
Element: <0/0/0/0> = 1
Element: <0/0/1/1> = 2
Element: <0/0/2/2> = 3
Element: <0/1/0/0> = 1
Element: <0/1/1/1> = 2
Element: <0/1/2/2> = 3
Element: <1/0/0/0> = 1
Element: <1/0/1/1> = 2
Element: <1/0/2/2> = 3
Element: <1/1/0/0> = 1
Element: <1/1/1/1> = 2
Element: <1/1/2/2> = 3
Element: <2/0/0/0> = 1
Element: <2/0/1/1> = 2
Element: <2/0/2/2> = 3
Element: <2/1/0/0> = 1
Element: <2/1/1/1> = 2
Element: <2/1/2/2> = 3
Cheerio!
h["cool"].keys
to then iterate the tree would be
h["cool"].keys.each |outer| { h["cool"][outer].each { |inner| puts inner }}
It really depends on what you're trying to do (nowhere near enough information in the question), but if you need to dive in three or more levels into a Hash, you may very well want a recursive tree traversal algorithm:
def hash_traverse(hash)
result = ""
for key, value in hash
result << key.to_s + ":\n"
if !value.kind_of?(Hash)
result << " " + value.to_s + "\n"
else
result << hash_traverse(value).gsub(/^/, " ")
end
end
return result
end
Are you sure a Hash is the best data structure for what you're trying to do?

Resources