I am trying to iterate over an array and conditionally increment a counter. I am using index to compare to other array's elements:
elements.each_with_index.with_object(0) do |(element, index), diff|
diff += 1 unless other[index] == element
end
I can't get diff to change value even when changing it unconditionally.
This can be solved with inject:
elements.each_with_index.inject(0) do |diff, (element, index)|
diff += 1 unless other[index] == element
diff
end
But I am wondering if .each_with_index.with_object(0) is a valid construction and how to use it?
From ruby docs for each_with_object
Note that you can’t use immutable objects like numbers, true or false
as the memo. You would think the following returns 120, but since the
memo is never changed, it does not.
(1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
So each_with_object does not work on immutable objects like integer.
You want to count the number of element wise differences, right?
elements = [1, 2, 3, 4, 5]
other = [1, 2, 0, 4, 5]
# ^
I'd use Array#zip to combine both arrays element wise and Array#count to count the unequal pairs:
elements.zip(other).count { |a, b| a != b } #=> 1
Related
I want to create a "bubble sort" method, which means that I take two consecutive elements in an array, compare them and if the left element is greater than the right element, they should switch the position. I want to repeat it until my array is sorted in ascending order.
My code only works partially. If my array is too big nothing will happen (I have to quit ruby with CTRL + C). With arrays smaller than 5 elements my code works fine:
def bubbles(array)
while array.each_cons(2).any? { |a, b| (a <=> b) >= 0 }
# "true" if there are two consecutives elements where the first one
# is greater than the second one. I know the error must be here somehow.
array.each_with_index.map do | number, index |
if array[index + 1].nil?
number
break
elsif number > array[index + 1]
array[index], array[index + 1] = array[index + 1], array[index] # Swap position!
else
number
end
end
end
p array
end
If I call my method with an array with 4 elements, it works fine:
bubbles([1, 5, 8, 3]) # => [1, 3, 5, 8]
If I call it with a bigger array, it doesn't work:
bubbles([5, 12, 2, 512, 999, 1, 2, 323, 2, 12]) # => Nothing happens. I have to quit ruby with ctrl + c.
Have I somehow created an infinite loop with my while statement?
The problem is in your stop condition. You won't stop until you have an array where each element is lesser than the next. But in your long array you have duplicated elements, so the sorted elements will have adjacent elements that are equal to each other.
Not being too fancy with your code will make your life easier :)
while array.each_cons(2).any? { |a, b| a > b }
I suggest you determine if the array is ordered in a separate method (and don't print the array from within the method:
def bubbles(array)
until ordered?(array)
...
end
array
end
Here's one way (among many) to define ordered?:
def ordered?(array)
enum = array.to_enum
loop do
return false if enum.next > enum.peek
end
true
end
ordered? [1,2,3,4,5] #=> true
ordered? [1,2,4,3,4] #=> false
Also, your code mutates the argument it receives (array), which is probably undesirable. You can avoid that by working on a copy, array.dup.
I have this little problem with this code: I want to pass parameter to this random_select method in form of array, and I want to have one random index returned. I know that It won't work due to the way that range operator works , for such purposes we have sample method. But can anyone explain me why is this code returning nil as one of its random value?
def random_select(array)
array[rand(array[0]..array[4])]
end
p random_select([1,2,3,4,5])
Because your range is accessing to array values, not to array indexes:
array = [1, 2, 3, 4, 5]
array[0] #=> 1
array[4] #=> 5
array[0]..array[4] #=> 1..5
What you want is achievable in this way:
def random_select(array)
indexes = 0...array.size #=> 0...5
array[rand(indexes)]
end
array = [1, 2, 3, 4, 5]
Array.new(100) { random_select array }.uniq.sort == array #=> true
Have you tried using sample?
def random_select(array)
array.sample
end
Choose a random element or n random elements from the array.
The elements are chosen by using random and unique indices into the
array in order to ensure that an element doesn’t repeat itself unless
the array already contained duplicate elements.
If the array is empty the first form returns nil and the second form
returns an empty array.
The optional rng argument will be used as the random number generator.
I can't seem to go beyond using Enumerators in a trivial way.
Maybe it's actually more limited than I realize.
Here is an example where I thought Enumerators would be handy, but couldn't implement them:
Let array = [0, 1, 2, "+", 4].
I'm interested in creating the following Enumerator-behavior:
iterate through the array
yield all objects that are not "+"
if it is a "+", delete that element and the previous two elements,
yield their sum, and reset the enumeration sequence to the
beginning.
Maybe the problem is that I can't "delete" something that has already been yielded? In other words, I can't un-yield? I'm interested in this behavior because of its ability to rewind the sequence at any given position after mutating the array (which messes up the order of iteration).
Enumerator does not have a previous method, it does not allow you to skip values while iterating and it does not have a method to delete elements.
So I think that you can't do what you want with the exact workflow you posted although I am sure it is possible to create the behaviour you seek when it is possible to deviate from your workflow.
Summary - this option seems to meet your criteria as expressed:
[0, 1, 2, "+", 4].inject([]) { |n,e| n << (e == "+" ? n.pop(2).reduce(:+) : e) }
# => [0, 3, 4]
Here was my thinking on it...
Option 1:
Use each_with_index and that way you can identify the previous elements easily.
array.each_with_index do |element, index|
your_logic_here
end
Option 2:
Or to avoid mutating the array you could just simply build a new one like this:
new_array = []
array.each do |element|
if element == "+"
ele_sum = new_array.pop(2)
new_array << (ele_sum.reduce :+)
else
new_array << element
end
end
And if you don't find it confusing you can combine the two lines in the if statement to:
new_array << new_array.pop(2).reduce(:+)
Or if you're okay with the ternary operator shorten the whole thing to:
def some_function(array)
new_array = []
array.each { |element| new_array << (element == "+" ? new_array.pop(2).reduce(:+) : element) }
end
> array = [0, 1, 2, "+", 4]
> some_function(array)
# => [0, 3, 4]
Works and tested in irb
...or use inject of course then you can do it all inline:
array.inject([]) { |new_array, element| new_array << (element == "+" ? new_array.pop(2).reduce(:+) : element) }
[0, 1, 2, "+", 4].inject([]) { |n,e| n << (e == "+" ? n.pop(2).reduce(:+) : e) }
# => [0, 3, 4]
But I prefer the expanded version for readability
I try to clean my Code. The first Version uses each_with_index. In the second version I tried to compact the code with the Enumerable.inject_with_index-construct, that I found here.
It works now, but seems to me as obscure as the first code.
Add even worse I don't understand the brackets around element,index in
.. .inject(groups) do |group_container, (element,index)|
but they are necessary
What is the use of these brackets?
How can I make the code clear and readable?
FIRST VERSION -- WITH "each_with_index"
class Array
# splits as good as possible to groups of same size
# elements are sorted. I.e. low elements go to the first group,
# and high elements to the last group
#
# the default for number_of_groups is 4
# because the intended use case is
# splitting statistic data in 4 quartiles
#
# a = [1, 8, 7, 5, 4, 2, 3, 8]
# a.sorted_in_groups(3) # => [[1, 2, 3], [4, 5, 7], [8, 8]]
#
# b = [[7, 8, 9], [4, 5, 7], [2, 8]]
# b.sorted_in_groups(2) {|sub_ary| sub_ary.sum } # => [ [[2, 8], [4, 5, 7]], [[7, 8, 9]] ]
def sorted_in_groups(number_of_groups = 4)
groups = Array.new(number_of_groups) { Array.new }
return groups if size == 0
average_group_size = size.to_f / number_of_groups.to_f
sorted = block_given? ? self.sort_by {|element| yield(element)} : self.sort
sorted.each_with_index do |element, index|
group_number = (index.to_f / average_group_size).floor
groups[group_number] << element
end
groups
end
end
SECOND VERSION -- WITH "inject" AND index
class Array
def sorted_in_groups(number_of_groups = 4)
groups = Array.new(number_of_groups) { Array.new }
return groups if size == 0
average_group_size = size.to_f / number_of_groups.to_f
sorted = block_given? ? self.sort_by {|element| yield(element)} : self.sort
sorted.each_with_index.inject(groups) do |group_container, (element,index)|
group_number = (index.to_f / average_group_size).floor
group_container[group_number] << element
group_container
end
end
end
What is the use of these brackets?
It's a very nice feature of ruby. I call it "destructuring array assignment", but it probably has an official name too.
Here's how it works. Let's say you have an array
arr = [1, 2, 3]
Then you assign this array to a list of names, like this:
a, b, c = arr
a # => 1
b # => 2
c # => 3
You see, the array was "destructured" into its individual elements. Now, to the each_with_index. As you know, it's like a regular each, but also returns an index. inject doesn't care about all this, it takes input elements and passes them to its block as is. If input element is an array (elem/index pair from each_with_index), then we can either take it apart in the block body
sorted.each_with_index.inject(groups) do |group_container, pair|
element, index = pair
# or
# element = pair[0]
# index = pair[1]
# rest of your code
end
Or destructure that array right in the block signature. Parentheses there are necessary to give ruby a hint that this is a single parameter that needs to be split in several.
Hope this helps.
lines = %w(a b c)
indexes = lines.each_with_index.inject([]) do |acc, (el, ind)|
acc << ind - 1 if el == "b"
acc
end
indexes # => [0]
What is the use of these brackets?
To understand the brackets, first you need to understand how destruction works in ruby. The simplest example I can think of this this:
1.8.7 :001 > [[1,3],[2,4]].each do |a,b|
1.8.7 :002 > puts a, b
1.8.7 :003?> end
1
3
2
4
You should know how each function works, and that the block receives one parameter. So what happens when you pass two parameters? It takes the first element [1,3] and try to split (destruct) it in two, and the result is a=1 and b=3.
Now, inject takes two arguments in the block parameter, so it is usually looks like |a,b|. So passing a parameter like |group_container, (element,index)| we are in fact taking the first one as any other, and destructing the second in two others (so, if the second parameter is [1,3], element=1 and index=3). The parenthesis are needed because if we used |group_container, element, index| we would never know if we are destructing the first or the second parameter, so the parenthesis there works as disambiguation.
9In fact, things works a bit different in the bottom end, but lets hide this for this given question.)
Seems like there already some answers given with good explanation. I want to add some information regards the clear and readable.
Instead of the solution you chose, it is also a possibility to extend Enumerable and add this functionality.
module Enumerable
# The block parameter is not needed but creates more readable code.
def inject_with_index(memo = self.first, &block)
skip = memo.equal?(self.first)
index = 0
self.each_entry do |entry|
if skip
skip = false
else
memo = yield(memo, index, entry)
end
index += 1
end
memo
end
end
This way you can call inject_with_index like so:
# m = memo, i = index, e = entry
(1..3).inject_with_index(0) do |m, i, e|
puts "m: #{m}, i: #{i}, e: #{e}"
m + i + e
end
#=> 9
If you not pass an initial value the first element will be used, thus not executing the block for the first element.
In case, someone is here from 2013+ year, you have each_with_object and with_index for your needs:
records.each_with_object({}).with_index do |(record, memo), index|
memo[record.uid] = "#{index} in collection}"
end
(1..4).collect do |x|
next if x == 3
x + 1
end # => [2, 3, nil, 5]
# desired => [2, 3, 5]
If the condition for next is met, collect puts nil in the array, whereas what I'm trying to do is put no element in the returned array if the condition is met. Is this possible without calling delete_if { |x| x == nil } on the returned array?
My code excerpt is heavily abstracted, so looking for a general solution to the problem.
There is method Enumerable#reject which serves just the purpose:
(1..4).reject{|x| x == 3}.collect{|x| x + 1}
The practice of directly using an output of one method as an input of another is called method chaining and is very common in Ruby.
BTW, map (or collect) is used for direct mapping of input enumerable to the output one. If you need to output different number of elements, chances are that you need another method of Enumerable.
Edit: If you are bothered by the fact that some of the elements are iterated twice, you can use less elegant solution based on inject (or its similar method named each_with_object):
(1..4).each_with_object([]){|x,a| a << x + 1 unless x == 3}
I would simply call .compact on the resultant array, which removes any instances of nil in an array. If you'd like it to modify the existing array (no reason not to), use .compact!:
(1..4).collect do |x|
next if x == 3
x
end.compact!
In Ruby 2.7+, it’s possible to use filter_map for this exact purpose. From the docs:
Returns an array containing truthy elements returned by the block.
(0..9).filter_map {|i| i * 2 if i.even? } #=> [0, 4, 8, 12, 16]
{foo: 0, bar: 1, baz: 2}.filter_map {|key, value| key if value.even? } #=> [:foo, :baz]
For the example in the question: (1..4).filter_map { |x| x + 1 unless x == 3 }.
See this post for comparison with alternative methods, including benchmarks.
just a suggestion, why don't you do it this way:
result = []
(1..4).each do |x|
next if x == 3
result << x
end
result # => [1, 2, 4]
in that way you saved another iteration to remove nil elements from the array. hope it helps =)
i would suggest to use:
(1..4).to_a.delete_if {|x| x == 3}
instead of the collect + next statement.
You could pull the decision-making into a helper method, and use it via Enumerable#reduce:
def potentially_keep(list, i)
if i === 3
list
else
list.push i
end
end
# => :potentially_keep
(1..4).reduce([]) { |memo, i| potentially_keep(memo, i) }
# => [1, 2, 4]