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

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]

Related

Able to use a variable within another variable's name? Ruby

So my goal is to be able to run through a "while" loop and in each iteration create a new variable that includes the "iteration count" within that variables name and stores it for later use outside of the loop. See below for more details.
NOTE: The code is clearly wrong in so many ways but I'm writing it this way to make it more clear? as to what I am trying to accomplish. Thanks for any input on how this is possible.
count = "4"
while count > "0"
player"#{count}"_roll = rand(20)
puts 'Player "#{count}" rolled: "#{player"#{count}"_roll}"'
count -= 1
end
My goal is then to be able to access the variables that were created from within the loop at a later part of the program like so (more or less)
puts player4_roll
puts player3_roll
puts player2_roll
puts player1_roll
The key being that these variables were A) created in the loop B) With names relying on another variables input, and C) accessible outside the loop for later use.
Hopefully my question came out clear and any input will be greatly appreciated. I'm very new to programming and trying to get my head wrapped around some more complex ideas. I'm not sure if this is even possible to do in Ruby. Thanks!
I think the best way is to use arrays or hashes, with arrays its something like this:
count = 0
array = []
while count < 4 do
array[count] = rand(20)
puts "Player #{count} rolled: #{array[count]}"
count += 1
end
array.each do |var|
puts var
end
You store the result in the array and then you loop trough it. If you want the result of the second iteration of the loop you do something like this:
puts array[1]
If you want to use Hashes there are some modifications you need to do:
count = 0
hash = {}
while count < 4 do
hash["player#{count}_roll"] = rand(20)
puts "Player #{count} rolled: #{hash["player#{count}_roll"]}"
count += 1
end
hash.each do |key, var|
puts var
end
If you want the result of the second iteration of the loop you do something like this:
puts hash["player1_roll"]
You could set the variable using instance_variable_set and reference it that way
instance_variable_set("#player#{count}_roll", rand(20))

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)

In Ruby, how can I collect each new element passing through a method into an array?

I'm creating a small prime number program, and am confused about one thing.
I have a function called create_numbers, that generates numbers and passes them to a new function called check_for_primes, which passes only prime numbers to a final function called count_primes. I want to collect each prime into an array in the function count_primes, but for some reason each number is collected as its own array.
Any idea of what I'm doing wrong?
Here is the code:
def create_numbers
nums = 1
while nums < 100
nums = nums + 2
check_for_primes(nums)
end
end
def count_primes(nums)
array = []
array << nums
puts array.inspect
end
def check_for_primes(nums)
(2...nums).each do |i|
if nums%i == 0
nums = false
break
end
end
if nums != false
count_primes(nums)
end
end
create_numbers
Try this:
START = 1
STEP = 2
class Integer
def prime?
return if self < 2
(2...self).each do |i|
return if self % i == 0
end
true
end
end
def create_numbers
num = START
while (num + STEP) < 100
num += STEP
primes << num if num.prime?
end
end
def primes
#primes ||= []
end
create_numbers
p primes
When you want to save the 'state' of something, put it in an instance variable (#var).
It'll be accessible outside of the current function's scope.
Also, try naming your variables differently. For instance, instead of 'nums', in the
create_numbers method, use 'num'. Since the variable is only referencing one number at a
time and not a list of numbers, naming it in the plural will confuse people (me included)...
Hope it helps,
-Luke
each time into count_primes you put a value into array (which should have a better name, btw). Unfortunately, each time it's a new variable called array and since no one outside the function can see that variable it's lost when the function ends. If you want to save the values you've already found you'll need to set some state outside your function.
I can think of 2 quick solutions. One would be to declare your storage at the top of create_numbers and pass it into both functions.
def count_primes(num, arr)
def check_for_primes(nums, arr)
The other would be to set a variable outside all the functions, $array, for example to hold the values.
$array = []
...
$array << num
Since the scope of $array is global (i.e. all functions have access to it) you have access to it from anywhere in the file and can just add things to it in count primes. Note that using globals in this way is generally considered bad style and a more elegant solution would pass parameters and use return values.

How to rewrite this Ruby loop in a cleaner fashion

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

How do I dynamically decide which hash to add a value to?

I have a class that has hashes in various stages of "completion". This is to optimize so that I don't have to keep recreating hashes with root data that I already know. For example this is a counter called #root that would serve as a starting point.
{3=>4, 4=>1, 10=>3, 12=>5, 17=>1}
and it took key+key+key+key+key number of iterations to create #root. But now I have all combinations of [x,y] left to be added to the counter and individually evaluated. So I could do it like:
a = (1..52)
a.combination{|x,y|
evaluate(x,y)
}
But instead of I would like to do this:
a.each{|x|
evaluate(x, "foo")
a.each {|y| evaluate(y, "bar")}
}
Where i have a method like this to keep track of the hash at each state:
def evaluate index, hsh
case hsh
when "root"
#root.key?(index) ? #root[index] += 1 : #root[index] = 1
when "foo"
#foo = #root.clone
#foo.key?(index) ? #foo[index] += 1 : #foo[index] = 1
when "bar"
#bar = #foo.clone
#bar.key?(index) ? #bar[index] += 1 : #bar[index] = 1
end
end
But there is alot of repetition in this method. Is there a way that I could do this dynamically without using eval?
Instead of using hsh as a string descriptor, you can directly pass the hash object as parameter to your method evaluate? E.g. instead of evaluate(x, "foo") you write
#foo = #root.clone
evaluate(x, #foo)
Also note the #root.clone in your code overwrites the field several times inside the loop.
Additionally if you use a default initializer for your hash you save quite some logic in your code. E.g. the code lines
h = Hash.new{0}
...
h[index] += 1
will set the default value to zero if non was set for index. Thus you do not have to take care of the special case inside your evaluate method.

Resources