How do I iterate the elements of an array in Ruby and in each step I can do something with the array containing all elements, except the one that is iterated in the moment?
For instance for [4,2,8] I iterate the elements and then I can do something with
[2,8]
[4,8]
[4,2]
It's not really directly possible (unless you do not need the missing element). But you can program it yourself:
Option 1 - just do it:
a = [11,22,33,44,55]
a.each_with_index { |e,i|
p e
p a.take(i) + a[i+1..-1]
}
Option 2 - integrate with Array:
class Array
def each_excluded(&block)
self.each_with_index { |e, i|
yield(e, self.take(i) + self[i+1..-1])
}
end
end
a.each_excluded { |e, rest|
p e
p rest
}
Output from either one:
11
[22, 33, 44, 55]
22
[11, 33, 44, 55]
33
[11, 22, 44, 55]
44
[11, 22, 33, 55]
55
[11, 22, 33, 44]
You can use the slice method and create a new array with the items except for the one in which have the index for.
[4, 2, 8].tap{|a| a.length.times{|i|
do_something_you_want_with(a[0...i]+a[i+1..-1])
}}
or
class Array
def each_but_one &pr
length.times{|i| pr.call(self[0...i]+self[i+1..-1])}
end
end
[4, 2, 8].each_but_one{|a| do_something_you_want_with(a)}
It really looks like you want Array#combination:
[4,2,8].combination(2).each do |ary|
p ary
end
Which outputs:
[4, 2]
[4, 8]
[2, 8]
Each sub-array created is yielded to the block, in this case to ary, so you can do what you want with the values.
Related
I'm new to ruby, and clearly see and find online that the following fails:
arr = [10, 20, 30, 40]
arr.each.with_index do |elmt, i|
print "#{elmt}, #{i}, "
arr.delete_at(i) if elmt == 20
puts arr.length
end
Clearly the delete_at is interacting with the iterator, but I cannot find a clear description of how the iterator and delete_at work such that this is so.
(BTW, I understand solutions that work - I'm not looking for a correct way to do this, I'm trying to understand the semantics such that I know why this is not doing what's expected.)
For completeness, here's the output
10, 0, 4
20, 1, 3
40, 2, 3
=> [10, 30, 40]
lightbulb!!
It seems clear the delete_at immediately adjusts the underlying data structure, left shifting all elements to the right of the deleted item. The array seems more like a linked list than an array. So the "next" item (here "30") is now arr[1] (which the iterator has already processed), and when the iterator increments to arr[2], it sees "40". arr[3] returns nil and puts seems to do nothing with nil.
look at:
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/include/ruby/intern.h
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/enumerator.c
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/array.c
each gives you an enumerator, and the with_index works with the enumerator.
when you reach element 20 you print it out, and after that you erase it, and at this point Ruby effectively shifts down all elements in the array. Now the enumerator which is backed by the array picks up the next element which is 40 because everything got shifted down (30 was copied over 20, 40 was copied over 30 and array was resized)
take a look at:
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/array.c#L3023
It's where the magic of moving the elements via a memmove happens.
Let's step through it:
arr = [10, 20, 30, 40]
enum0 = arr.each
#=> #<Enumerator: [10, 20, 30, 40]:each>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [10, 20, 30, 40]:each>:with_index>
We can see the contents of enum1 by converting it to an array:
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
This tells us that Enumerator#each (which will invoke Array#each) will pass the four elements of the enumerator enum1 into the block, assigning them in turn to the block variables. The first is:
elmt, i = enum1.next
#=> [10, 0]
puts elmt, i
# 10
# 0
elmt == 20
#=> false
so arr.delete_at(i) is not executed.
Neither arr nor enum1 have been altered:
arr
#=> [10, 20, 30, 40]
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
each now passes the next element of enum1 into the block:
elmt, i = enum1.next
#=> [20, 1]
elmt == 20
#=> true
so we execute:
arr.delete_at(i)
#=> [10, 20, 30, 40].delete_at(1)
#=> 20
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
Ah! So the enumerator has been changed as well as arr. That makes perfect sense, because when the enumerator is created a reference to the original receiver is established, together with rules for what is to be be done with it. Changes to the receiver will therefore affect the enumerator.
We can use Enumerator#peek to see what will be the next element of enum1 that each will pass into the block:
enum1.peek
#=> [40, 2]
So you see that each moves on to the next indexed position, oblivious to the fact that an earlier element has been removed, causing the later elements to each shift down by one position, causing each to skip [30,1].
elmt, i = enum1.next
#=> [40, 2]
elmt == 20
#=> false
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
At this point each reaches the end of the enumerator, so it's job is finished. It therefore returns the original receiver, arr, but that has been modified, so we get:
[10, 30, 40]
A better example might be:
arr = [10, 20, 20, 40]
where:
[10, 20, 40]
would be returned.
I have a hash of ids and their scores, it's something like this:
#objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
How can I pick the top five and drop the rest ?
I'm doing this:
#orderedObject = #objects.sort_by {|k,v| v}.reverse
=>[[29, 97], [39, 58], [1, 57], [55, 47], [32, 39], [17, 27], [4, 12], [3, 9], [5, 3]]
Then I do this:
only Keys of the #orderedObjects:
#keys = #orderedObject.map { |key, value| key }
which gives me:
=>[29, 39, 1, 55, 32, 17, 4, 3, 5]
ALL I need is [29, 39, 1, 55, 32] the first 5 indexes. But I'm stuck I don't know how to do this.
You can do
#objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
#objects.sort_by { |_, v| -v }[0..4].map(&:first)
# => [29, 39, 1, 55, 32]
#objects.sort_by { |_, v| -v }.first(5).map(&:first)
# => [29, 39, 1, 55, 32]
May i suggest this more verbose requires ruby > 1.9
Hash[#objects.sort_by{|k,v| -v}.first(5)].keys
A variant of Prof. Arup's answer:
objects = {1=>57, 4=>12, 3=>9, 5=>3, 55=>47, 32=>39, 17=>27, 29=>97, 39=>58}
objects.sort_by { |k,v| -v }.first(5).to_h.keys #=> [29, 39, 1, 55, 32]
Now suppose 3=>9 were instead 3=>39 and you wanted the keys corresponding to the top 5 values (which, in this case, would be 6 keys, as 39 is the fifth largest value, 3=>39 and 32=>39), you could first compute:
threshold = objects.values.sort.last(5).min #=> 39
If you wanted the keys to be ordered by the order of values threshold or larger,
objects.select { |_,v| v >= threshold }.sort_by { |_,v| -v }.map(&:first)
#=> [29, 39, 1, 55, 3, 32]
If you don't care about the order,
objects.select { |_,v| v >= threshold }.keys #=> [1, 3, 55, 32, 29, 39]
I want to build a custom method Array#drop_every(n) (I know it's monkey patching, I am doing this for a homework), which returns a new array omitting every nth element:
[4, 8, 15, 16, 23, 42].drop_every(2) # [4, 15, 23]
I want to implement it with Array#delete_if, but by referring to the index and not to the element itself, (similar to each_index) something like this:
def drop_every(step)
self.delete_if { |index| index % step == 0 }
end
How do I do this? I don't insist on using delete_if, I also looked at drop_while and reject, other suggestions are welcome.
You can use with_index method that returns enumerator, filter your collection and then get rid of the indexes.
class Array
def drop_every(step)
self.each.with_index.select { |_, index| index % step == 0 }.map(&:first)
end
end
[4, 8, 15, 16, 23, 42].drop_every(2) # => [4, 15, 23]
def drop_every(step)
reject.with_index { |x,i| (i+1) % step == 0 }
end
[4, 8, 15, 16, 23, 42].reject.with_index{|x,i| (i+1) % 2 == 0}
# => [4, 15, 23]
[4, 8, 15, 16, 23, 42].reject.with_index{|x,i| (i+1) % 3 == 0}
# => [4, 8, 16, 23]
You could use the values_at method to selectively filter out indices which you want.
class Array
def drop_every(step)
self.values_at(*(0...self.size).find_all{ |x| (x+1) % step != 0 })
end
end
The answer was accepted while I was typing it. I will post it anyways.
def drop_every step
delete_if.with_index(1){|_, i| i.%(step).zero?}
end
class Array
def drop_every(step)
self.each_slice(step).flat_map{|slice| slice[0..-2]}
end
end
p [4, 8, 15, 16, 23, 42].drop_every(2) #=> [4, 15, 23]
I'd extend the Enumerable mixin instead:
module Enumerable
def drop_every(step)
return to_enum(:drop_every, step) unless block_given?
each.with_index(1) do |o, i|
yield o unless i % step == 0
end
end
end
(1..10).drop_every(3) { |a| p a }
# outputs below
1
2
4
5
7
8
10
I need a ruby formula to create an array of integers. The array must be every other 2 numbers as follows.
[2, 3, 6, 7, 10, 11, 14, 15, 18, 19...]
I have read a lot about how I can do every other number or multiples, but I am not sure of the best way to achieve what I need.
Here's an approach that works on any array.
def every_other_two arr
arr.select.with_index do |_, idx|
idx % 4 > 1
end
end
every_other_two((0...20).to_a) # => [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
# it works on any array
every_other_two %w{one two three four five six} # => ["three", "four"]
array = []
#Change 100000 to whatever is your upper limit
100000.times do |i|
array << i if i%4 > 1
end
This code works for any start number to any end limit
i = 3
j = 19
x =[]
(i...j).each do |y|
x << y if (y-i)%4<2
end
puts x
this should work
For fun, using lazy enumerables (requires Ruby 2.0 or gem enumerable-lazy):
(2..Float::INFINITY).step(4).lazy.map(&:to_i).flat_map { |x| [x, x+1] }.first(8)
#=> => [2, 3, 6, 7, 10, 11, 14, 15]
here's a solution that works with infinite streams:
enum = Enumerator.new do |y|
(2...1/0.0).each_slice(4) do |slice|
slice[0 .. 1].each { |n| y.yield(n) }
end
end
enum.first(10) #=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
enum.each do |n|
puts n
end
Single Liner:
(0..20).to_a.reduce([0,[]]){|(count,arr),ele| arr << ele if count%4 > 1;
[count+1,arr] }.last
Explanation:
Starts the reduce look with 0,[] in count,arr vars
Add current element to array if condition satisfied. Block returns increment and arr for the next iteration.
I agree though that it is not so much of a single liner though and a bit complex looking.
Here's a slightly more general version of Sergio's fine answer
module Enumerable
def every_other(slice=1)
mod = slice*2
res = select.with_index { |_, i| i % mod >= slice }
block_given? ? res.map{|x| yield(x)} : res
end
end
irb> (0...20).every_other
=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
irb> (0...20).every_other(2)
=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
irb> (0...20).every_other(3)
=> [3, 4, 5, 9, 10, 11, 15, 16, 17]
irb> (0...20).every_other(5) {|v| v*10 }
=> [50, 60, 70, 80, 90, 150, 160, 170, 180, 190]
If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:
a.zip(b).zip(c).flatten
However, how do we solve this problem if the arrays can be different sizes?
We could do something like:
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
output = Array.new
for i in 0...max_length
args.each { |elem|
output << elem[i] if i < elem.length
}
end
return output
end
But is there a better 'Ruby' way, perhaps using zip or transpose or some such?
Here is a simpler approach. It takes advantage of the order that you pass the arrays to zip:
def interleave(a, b)
if a.length >= b.length
a.zip(b)
else
b.zip(a).map(&:reverse)
end.flatten.compact
end
interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]
interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]
interleave([], [21, 22])
# => [21, 22]
interleave([], [])
# => []
Be warned: this removes all nil's:
interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]
If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops
def interleave(a,*args)
max_length = args.map(&:size).max
padding = [nil]*[max_length-a.size, 0].max
(a+padding).zip(*args).flatten.compact
end
Here is a slightly more complicated version that works if the arrays do contain nil
def interleave(*args)
max_length = args.map(&:size).max
pad = Object.new()
args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
([pad]*max_length).zip(*args).flatten-[pad]
end
Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.
Edit: Fixed the booboo.
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
output = []
max_length.times do |i|
args.each do |elem|
output << elem[i] if i < elem.length
end
end
output
end
a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
Edit: For fun
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
# assumes no values coming in will contain nil. using dup because fill mutates
args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]