Nested if else inside .each iteration - ruby

I'm wondering if this makes sense or if the syntax is wrong and basically if this is acceptable. I wanted to nest an if/else condition within my iteration of the array.
def change_numbers(first_array, second_array)
second_array.each do |index|
if first_array[index] == 0
first_array[index] = 1
else
first_array[index] = 0
end
end
end
The array is a simple (binary) array and will only consist of 0s and 1s and I want to use the second array's elements as the indices of the first array that I am going to change.
Example:
first_array = [0, 0, 0, 0, 1, 1, 1, 1, 1]
second_array = [3, 5, 7]
Result:
first_array = [0, 0, 0, 1, 1, 0, 1, 0, 1]

If you don't want to use an if/else you can do:
second_array.each do |index|
first_array[index] = (first_array[index] + 1) % 2
end

def change_numbers(first_array, second_array)
second_array.each { |index| first_array[index] = 1 - first_array[index] }
end

A bit-wise XOR:
ar = [0, 0, 0, 0, 1, 1, 1, 1, 1]
indices = [3, 5, 7]
indices.each{|i| ar[i] ^= 1 }

You can try this -
def change_numbers(first_array, second_array)
second_array.each do |index|
first_array[index] = ((first_array[index] == 0) ? 1 : 0)
end
end

Related

Sorting a subsequence of an array without an additional array

I'm new to programming. I start to learn Ruby freshly and I can't understand how to implement an algorithm for permuting negative(or positive) elements of an array without using an additional array.
For example:
Like this: [-10, 3, -1, 0, -9 ] -> [-10, 3, -9, 0, -1 ] (sort only negatives)
Either that: [-5, 5, -8, 2, 1] -> [-5, 1, -8, 2, 5] (sort only positives)
def sort_positives(arr)
(arr.size-1).times do |i|
next if arr[i] <= 0
smallest = i
(i+1..arr.size-1).each do |j|
smallest = j if arr[j] > 0 && arr[j] < arr[smallest]
end
if smallest > i
tmp = arr[i]
arr[i] = arr[smallest]
arr[smallest] = tmp
end
end
arr
end
arr = [-2, 5, 3, -4, 6, 1, -3]
sort_positives arr
#=> [-2, 1, 3, -4, 5, 6, -3]
arr
#=> [-2, 1, 3, -4, 5, 6, -3]
sort_negatives is a straightforward modification to sort_positives.
def swap(arr, i, j)
arr[i] = arr[i] ^ arr[j]
arr[j] = arr[j] ^ arr[i]
arr[i] = arr[j] ^ arr[i]
end
def sort_negative(arr)
size = arr.size
i = 0
while i < size - 1
next if arr[i] > 0 # first index of negative number
next_neg = 0
min_neg = i
(i + 1).upto(size - 1) do |j|
if arr[i] < 0 && arr[j] < 0
next_neg = j if next_neg == 0
min_neg = j if arr[min_neg] > arr[j]
end
end
swap(arr, i, min_neg) if min_neg > i
if next_neg > 0
i = next_neg # jump to next negative number
else
break
end
end
end
arr = [-10, 0, 2, 6, 10, -1, 7, 20, 1, 0, -9, 13, 6, 88, -11, -156]
sort_negative(arr)
puts arr.inspect # [-156, 0, 2, 6, 10, -11, 7, 20, 1, 0, -10, 13, 6, 88, -9, -1]

Iterate over an array and initialize multiple variables in one line in Ruby

I am trying to iterate over an array and count the number of positive, negative and zeros in an array. Right now I am doing it like this
arr = [1, -1, 0, 2, 3, -2, -5]
pos = arr.select { |i| i > 0 }.count
neg = arr.select { |i| i < 0 }.count
zero = arr.select { |i| i == 0 }.count
puts pos
puts neg
puts zero
But is there any way where I can do this in one line? Something like this?
pos, neg, zero = arr.select { |i| i > 0; i < 0; i == 0; }.count
Use inject and the <=> operator:
neg, zero, pos = arr.inject([0,0,0]) { |a,b| a[(b<=>0)+1] += 1; a }
Alternatively, as #HolgerJust mentioned:
neg, zero, pos = arr.each_with_object([0,0,0]) { |a,b| b[(a<=>0)+1] += 1 }
is slightly longer but doesn't have the extra ; a in the block.
Inspired by #steenslag's use of tally:
neg, zero, pos = arr.map { |x| x<=>0 }.tally.values_at(-1,0,1)
If you use a counting hash the code is short and the results are returned in a hash, which may be convenient.
arr = [1, -1, 0, 2, 3, -2, -5, 4]
You could write
arr.each_with_object(Hash.new(0)) { |n,h| h[n<=>0] += 1 }
#=> {1=>4, -1=>3, 0=>1}
or perhaps you would prefer
labels = { -1=>:neg, 0=>:zero, 1=>:pos }
arr.each_with_object(Hash.new(0)) { |n,h| h[labels[n<=>0]] += 1 }
#=> {:pos=>4, :neg=>3, :zero=>1}
the last line of which could alternatively be written
arr.each_with_object({}) { |n,h| h[labels[n<=>0]] = (h[labels[n<=>0]] ||= 0) + 1 }
See Hash::new, specifically the (second) form that takes an argument called the default value (here zero), and no block. If a hash is defined h = Hash.new(0), then if h has no key k, h[k] returns 0 (and h is not changed).
arr = [1, -1, 0, 2, 3, -2, -5]
neg, zero, pos = arr.map{|n| n <=> 0}.tally.values_at(-1, 0, 1)
Using the new tally method.
As others have already said, you should just use inject and count using the <=> operator. If you plan to use similar logic frequently, you could monkey patch a #tally_by method into Enumerable like so:
class Enumerable
def my_tally(*keys, &proc)
proc ||= -> e {e} # Default identity proc
h = keys.empty? ? Hash.new(0) : Hash[keys.map{|k|[k, 0]}]
inject(h){|a, e| a[proc.call(e)] += 1; a}
end
end
This allows you to write:
neg, zero, pos = arr.my_tally(-1, 0, 1){|e| e <=> 0}
While this is certainly more upfront code than the others, it may be nice to have if you find yourself using similar logic frequently. You could also just make this a regular method somewhere if you don't like monkey-patching.

Sum of two sums in Ruby with two loops

If a pair of numbers in the array sums to zero, I want the positions of those two numbers. If no pair of numbers sums to zero, i should return nil.
The iteration not happening in my outer loop:
def two_sum(nums)
# puts(nums)
l = nums.length
i = 0
j = 1
while(i < l)
while(j < l)
puts(nums[i] + nums[j])
puts(nums[i])
puts(nums[j])
if(nums[i] + nums[j] == 0)
return ("[" + i.to_s + "," + j.to_s + "]")
puts("[" + i.to_s + "," + j.to_s + "]")
else
j = j + 1
end
end
i = i + 1
end
end
It's much simpler to use ranges and each; this makes the code much clearer and more concise:
#!/usr/bin/env ruby
def two_sum(nums)
(0...nums.length).each do |i|
((i+1)...nums.length).each do |j|
return [i, j] if nums[i] + nums[j] == 0
end
end
nil
end
p two_sum([1, 2, 3, -1, 4]) # [0, 3]
p two_sum([1, 2, 3]) # nil
p two_sum([]) # nil
The problem is that you set the value of j = 1 only at the beginning, but you need it reset for each outer iteration. Just move the j = 1 after the
while(i < l)
or even better: j = i + 1
As ✅ has answered you question, I would like to suggest an alternative:
def pair_sums_to_zero(arr)
h = arr.each_with_index.group_by { |n,_| n.abs }
return h[0].first(2).map(&:last) if h.key?(0) and h[0].size > 1
a = h.map { |k,v| v.uniq(&:first) }.find { |b| b.size == 2 }
a ? a.map(&:last) : nil
end
arr = [3,2,-4,-2,3,2]
pair_sums_to_zero arr
#=> [1,3]
The steps:
h = arr.each_with_index.group_by { |n,_| n.abs }
#=> {3=>[[3, 0], [3, 4]], 2=>[[2, 1], [-2, 3], [2, 5]], 4=>[[-4, 2]]}
h.key?(0) and h[0].size > 1
#=> false
c = h.map { |k,v| v.uniq(&:first) }
#=> [[[3, 0]], [[2, 1], [-2, 3]], [[-4, 2]]]
a = c.find { |b| b.size == 2 }
a ? a.map(&:last) : nil
#=> [[2, 1], [-2, 3]]
a ? a.map(&:last) : nil
#=> [1, 3]
Another example:
arr = [3,0,2,-4,-6,0,3,0,2]
pair_sums_to_zero arr
h = arr.each_with_index.group_by { |n,_| n.abs }
#=> {3=>[[3, 0], [3, 6]], 0=>[[0, 1], [0, 5], [0, 7]], 2=>[[2, 2], [2, 8]],
# 4=>[[-4, 3]], 6=>[[-6, 4]]}
h.key?(0) and h[0].size > 1
#=> true
h[0].first(2).map(&:last)
#=> [1, 5] (returned)

Changing elements in multi-dimensional array

Inside arrays, I have 0s and one 1.
class Image
def initialize(rows)
#rows = rows
end
end
image = Image.new([
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
])
I want the numbers that are located up, down, left, and right to turn 1 as well. I tried to do this by manipulating column_index and row_index. The code is:
class Image
def blur
#rows_copy = Array.new(#rows.size) {Array.new(#rows.first.size)}
#rows.each_with_index do |row, row_index|
row.each_with_index do |cell, column_index|
blur_location(row_index,column_index)
end
end
#rows = #rows_copy
end
def blur_location (row_index, column_index)
if #rows[row_index][column_index] == 1
#rows_copy[row_index][column_index] = 1
#rows_copy[row_index + 1][column_index] = 1
#rows_copy[row_index - 1][column_index] = 1
#rows_copy[row_index][column_index + 1] = 1
#rows_copy[row_index][column_index - 1] = 1
else
#rows_copy[row_index][column_index] = 0
end
end
def output_image
#rows.each_with_index do |row, row_index|
puts row.join('')
end
end
end
image.blur
image.output_image
But only half of the code is working (i.e., the top and left turns to 1, but not the other two).
the code almost works as expected but you are a victim of the following piece of code:
else
#rows_copy[row_index][column_index] = 0
end
What happens is the when you hit the '1' you set everything as expected, but when you move on and you hit the zeros that are near the '1' (to the right and down as you're processing) you are resetting the rows_copy to zero.
Here is a revised version of the code the does the right thing (notice how the copy is all first set to 0 and after that only 1s are marked):
#!/usr/bin/env ruby
class Image
def initialize(rows)
#rows = rows
end
def blur
#rows_copy = Array.new(#rows.size) {Array.new(#rows.first.size)}
#rows.each_with_index do |row, row_index|
row.each_with_index do |cell, column_index|
set_zero(row_index,column_index)
end
end
#rows.each_with_index do |row, row_index|
row.each_with_index do |cell, column_index|
blur_location(row_index,column_index)
end
end
#rows = #rows_copy
end
def set_zero(row_index, column_index)
#rows_copy[row_index][column_index] = 0
end
def blur_location (row_index, column_index)
if #rows[row_index][column_index] == 1
#rows_copy[row_index][column_index] = 1
#rows_copy[row_index + 1][column_index] = 1
#rows_copy[row_index - 1][column_index] = 1
#rows_copy[row_index][column_index + 1] = 1
#rows_copy[row_index][column_index - 1] = 1
end
end
def output_image
#rows.each_with_index do |row, row_index|
puts row.join('')
end
end
end
image = Image.new([
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
])
image.blur
image.output_image

Distribute range in array

I need to create one array of numbers inside one range, like:
[1..5] in 10 times = [1,1,2,2,3,3,4,4,5,5]
[1..5] in 5 times = [1,2,3,4,5]
[1..5] in 3 times = [1,3,5]
def distribute(start_value, end_value, times, is_integer)
array = Array.new(times-1)
min_value = [end_value,start_value].min
max_value = [end_value,start_value].max
if max_value-min_value<times
factor = (max_value-min_value).abs/(array.size).to_f
else
factor = (max_value-min_value).abs/(array.size-1).to_f
end
for i in 0..array.size
v = [ [max_value, factor*(i+1)].min, min_value].max
is_integer ? array[i] = v.round : array[i] = v
end
start_value < end_value ? array : array.reverse
end
distribute(1, 5, 10, true)
=> [1, 1, 1, 2, 2, 3, 3, 4, 4, 4] #WRONG should be [1,1,2,2,3,3,4,4,5,5]
distribute(5, 1, 5, true)
=> [5, 4, 3, 2, 1] #OK
distribute(1, 5, 3, true)
=> [4, 5, 5] #WRONG should be [1, 3, 5]
How 'bout this:
def distribute(min,max,items)
min,max = [min,max].sort
(0...items).map {|i| (min + i * (max - min) / (items-1.0)).round}
end
Or if you really need the int/float flag:
def distribute(min,max,items,ints)
min,max = [min,max].sort
a = (0...items).map {|i| min + i * (max - min) / (items-1.0)}
ints ? a.map {|i| i.round} : a
end
And if you really need this to go in reverse if the parameters are given to you backwards:
def distribute(min,max,items,ints)
usemin,usemax = [min,max].sort
diff = usemax - usemin
a = (0...items).map {|i| usemin + i * diff / (items-1.0)}
a.map! {|i| i.round} if ints
min != usemin ? a.reverse : a
end
just a little correction... when the array_size is 0
def distribute(start_value, end_value, array_size, want_ints)
diff = 1.0 * (end_value - start_value)
n = [array_size-1, 1].max
(0..(array_size-1)).map { |i|
v = start_value + i * diff / n
want_ints ? v.round : v
}
end

Resources