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
Related
Having two arrays of different sizes, I'd like to get the longer array as keys and the shorter one as values. However, I don't want any keys to remain empty, so that is why I need to keep iterating on the shorter array until all keys have a value.
EDIT: I want to keep array longer intact, but without empty values, that means keep iterating on shorter until all keys have a value.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
Hash[longer.zip(shorter)]
#=> {1=>"a", 2=>"b", 3=>"c", 4=>nil, 5=>nil, 6=>nil, 7=>nil}
Expected Result
#=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's an elegant one. You can "loop" the short array
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
longer.zip(shorter.cycle).to_h # => {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
A crude way until you find something more elegant:
Slice the longer array as per length of shorter one, and iterate over it to re-map the values.
mapped = longer.each_slice(shorter.length).to_a.map do |slice|
Hash[slice.zip(shorter)]
end
=> [{1=>"a", 2=>"b", 3=>"c"}, {4=>"a", 5=>"b", 6=>"c"}, {7=>"a"}]
Merge all hashes withing the mapped array into a single hash
final = mapped.reduce Hash.new, :merge
=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's a fun answer.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
h = Hash.new do |h,k|
idx = longer.index(k)
idx ? shorter[idx % shorter.size] : nil
end
#=> {}
h[1] #=> a
h[2] #=> b
h[3] #=> c
h[4] #=> a
h[5] #=> b
h[6] #=> c
h[7] #=> a
h[8] #=> nil
h #=> {}
h.values_at(3,5) #=> ["c", "b"]
If this is not good enough (e.g., if you wish to use Hash methods such as keys, key?, merge, to_a and so on), you could create the associated hash quite easily:
longer.each { |n| h[n] = h[n] }
h #=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
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
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.
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]
I have two Ruby arrays, and I need to see if they have any values in common. I could just loop through each of the values in one array and do include?() on the other, but I'm sure there's a better way. What is it? (The arrays both hold strings.)
Thanks.
Set intersect them:
a1 & a2
Here's an example:
> a1 = [ 'foo', 'bar' ]
> a2 = [ 'bar', 'baz' ]
> a1 & a2
=> ["bar"]
> !(a1 & a2).empty? # Returns true if there are any elements in common
=> true
Any value in common ? you can use the intersection operator : &
[ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
If you are looking for a full intersection however (with duplicates) the problem is more complex there is already a stack overflow here : How to return a Ruby array intersection with duplicate elements? (problem with bigrams in Dice Coefficient)
Or a quick snippet which defines "real_intersection" and validates the following test
class ArrayIntersectionTests < Test::Unit::TestCase
def test_real_array_intersection
assert_equal [2], [2, 2, 2, 3, 7, 13, 49] & [2, 2, 2, 5, 11, 107]
assert_equal [2, 2, 2], [2, 2, 2, 3, 7, 13, 49].real_intersection([2, 2, 2, 5, 11, 107])
assert_equal ['a', 'c'], ['a', 'b', 'a', 'c'] & ['a', 'c', 'a', 'd']
assert_equal ['a', 'a', 'c'], ['a', 'b', 'a', 'c'].real_intersection(['a', 'c', 'a', 'd'])
end
end
Using intersection looks nice, but it is inefficient. I would use "any?" on the first array (so that iteration stops when one of the elements is found in the second array). Also, using a Set on the second array will make membership checks fast. i.e.:
a = [:a, :b, :c, :d]
b = Set.new([:c, :d, :e, :f])
c = [:a, :b, :g, :h]
# Do a and b have at least a common value?
a.any? {|item| b.include? item}
# true
# Do c and b have at least a common value?
c.any? {|item| b.include? item}
#false
Array#intersect? (Ruby 3.1+)
Starting from Ruby 3.1, there is a new Array#intersect? method,
which checks whether two arrays have at least one element in common.
Here is an example:
a = [1, 2, 3]
b = [3, 4, 5]
c = [7, 8, 9]
# 3 is the common element
a.intersect?(b)
# => true
# No common elements
a.intersect?(c)
# => false
Also, Array#intersect? can be much faster than alternatives since it avoids creating an intermediate array, returns true as soon as it finds a common element, it is implemented in C.
Sources:
Ruby 3.1 adds Array#intersect?.
Pull Request.
Discussion.
Source code.
Try this
a1 = [ 'foo', 'bar' ]
a2 = [ 'bar', 'baz' ]
a1-a2 != a1
true