How to continuosly loop over array values from another loop? - ruby

I've got a array of numbers for example :
a = [1,2,3,4,5,6,7,8,9,10,11]
And number of constants:
TITLES = ['a', 'b', 'c', 'd']
When I iterate over a I want to get a title for each item like this :
- iterating over a, first item (1) get title 'a'
- iterating over a, first item (2) get title 'b'
- iterating over a, first item (3) get title 'c'
- iterating over a, first item (4) get title 'd'
- iterating over a, first item (5) get title 'a'
- iterating over a, first item (6) get title 'b'
So when I run over titles start from the begining, this is what I have now :
a.each_with_index do |m, i|
if TITLES[i].nil?
title = TITLES[(i - TITLES.length)]
else
title = TITLES[i]
end
end
But this doesn't work unfortunately I get a nil title for the last item of a. How can I make this work?

You can use the zip and cycle methods like this:
a.zip(TITLES.cycle).each do |x, title|
p [x, title]
end
# Output:
# [1, "a"]
# [2, "b"]
# [3, "c"]
# [4, "d"]
# [5, "a"]
# ...

The kind-of-obvious way uses #each_with_index
a.each_with_index do |x, i|
p [x, TITLES[i % TITLES.length]]
end
Or, try something like this...
a.zip(TITLES*3).each do |x, y|
p [x, y]
end

What about:
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
TITLES = ['a', 'b', 'c', 'd']
t_length = TITLES.length
a.each_with_index do |item, index|
t_index = index % t_length
title = TITLES[t_index]
puts "item: #{item} - title: #{title}"
end

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
TITLES = ['a', 'b', 'c', 'd']
TITLES = TITLES + TITLES + TITLES
(a.zip TITLES).each do |p, q|
puts "======#{p}==#{q}======"
end

Related

How to loop through multiple arrays that correspond with each other in Ruby

How would you incorporate multiple arrays into one loop? I want them to iterate over just one at a time at the same time.
example:
a = [1, 2, 3, 4]
b = [a, b, c, d]
c = [w, x, y, z]
desired output:
"on the 1st day, there was a 1 on a. Add w."
"on the 2nd day, there was a 2 on b. Add x."
"on the 3rd day, there was a 3 on c. Add y."
"on the 4th day, there was a 4 on d. Add z."
Also, can we incorporate .each_with_index? Because I also need the index number to be printed in the loop. The number in "on the 1st/2nd/etc day" is where I would input #{index + 1}.
This is what zip is for:
a.zip(b)
# => [ [ 1, 'a' ], [ 2, 'b' ], ... ]
Now you can just iterate over that:
a.zip(b).each.with_index(1) do |(was, on), day|
# ... Print message
end
Where here you can use .each.with_index(1) to specify a starting index, where each_with_index, for whatever reason, does not allow that.
You can use map.with_index
For example:
a = [1, 2, 3, 4]
b = ['a', 'b', 'c', 'd']
a.map.with_index do |item, index|
puts "on the #{index + 1}th day, there was a #{index + 1} on #{b[index]}."
end
Output would be like a
on the 1th day, there was a 1 on a.
on the 2th day, there was a 2 on b.
on the 3th day, there was a 3 on c.
on the 4th day, there was a 4 on d.
=> [nil, nil, nil, nil]
Also you can use with some arrays:
a = [1, 2, 3, 4]
b = ['a', 'b', 'c', 'd']
c = [:foo, :bar, :fu, :bar]
a.map.with_index do |item, index|
puts "on the #{index + 1}th day, there was a #{index + 1} on #{b[index]} with #{c[index]}."
end
It will returns
on the 1th day, there was a 1 on a with foo.
on the 2th day, there was a 2 on b with bar.
on the 3th day, there was a 3 on c with fu.
on the 4th day, there was a 4 on d with bar.
=> [nil, nil, nil, nil]
So, if you want to return string instead of puts it you just need to remove puts from block
a = [1, 2, 3, 4]
b = ['a', 'b', 'c', 'd']
c = [:foo, :bar, :fu, :bar]
a.map.with_index do |item, index|
"on the #{index + 1}th day, there was a #{index + 1} on #{b[index]} with #{c[index]}."
end
It will returns Array
=> ["on the 1th day, there was a 1 on a with foo.", "on the 2th day, there was a 2 on b with bar.", "on the 3th day, there was a 3 on c with fu.", "on the 4th day, there was a 4 on d with bar."]
a = [1, 2, 3, 4]
b = %w|a b c d| #=> ["a", "b", "c", "d"]
c = %w|w x y z| #=> ["w", "x", "y", "z"]
a.each_index do |i|
puts "on day %d, there was a %d on %s. Add %s." % [i+1, a[i], b[i], c[i]]
end
on day 1, there was a 1 on a. Add w.
on day 2, there was a 2 on b. Add x.
on day 3, there was a 3 on c. Add y.
on day 4, there was a 4 on d. Add z.
or
[a,b,c].transpose.each.with_index(1) do |arr,i|
puts "on day %d, there was a %d on %s. Add %s." % [i,*arr]
end
on day 1, there was a 1 on a. Add w.
on day 2, there was a 2 on b. Add x.
on day 3, there was a 3 on c. Add y.
on day 4, there was a 4 on d. Add z.
Note
[a,b,c].transpose
#=> [[1, "a", "w"],
# [2, "b", "x"],
# [3, "c", "y"],
# [4, "d", "z"]]

Ruby each and collect change array of arrays

I expected that Array.each and Array.collect would never change an object, like in this example:
a = [1, 2, 3]
a.each { |x| x = 5 }
a #output => [1, 2, 3]
But this doesn't seem to be the case when you are working with an array of arrays or an array of hashes:
a = [[1, 2, 3], [10, 20], ["a"]]
a.each { |x| x[0]=5 }
a #output => [[5, 2, 3], [5, 20], [5]]
Is this behaviour expected or am I doing something wrong?
Doesn't this make ruby behaviour a little unexpected? For example, in C++ if a function argument is declared const, one can be confident the function won't mess with it (ok, it can be mutable, but you got the point).
a = [[1, 2, 3], [10, 20], ["a"]]
a.each { |x| x[0]=5 }
In the above example, x is an array ( which you are passing to the block in each iteration ), from which you are accessing an element from its 0th index, and updating it. As array is mutable object, it also updating. Here a is an array of array.
In 1st iteration x is [1, 2, 3]. Now you are calling, Array#[]= method to update the 0th element of [1, 2, 3].
In 2nd iteration x is [10, 20]. same as above.
..and so on.. Thus after #each has completed its iterations, you got modified a.
a = [1, 2, 3]
a.each { |x| x = 5 }
In the above example, you are passing the array element to the each block, which are Fixnum object, and not mutable also. Here a ia an array of elements, and you are just accessing those elements.
update ( to clear OP's comment )
a = [[1, 2, 3], [10, 20], ["a"]]
a.each do |x|
# here x is holding the object from the source array `a`.
x # => [1, 2, 3]
x.object_id # => 72635790
# here you assgined a new array object, which has the same content as the
# inner array element [1, 2, 3]. But strictly these are 2 different object. Check
# out the object_id of those two.
x = [1, 2, 3]
x # => [1, 2, 3]
x.object_id # => 72635250
break # used break to stop iteration after 1st one.
end
Using each or map does not change the array itself. But is might look like it changes elements in the array. In fact when a array is holding references to other object, that references are keep unchanged, but the referenced object itself can change. I agree it is surprising when you learn it.
What you noticed:
a = ['a', 'b', 'c']
a.each { |x| x[0] = 'x' }
puts a # => ['x', 'x', 'x']
Here the first array element still references the same string, but the string has change.
Why it is important to understand this references?
array = ['a', 'b', 'c']
a = array
b = array
puts b # => ['a', 'b', 'c']
a[0] = 'x'
puts b # => ['x', 'b', 'c']
Does freeze protect us from changes?
a = ['a', 'b', 'c'].freeze
a << ['d'] # throws 'can't modify frozen Array (RuntimeError)'
Seems so. But again only for the array itself. It does not deep freeze the array.
a[0][0] = 'x'
puts a.inspect ['x', 'b', 'c']
I suggest the read about topics like referenced objects, pointers, call by value vs. call by reference.

Reposition an element to the front of an array in Ruby

Even coming from javascript this looks atrocious to me:
irb
>> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
>> a.unshift(a.delete('c'))
=> ["c", "a", "b"]
Is there a more legible way placing an element to the front of an array?
Edit my actual code:
if #admin_users.include?(current_user)
#admin_users.unshift(#admin_users.delete(current_user))
end
Maybe this looks better to you:
a.insert(0, a.delete('c'))
Maybe Array#rotate would work for you:
['a', 'b', 'c'].rotate(-1)
#=> ["c", "a", "b"]
This is a trickier problem than it seems. I defined the following tests:
describe Array do
describe '.promote' do
subject(:array) { [1, 2, 3] }
it { expect(array.promote(2)).to eq [2, 1, 3] }
it { expect(array.promote(3)).to eq [3, 1, 2] }
it { expect(array.promote(4)).to eq [1, 2, 3] }
it { expect((array + array).promote(2)).to eq [2, 1, 3, 1, 2, 3] }
end
end
sort_by proposed by #Duopixel is elegant but produces [3, 2, 1] for the second test.
class Array
def promote(promoted_element)
sort_by { |element| element == promoted_element ? 0 : 1 }
end
end
#tadman uses delete, but this deletes all matching elements, so the output of the fourth test is [2, 1, 3, 1, 3].
class Array
def promote(promoted_element)
if (found = delete(promoted_element))
unshift(found)
end
self
end
end
I tried using:
class Array
def promote(promoted_element)
return self unless (found = delete_at(find_index(promoted_element)))
unshift(found)
end
end
But that failed the third test because delete_at can't handle nil. Finally, I settled on:
class Array
def promote(promoted_element)
return self unless (found_index = find_index(promoted_element))
unshift(delete_at(found_index))
end
end
Who knew a simple idea like promote could be so tricky?
Adding my two cents:
array.select{ |item| <condition> } | array
Pros:
Can move multiple items to front of array
Cons:
This will remove all duplicates unless it's the desired outcome.
Example - Move all odd numbers to the front (and make array unique):
data = [1, 2, 3, 4, 3, 5, 1]
data.select{ |item| item.odd? } | data
# Short version:
data.select(&:odd?) | data
Result:
[1, 3, 5, 2, 4]
Another way:
a = [1, 2, 3, 4]
b = 3
[b] + (a - [b])
=> [3, 1, 2, 4]
If by "elegant" you mean more readable even at the expense of being non-standard, you could always write your own method that enhances Array:
class Array
def promote(value)
if (found = delete(value))
unshift(found)
end
self
end
end
a = %w[ a b c ]
a.promote('c')
# => ["c", "a", "b"]
a.promote('x')
# => ["c", "a", "b"]
Keep in mind this would only reposition a single instance of a value. If there are several in the array, subsequent ones would probably not be moved until the first is removed.
In the end I considered this the most readable alternative to moving an element to the front:
if #admin_users.include?(current_user)
#admin_users.sort_by{|admin| admin == current_user ? 0 : 1}
end
If all the elements in the array are unique you can use array arithmetic:
> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
> a -= "c"
=> ["a", "b"]
> a = ["c"] + a
=> ["c", "a", "b"]
Building on above:
class Array
def promote(*promoted)
self - (tail = self - promoted) + tail
end
end
[1,2,3,4].promote(5)
=> [1, 2, 3, 4]
[1,2,3,4].promote(4)
=> [4, 1, 2, 3]
[1,2,3,4].promote(2,4)
=> [2, 4, 1, 3]

Ruby: Array contained in Array, any order [duplicate]

This question already has answers here:
Check if an array is subset of another array in Ruby
(4 answers)
Closed 6 years ago.
Suppose I have the following Ruby code:
array_1 = ['a', 'b']
array_2 = ['a', 'b', 'c']
some_function(array_1, array_2) # => True
some_function(array_2, array_1) # => False
some_function(['a', 'b'], ['a', 'd']) # => False
some_function(['x', 'y'], array_2) # => False
I am pretty much looking for some_function to return True when Parameter 2 contains all of the elements in Parameter 1.
def f a,b
(a-b).empty?
end
From a previous post,
def f a,b
(a-b).empty?
end
will not work the way you expect, for example:
a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
a2 = [2, 3, 5, 9]
(a1-a2).empty? # returns true
however,
a1-a2 # returns [1, 4, 6, 7, 8], not empty
thus f returns false.
A more accurate solution, if you want a one-liner would be:
def f a,b
a&b == b
end
a&b will return all elements that are in both a and b then we check to see if that is equal to b
For ambiguity sake:
def f a,b
(a&b == a) || (a&b == b)
end
def f a,b
tmp = a.map(|i| b.include?(i))
tmp.include?(false)
end

Basic Array Iteration in Ruby

What's a better way to traverse an array while iterating through another array? For example, if I have two arrays like the following:
names = [ "Rover", "Fido", "Lassie", "Calypso"]
breeds = [ "Terrier", "Lhasa Apso", "Collie", "Bulldog"]
Assuming the arrays correspond with one another - that is, Rover is a Terrier, Fido is a Lhasa Apso, etc. - I'd like to create a dog class, and a new dog object for each item:
class Dog
attr_reader :name, :breed
def initialize(name, breed)
#name = name
#breed = breed
end
end
I can iterate through names and breeds with the following:
index = 0
names.each do |name|
Dog.new("#{name}", "#{breeds[index]}")
index = index.next
end
However, I get the feeling that using the index variable is the wrong way to go about it. What would be a better way?
dogs = names.zip(breeds).map { |name, breed| Dog.new(name, breed) }
Array#zip interleaves the target array with elements of the arguments, so
irb> [1, 2, 3].zip(['a', 'b', 'c'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
You can use arrays of different lengths (in which case the target array determines the length of the resulting array, with the extra entries filled in with nil).
irb> [1, 2, 3, 4, 5].zip(['a', 'b', 'c'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'], [4, nil], [5, nil] ]
irb> [1, 2, 3].zip(['a', 'b', 'c', 'd', 'e'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
You can also zip more than two arrays together:
irb> [1,2,3].zip(['a', 'b', 'c'], [:alpha, :beta, :gamma])
#=> [ [1, 'a', :alpha], [2, 'b', :beta], [3, 'c', :gamma] ]
Array#map is a great way to transform an array, since it returns an array where each entry is the result of running the block on the corresponding entry in the target array.
irb> [1,2,3].map { |n| 10 - n }
#=> [ 9, 8, 7 ]
When using iterators over arrays of arrays, if you give a multiple parameter block, the array entries will be automatically broken into those parameters:
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each { |array| p array }
[ 1, 'a' ]
[ 2, 'b' ]
[ 3, 'c' ]
#=> nil
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each do |num, char|
...> puts "number: #{num}, character: #{char}"
...> end
number 1, character: a
number 2, character: b
number 3, character: c
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
Like Matt Briggs mentioned, #each_with_index is another good tool to know about. It iterates through the elements of an array, passing a block each element in turn.
irb> ['a', 'b', 'c'].each_with_index do |char, index|
...> puts "character #{char} at index #{index}"
...> end
character a at index 0
character b at index 1
character c at index 2
#=> [ 'a', 'b', 'c' ]
When using an iterator like #each_with_index you can use parentheses to break up array elements into their constituent parts:
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each_with_index do |(num, char), index|
...> puts "number: #{num}, character: #{char} at index #{index}"
...> end
number 1, character: a at index 0
number 2, character: b at index 1
number 3, character: c at index 2
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
each_with_index leaps to mind, it is a better way to do it the way you are doing it. rampion has a better overall answer though, this situation is what zip is for.
This is adapted from Flanagan and Matz, "The Ruby Programming Language", 5.3.5 "External Iterators", Example 5-1, p. 139:
++++++++++++++++++++++++++++++++++++++++++
require 'enumerator' # needed for Ruby 1.8
names = ["Rover", "Fido", "Lassie", "Calypso"]
breeds = ["Terrier", "Lhasa Apso", "Collie", "Bulldog"]
class Dog
attr_reader :name, :breed
def initialize(name, breed)
#name = name
#breed = breed
end
end
def bundle(*enumerables)
enumerators = enumerables.map {|e| e.to_enum}
loop {yield enumerators.map {|e| e.next} }
end
bundle(names, breeds) {|x| p Dog.new(*x) }
+++++++++++++++++++++++++++++++++++++++++++
Output:
#<Dog:0x10014b648 #name="Rover", #breed="Terrier">
#<Dog:0x10014b0d0 #name="Fido", #breed="Lhasa Apso">
#<Dog:0x10014ab80 #name="Lassie", #breed="Collie">
#<Dog:0x10014a770 #name="Calypso", #breed="Bulldog">
which I think is what we wanted!
As well as each_with_index (mentioned by Matt), there's each_index. I sometimes use this because it makes the program more symmetrical, and therefore wrong code will look wrong.
names.each_index do |i|
name, breed = dogs[i], breeds[i] #Can also use dogs.fetch(i) if you want to fail fast
Dog.new(name, breed)
end

Resources