Ruby's enumerable module includes a nifty little method called #cycle. It's just like the #each method, only it won't stop iterating unless you tell it too. Once it iterates throught the last object, it will start right back at the beginning and keep going, unless you've specified a specific amount of cycles for it to run, or a specific breaking point. Lets look at an example. If we ran the code:
[1,2,3,4,5].each {|num| p num}
puts
[1,2,3,4,5].cycle(1) {|num| p num}
12345
12345
[1,2,3,4,5].each {|num| p num}
puts
[1,2,3,4,5].cycle(3) {|num| p num}
12345
123451234512345
Another interesting use of the #cycle method is that it can be used in conjunction with break to only stop running when a specific event has occured. As a simple example, lets try running something like:
i = 0
(1..10).to_a.cycle {|num| print "#{num} "; i += 1; break if i == 15}
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5
Notice how it stops printing numbers halfway through the second cycle?Here, we quickly converted the range 1-10 to an array, and ran #cycle on it. But we didn't include a parameter to tell the code when to stop running. The only reason this sucker stops at all is because of the break we input at the end of the block. By including a counter "i" and increasing the value of it at every iteration through "i += 1", we can tell the code exactly when we want it to stop running, even if its only partially through the current cycle.
There are a lot of simple ways that #cycle can be used to quickly perform some tasks. I won't get too into those because there are other great resources explaining them. If you're interested, try:
Global Nerdy
Instead, I figured I'd dive into what I think would be practical applications of the #cycle method.
Let's say you and you're friends are lazy and want to split up the studying for a really boring class that you're all taking. You might decide to divvy up the chapters of the book by cycling through your group of friends - each taking one chapter, and then starting from the first person again. It may behoove you to write some code to figure this out. You might start with an array including your groups names:
team = [:mike, :adam, :emily, :sarah, :ken, :barbie]
Let's assume there are 100 chapters to allocate. You might then write:
i = 1
counts = {}
team.cycle {|name| counts[name] == nil ? counts[name] = [i] : counts[name] << i; i += 1; break if i == 101}
- counts[name] == nil ? -- We're asking, does the counts hash include the current name as a key, and does it have a value?
- ? counts[name] = [i] -- If there is no value (meaning the code prior to the "?" is true), then lets set the value for this key equal to an array that holds the current number we are on.
- : counts[name] << i; -- If the key already exists and has a value (which would be an array established in the prior step), lets add into that value the current number
- i += 1; -- Every time we add in a number, we want this counter to go up until we've allocated all 100 chapters
- break if i == 101 -- This determines when the cycles stop.
p counts
We'd get:{:adam=>[2, 8, 14, 20, 26, 32, 38, 44, 50, 56, 62, 68, 74, 80, 86, 92, 98], :emily=>[3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 87, 93, 99], :sarah=>[4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 64, 70, 76, 82, 88, 94, 100], :ken=>[5, 11, 17, 23, 29, 35, 41, 47, 53, 59, 65, 71, 77, 83, 89, 95], :barbie=>[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96], :mike=>[1, 7, 13, 19, 25, 31, 37, 43, 49, 55, 61, 67, 73, 79, 85, 91, 97]}
We might clean up the output a little by using:counts.each {|k, v| p k, v}
:adam
[2, 8, 14, 20, 26, 32, 38, 44, 50, 56, 62, 68, 74, 80, 86, 92, 98]
:emily
[3, 9, 15, 21, 27, 33, 39, 45, 51, 57, 63, 69, 75, 81, 87, 93, 99]
:sarah
[4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 64, 70, 76, 82, 88, 94, 100]
:ken
[5, 11, 17, 23, 29, 35, 41, 47, 53, 59, 65, 71, 77, 83, 89, 95]
:barbie
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]
:mike
[1, 7, 13, 19, 25, 31, 37, 43, 49, 55, 61, 67, 73, 79, 85, 91, 97]
What if you wanted to assign random chapters to you and your friends? This gets a bit more complicated, and before I take you through this let me preface this by saying that there are likely better ways of getting to the result in this example. I'd love to hear your feedback if you're aware of any!
Here is the ending code:
def randassign(team, count)
numbers = []
i = 0
count.times {|num| numbers[i] = i+1; i += 1}
rands = {}
j = 0
team.cycle do |x|
position = rand(count-j)
if rands[x] == nil
rands[x] = [numbers[position]]
else
rands[x] << numbers[position]
end
j+=1
numbers.delete_at(position)
break if numbers.count == 0
end
rands.each {|name, numbers| p name, numbers.sort}
end
randassign(team,50)
:adam
[2, 5, 10, 14, 16, 25, 44, 48, 50]
:emily
[1, 4, 7, 26, 28, 33, 38, 40]
:sarah
[17, 21, 22, 23, 32, 42, 43, 47]
:ken
[3, 13, 15, 19, 27, 29, 37, 39]
:barbie
[6, 11, 12, 18, 20, 36, 41, 45]
:mike
[8, 9, 24, 30, 31, 34, 35, 46, 49]
:adam
[1, 17, 21, 23, 27, 30, 44, 47, 48]
:emily
[3, 7, 11, 19, 22, 38, 41, 43]
:sarah
[2, 5, 12, 16, 20, 25, 36, 45]
:ken
[8, 10, 14, 18, 28, 34, 35, 49]
:barbie
[4, 9, 31, 32, 37, 39, 42, 46]
:mike
[6, 13, 15, 24, 26, 29, 33, 40, 50]
def randassign(team, count)
numbers = []
i = 0
count.times {|num| numbers[i] = i+1; i += 1}
rands = {}
This line just establishes an empty hash that will ultimately hold each team member name as a key, with corresponding arrays of chapter numbers as values.Now comes our implementation of #cycle:
j = 0
team.cycle do |x|
position = rand(count-j)
if rands[x] == nil
rands[x] = [numbers[position]]
else
rands[x] << numbers[position]
end
j+=1
numbers.delete_at(position)
break if numbers.count == 0
end
j = 0
team.cycle do |x|
position = rand(count-j)
Next, we have:
if rands[x] == nil
rands[x] = [numbers[position]]
else
rands[x] << numbers[position]
end
The next 3 lines are really important:
j+=1
numbers.delete_at(position)
break if numbers.count == 0
end
Finally, the last line of code just outputs the completed rand hash in a clean, sorted manner:
rands.each {|name, numbers| p name, numbers.sort}
Try it for yourself!I had a lot of fun working through this exercise and learning about #cycle. Feel free to tell me my example is terrible and that there are better ways of doing that. Hopefully you learned something from this post!
Copyright: Gary Hammell 2014