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.
Related
hi i have an array that dimension after mining push to that how can i increase Qty after each pushing for similar dimension?
suppose to we have this array:
[x,y,Qty]
b=[]
b.push[100,50,1]
b.push[20,30,1]
b.push[100,50,1]
b.push[10,60,1]
how can i have this result: b = [ [100,50,2],[20,30,1],[10,60,1] ]
Suppose we are given the following (which differs slightly from the example).
b = [[100, 50, 1], [20, 30, 2], [100, 50, 1], [10, 60, 1]]
Let's begin with a relatively inefficient solution and then see how it can be improved.
Staightforward but relatively inefficient solution
We first compute the following array.
arr = b.map { |x,y,z| [x, y] }
#=> [[100, 50], [20, 30], [100, 50], [10, 60]]
Before continuing suppose b were to change in future such that each element contain four, rather than three, values, such as [100, 50, 20, 2]. We could change the calculation of arr to arr = b.map { |w,x,y,z| [w, x, y] }, but a better solution is to use the splat operator, *, to perform Array#decomposition. By using the splat operator we need not change this code if the size of the elements of b changes.
arr = b.map { |*a,z| a }
#=> [[100, 50], [20, 30], [100, 50], [10, 60]]
For each element (array) e of b, the array a will contain all but the last element of e and z will contain the last element of e, regardless of the size of e.
Next we use the method Array#uniq to obtain the unique elements of arr.
arr_uniq = arr.uniq
#=> [[100, 50], [20, 30], [10, 60]]
We may then compute the desired array as follows.
arr_uniq.map do |a|
tot = 0
b.each { |*c,z| tot += z if a == c }
[*a, tot]
end
#=> [[100, 50, 2], [20, 30, 2], [10, 60, 1]]
Recall that tot += z is effectively the same as tot = tot + z.
This approach is relatively inefficient because the array b is traversed completely for each element of arr_uniq. We should be able to make only a single pass through b.
Improving efficiency
We step through each element of b to construct a hash.
h = {}
b.each do |*a,z|
if h.key?(a)
h[a] += z
else
h[a] = z
end
end
h #=> {[100, 50]=>2, [20, 30]=>2, [10, 60]=>1}
We now need only convert h to the desired array (though it may be more useful to stop here and use the hash for subsequent calculations).
h.map { |k,v| [*k, v] }
#=> [[100, 50, 2], [20, 30, 2], [10, 60, 1]]
We now consider how to make this calculation more Ruby-like.
Wrapping up
To improve upon the construction of h above we make use of two methods: the form of class method Hash::new that takes an argument called the default value; and Enumerable#each_with_object.
b.each_with_object(Hash.new(0)) { |(*a,z),h| h[a] += z }
.map { |k,v| [*k, v] }
#=> [[100, 50, 2], [20, 30, 2], [10, 60, 1]]
This first step is to compute a hash:
b.each_with_object(Hash.new(0)) { |(*a,z),h| h[a] += z }
#=> {[100, 50]=>2, [20, 30]=>2, [10, 60]=>1}
You may wish to review the link to array decomposition that I gave earler to see how the block variables a, z and h in |(*a,z),h| are assigned values.
If a hash is defined
h = Hash.new(0)
h[k] returns the default value, here zero, if h does not contain a key k.
Initially,
h[[100, 50]] += 1
expands to
h[[100, 50]] = h[[100, 50]] + 1
Since h has no keys at this time--specifically no key [100, 50]--h[[100, 50]] on the right returns the default value, so the expression becomes
h[[100, 50]] = 0 + 1
h #=> { [100, 50] => 1 }
Later, when the same key [100, 50] is encountered (when h #=> { [100, 50] => 1, [20, 30] => 2 }), the expresson becomes
h[[100, 50]] = 1 + 1
h #=> { [100, 50] => 2, [20, 30] => 2 }
This time, h has a key [100, 50], so h[[100, 50]] on the right returns its value, which equals 1 (and the default value is not used).
Alternative method: use Enumerable#group_by
We could alternatively compute the desired array as follows.
b.group_by { |*a,z| a }
.map { |k,v| [*k, v.sum(&:last)] }
#=> [[100, 50, 2], [20, 30, 2], [10, 60, 1]]
The first step is to compute a hash:
b.group_by { |*a,z| a }
#=> {[100, 50]=>[[100, 50, 1], [100, 50, 1]],
[20, 30]=>[[20, 30, 2]], [10, 60]=>[[10, 60, 1]]}
When compared to the method above that employs Hash::new, this calculation has the minor disadvantage that the memory requirements for the values in the intermediate are somewhat greater.
I'm tryng to do this exercise:
Create a function with two arguments that will return a list of length
(n) with multiples of (x).
Assume both the given number and the number of times to count will be
positive numbers greater than 0.
Return the results as an array (or list in Python, Haskell or Elixir).
Examples:
count_by(1,10) #should return [1,2,3,4,5,6,7,8,9,10]
count_by(2,5) #should return [2,4,6,8,10]
Quite easy, nothing to say. BUT I really do not understand why my code is not working.
PLease DO NOT GIVE ME NEW CODE OR SOLUTION, I JUST WANT TO UNDERSTAND WHY MINE DOESN'T WORK.
My solution:
def count_by(x, n)
arrays = []
arrays.push(x)
valore_x = x
unless arrays.count == n
arrays.push( x + valore_x)
x += valore_x
end
return arrays
end
count_by(3, 5)
ERROR MESSAGE =
Expected: [1, 2, 3, 4, 5], instead got: [1, 2]
✘ Expected: [2, 4, 6, 8, 10], instead got: [2, 4]
✘ Expected: [3, 6, 9, 12, 15], instead got: [3, 6]
✘ Expected: [50, 100, 150, 200, 250], instead got: [50, 100]
✘ Expected: [100, 200, 300, 400, 500], instead got: [100, 200].
So looks like that my code do not put all the numbers. Thanks.
Now you have answer on your question, so I just recommend one more variant of solution, I think it's more ruby-way :)
def count_by(x, y)
y.times.with_object([]) do |i, result|
result << x*(i+1)
end
end
Change
unless arrays.count == n
to
until arrays.count == n
unless is not a loop. It's just like an if, but the code is executed if the condition is false.
until is just like while, but the code is executed while the condition is false.
Array::new can be used here.
def count_by(x, n)
Array.new(n) { |i| x*(i+1) }
end
count_by(1,5) #=> [1, 2, 3, 4, 5]
count_by(2,5) #=> [2, 4, 6, 8, 10]
count_by(3,5) #=> [3, 6, 9, 12, 15]
count_by(50,5) #=> [50, 100, 150, 200, 250]
count_by(100,5) #=> [100, 200, 300, 400, 500]
count_by(0,5) #=> [0, 0, 0, 0, 0]
Just a few other ways to achieve the same result. Using Numeric#step:
x.step(by: x, to: x*y).entries
shorter but less readable:
x.step(x*y, x).entries
or using a range with Range#step:
(x..x*y).step(x).entries
In each of the examples entries can be replaced with to_a
My list contains
User1, 0
User2, 50
User3, 30
User1, 50
User3, 68
How do I get rid of the duplicates, but take the largest value out of them?
Like need to contain this: User1, 50, User2, 50, User3, 68 and get rid of User1, 0 and User3, 30
There are many ways to do that. Here's are three.
arr = [[:User1, 0], [:User2, 50], [:User3, 30], [:User1, 50], [:User3, 68]]
**#1 Use Hash#update **
arr.each_with_object({}) { |(u,x),h| h.update(u=>x) { |_,o,n| [o,n].max } }.to_a
#=> [[:User1, 50], [:User2, 50], [:User3, 68]]
This uses the form of Hash#update (aka Hash#merge!) that uses the block ( { |_,o,n| [o,n].max } to determine the values of keys that are present in both hashes being merged. See the doc for the interpretation of the three block variables. The first of those variables (the common key) is not used in the block calculation, so I've represented it with an underscore (which is indeed a local variable).
#2 Order the elements by the second value (using Enumerable#sort_by) and convert to a hash
arr.sort_by(&:last).to_h
#=> {:User1=>50, :User3=>68, :User2=>50}
We first compute
a = arr.sort_by(&:last)
#=> [[:User1, 0], [:User3, 30], [:User2, 50], [:User1, 50], [:User3, 68]]
When using (Array#to_h) to convert a to a hash, the steps are as follows:
h = {}
h[:User1] = 0
h #=> {:User1=>0}
h[:User3] = 30
h #=> {:User1=>0, :User3=>30}
h[:User2] = 50
h #=> {:User1=>0, :User3=>30, :User2=>50}
h[:User1] = 50
h #=> {:User1=>50, :User3=>30, :User2=>50}
h[:User3] = 68
h #=> {:User1=>50, :User3=>68, :User2=>50}
#3 Use Enumerable#group_by
arr.group_by(&:first).map { |k,v| [k, v.map(&:last).max] }
#=> [[:User1, 50], [:User2, 50], [:User3, 68]]
The steps are as follows:
h = arr.group_by(&:first)
#=> {:User1=>[[:User1, 0], [:User1, 50]],
# :User2=>[[:User2, 50]],
# :User3=>[[:User3, 30], [:User3, 68]]}
h.map { |k,v| [k, v.map(&:last).max] }
#=> [[:User1, 50], [:User2, 50], [:User3, 68]]
For the first key-value pair of h that is passed to the block, we have:
k,v = [:User1, [[:User1, 0], [:User1, 50]]]
#=> [:User1, [[:User1, 0], [:User1, 50]]]
k #=> :User1
v #=> [[:User1, 0], [:User1, 50]]
and the block calculation is:
b = v.map(&:last)
#=> [0, 50]
[k, v.map(&:last).max]
#=> [:User1, [0, 50].max]
#=> [:User1, 50]
There are a lot of ways to do this, but I'd probably start by using the group_by method that Array gets from Enumerable, like so:
users = [[user1,0],[user2,50],[user3,30],[user1,50],[user3,68]]
user_hash = users.group_by {|user_array| user_array[0]}
This will group all the items that have the same value for their first element, so user_hash will be have a structure such as:
{
user1 => [[user1,0],[user1,50]],
user2 => [[user2,50]],
user3 => [[user3,30],[user3,68]]
}
From there, you'd want to pick get the highest value from each user. The way the question is worded I'm assuming you want your results back as an array of arrays the way the input was, so, I'd go with with Enumerable#map to transform each hash pair, and Enumerable#max_by to pick the one with the largest second element:
unique_users = user_hash.map do |user,array_of_arrays|
array_of_arrays.max_by {|array| array[1]}
end
End result, unique_users is [[user1,50],[user2,50],[user3,68]]
I have an array in Ruby like [3,4,5] and I want to create sub-arrays by diving or multiplying. For example, I want to multiply each number in the array by 2, 3, and 4, returning [[6,9,12],[8,12,16],[10,15,20]]
After that, what's the best way to count the total number of units? In this example, it would be 9, while array.count would return 3.
Thanks
The simplest way I could think of was:
[3,4,5].map { |v|
[3,4,5].map { |w|
w * v
}
}
I'm sure there is a more elegant way.
As for the count you can use flatten to turn it into a single array containing all the elements.
[[9, 12, 15], [12, 16, 20], [15, 20, 25]].flatten
=> [9, 12, 15, 12, 16, 20, 15, 20, 25]
You might find it convenient to use matrix operations for this, particularly if it is one step among several involving matrices, vectors, and/or scalars.
Code
require 'matrix'
def doit(arr1, arr2)
(Matrix.column_vector(arr2) * Matrix.row_vector(arr1)).to_a
end
def nbr_elements(arr1, arr2) arr1.size * arr2.size end
Examples
arr1 = [3,4,5]
arr2 = [3,4,5]
doit(arr1, arr2)
#=> [[ 9, 12, 15],
# [12, 16, 20],
# [15, 20, 25]]
nbr_elements(arr1, arr2)
#=> 9
doit([1,2,3], [4,5,6,7])
#=> [[4, 8, 12],
# [5, 10, 15],
# [6, 12, 18],
# [7, 14, 21]]
nbr_elements([1,2,3], [4,5,6,7])
#=> 12
Alternative
If you don't want to use matrix operations, you could do it like this:
arr2.map { |e| [e].product(arr1).map { |e,f| e*f } }
Here's an example:
arr1 = [1,2,3]
arr2 = [4,5,6,7]
arr2.map { |e| [e].product(arr1).map { |e,f| e*f } }
#=> [[4, 8, 12],
# [5, 10, 15],
# [6, 12, 18],
# [7, 14, 21]]
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.