Ruby finding element with max value in multidimensional array using max_by - ruby

I'm trying to implement max_by to find element with highest value in multidimensional array.
code as follows
ar = [[123,345,43,35,43,1],[456,123,43,35,43,1],[675,123,43,35,43,1],[123,123,43,35,43,321]]
x = ar.max_by { |a,b| a <=> b }
p "result #{x.inspect}"
And the output is " result [456, 123, 43, 35, 43, 1]"
Can you please explain to me what's wrong with my code ?
Update 1
using max_by
ar = [ {a:1},{a:2},{a:3}]
x = ar.max_by { |e| e[:a] }
p "result #{x.inspect}"
I've left this update as a reminder for myself of whoever may bump into similar problem

You need to do :
ar = [[123,345,43,35,43,1],[456,123,43,35,43,1],[675,123,43,35,43,1],[123,123,43,35,43,321]]
x = ar.max { |a,b| a.max <=> b.max }
With #max_by, you are passing each element array, and then |a, b|, actually doing parallel assignment on a and b. This is not what you want I trust. What I have given above is the way to do it.

max_by handles the comparison for you, just return the maximum value for one element:
ar.max_by { |a| a.max }
#=> [675, 123, 43, 35, 43, 1]
Or even shorter:
ar.max_by(&:max)
#=> [675, 123, 43, 35, 43, 1]

Well, if I'm not mistaken then you'll need to find the greatest values of each subarray and the results should be something like:
[345, 456, 675, 321]
If that what are you looking for:
x = ar.map{|x| x.max}

Related

Remove groups of consecutive numbers from array in Ruby

I have an array:
[1, 2, 3, 6, 8, 9, 10, 23, 34, 35, 36, 45, 50, 51, ...]
I'm trying to remove each group of consecutive numbers so I end up with:
[6, 23, 45, ...]
I am looking for anomalies in serial ids. Does anyone have suggestions?
My initial attempt only checks for the id before each element:
non_consecutive_ids = []
ids.each_with_index do |x, i|
unless x == ids[i-1] + 1
non_consecutive_ids << x
end
end
The thing I think I was missing was to also check to see if the next element in the array is 1 more than the current.
Other option:
array.chunk_while { |i, j| i + 1 == j }.select { |e| e.size == 1 }.flatten
#=> [6, 23, 45]
The good of Enumerable#chunk_while is that it takes two params. The core doc has just an example of a one-by-one increasing subsequence.
You can use select and check the surrounding values:
array.select.with_index{ |x, index| (array[index-1] != x-1) && (array[index+1] != x+1)}

Check if numbers summed in array match input parameter

I'm following Ruby practice problem from a website and I'm completely stuck on figuring out a solution to this problem. Basically, given a function has_sum?(val, arr), return true if any combination of numbers in the array (second parameter) can be added together to equal the first parameter, otherwise return false. So:
has_sum?(5, [1, 2, 3, 4]) # true
has_sum?(5, [1, 2, 6]) # false
I'm completely stuck and not quite sure how to accomplish this... Here's what I have so far.
def has_sum?(val, arr)
arr.each_with_index do |idx, v|
# ??? no idea what to do here except add the current num to the next in the list
end
end
Any help would be greatly appreciated - thanks!
An array can produce a sum when there is a subset of any length that adds up to that sum:
def has_sum?(val, arr)
(arr.size + 1).times
.flat_map { |i| arr.combination(i).to_a }
.any? { |s| s.inject(:+) == val }
end
has_sum?(5, [5])
# => true
has_sum?(5, [1, 2, 3])
# => true
has_sum?(5, [1, 1, 1, 1, 1, 1])
# => true
has_sum?(5, [1, 2, 7])
# => false
This is not very efficient as it generates all the possibilities before testing. This should terminate sooner:
def has_sum?(val, arr)
(arr.size + 1).times.any? { |i|
arr.combination(i).any? { |s| s.inject(:+) == val }
}
end
Even more efficiently, a recursive implementation, with the idea that a sum of an empty array is zero (and has_sum(nonzero, []) should return false); for a larger array, we pop off its head, and see if the sum of the rest of the array is okay if we count, or don't count, the head element. Here, we don't do the useless summing of the whole array over and over again:
def has_sum?(val, arr)
if arr.empty?
val.zero?
else
first, *rest = arr
has_sum?(val, rest) || has_sum?(val - first, rest)
end
end
This solution employs dynamic programming. I assume that zeroes have been removed from the array. If all numbers in the array are positive, we can also remove elements that are larger than the target sum.
Code
def sum_to_target(arr, target)
h = arr.each_index.with_object({}) do |i,h|
v = arr[i]
h.keys.each do |n|
unless h.key?(n+v) # || (n+v > target)
h[n+v] = v
return reconstruct(h, target) if n+v == target
end
end
h[v] = v unless h.key?(v)
return reconstruct(h, target) if v == target
end
nil
end
def reconstruct(h, target)
a = []
loop do
i = h[target]
a.unshift i
target -= i
return a if target == 0
end
a
end
Additional efficiency improvements are possible if arr contains only postive values.1
Examples
#1
sum_to_target [2,4,7,2], 8
#=> [2, 4, 2]
#2
arr = [64, 18, 64, 6, 39, 51, 87, 62, 78, 62, 49, 86, 35, 57, 40, 15, 74, 10, 8, 7]
a = sum_to_target(arr, 461)
#=> [64, 18, 39, 51, 87, 62, 78, 62]
Let's check that.
a.reduce(:+)
#=> 461
#3
a = sum_to_target([-64, 18, 64, -6, 39, 51, -87, 62, -78, 62, 49, 86, 35, 57,
40, 15, -74, 10, -8, -7], 190)
#=> [18, 64, -6, 39, 51, -87, 62, 49]
a.reduce(:+)
#=> 190
#4
arr = 1_000.times.map { rand 1..5_000 }
#=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
# ...
# 521, 1316, 1986, 4099, 1398, 3803, 4498, 4607, 2262, 3941, 4367]
arr is an array of 1,000 elements, each a random number between 1 and 5,000.
answer = arr.sample(500)
#=> [3469, 2957, 1542, 950, 4765, 3126, 3602, 755, 4132, 4281, 2374,
# ...
# 427, 4238, 4397, 2717, 912, 1690, 3626, 169, 3607, 4084, 3161]
answer is an array of 500 elements from arr, sampled without replacement.
target = answer.reduce(:+)
#=> 1_226_020
target is the sum of the elements of answer. We will now search arr for a collection of elements that sum to 1,226,020 (answer being one such collection).
require 'time'
t = Time.now
#=> 2016-12-12 23:00:51 -0800
a = sum_to_target(arr, target)
#=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
# ...
# 3616, 4150, 3222, 3896, 631, 2806, 1932, 3244, 2430, 1443, 1452]
Notice that a != answer (which is not surprising).
a.reduce(:+)
#=> 1226020
(Time.now-t).to_i
#=> 60 seconds
For this last example, methods that use Array#combination would have to wade though as many as
(1..arr.size).reduce(0) { |t,i| t + arr.combination(i).size }.to_f
#~> 1.07+301
combinations.
Explanation
Let
arr = [2,4,7,2]
target = 8
Suppose we temporarily redefine reconstruct to return the hash passed to it.
def reconstruct(h, target)
h
end
We then obtain the following:
h = sum_to_target(arr, target)
#=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}
h is defined as follows.
Given an array of non-zero integers arr and a number n, if n is a key of h there exists an array a containing elements from arr, in the same order, such that the elements of a sum to n and the last element of a equals h[n].
which, admittedly, is a mouthful.
We now use the reconstruct (as defined in the "Code" section) to construct an array answer that will contain elements from arr (without repeating elements) that sum to target.
reconstruct(h, target) #=> [2, 4, 2]
Initially, reconstruct initializes the array answer, which it will build and return:
answer = []
h will always contain a key equal to target (8). As h[8] #=> 2 we conclude that the last element of answer equals 2, so we execute
answer.unshift(2) #=> [2]
The problem is now to find an array of elements from arr that sum to 8 - 2 #=> 6. As h[6] #=> 4, we conclude that the element in answer that precedes the 2 we just added is 4:
answer.unshift(4) #=> [4, 2]
We now need 8-2-4 #=> 2 more to total target. As h[2] #=> 2 we execute
answer.unshift(2) #=> [2, 4, 2]
Since 8-2-4-2 #=> 0 we are finished and therefore return answer.
Notice that 4 precedes the last 2 in arr and the first 2 precedes the 4 in arr. The way h is constructed ensures the elements of answer will always be ordered in this way.
Now consider how h is constructed. First,
h = {}
As arr[0] #=> 2, we conclude that, using only the first element of arr, all we can conclude is:
h[2] = 2
h #=> {2=>2}
h has no key equal to target (8), so we continue. Now consider arr[1] #=> 4. Using only the first two elements of arr we can conclude the following:
h[2+4] = 4
h #=> {2=>2, 6=>4}
and since h has no key 4,
h[4] = 4
h #=> {2=>2, 6=>4, 4=>4}
h still has no key equal to target (8), so we press on and examine arr[2] #=> 7. Using only the first three elements of arr we conclude the following:
h[2+7] = 7
h[6+7] = 7
h[4+7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7}
and since h has no key 7:
h[7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7}
We added four elements to h, but since arr contains only positive numbers, those with keys 9, 13 and 11 are of no interest.
Since h still does not have a key equal to target (8), we examine the next element in arr: arr[3] #=> 2. Using only the first four elements of arr we conclude the following:
h[4+2] = 2
h[6+2] = 2
Here we stop, since 6+2 == target #=> true.
h #=> {2=>2, 6=>2, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}
Notice that we did not compute h[2+2] = 2 since h already has a key 4. Further, had arr contained additional elements we still would have terminated the construction of the hash at this point.
Had we modified the code to take advantage of the fact that arr contains only positive values, the final hash would have been:
h #=> {2=>2, 6=>2, 4=>4, 7=>7, 8=>2}
If this is still not clear, it might be helpful to run the code for this example with included puts statements (e.g., puts "i=#{i}, h=#{h}, v=#{v}" after the line v = arr[i] in sum_to_target, and so on).
1 The line unless h.key?(n+v) can be changed to unless h.key?(n+v) || (n+v > target) if it is known that the array contains no negative elements. (Doing so reduced the solution time for example #4 by 4 seconds.) One could also compute #all_positive = arr.all?(&:positive?) and then make that line conditional on #all_positive.
I would do nested loops.
for x = 0 to length of array
for y = x + 1 to length of array
if number at x + number at y = sum return true
return false
Basically it will check the sum of each number with each of the numbers after it.
EDIT: this will only sum 2 numbers at a time. If you want to be able to sum any amount of numbers this wont work.

Check if all items within sub-array are identical Ruby

Trying to check if all items within sub-arrays are the same. For example, I have a 5x5 board and I want to know if one of the arrays contains all x's:
board = [[47, 44, 71, 8, 88],
['x', 'x', 'x', 'x', 'x'],
# [83, 85, 97, 'x', 57],
[83, 85, 97, 89, 57],
[25, 31, 96, 68, 51],
[75, 70, 54, 80, 83]]
I currently have:
def check_x
board.each do |x|
return true if x.include?('x')
end
return false
end
But this will merely check if one of the integers is x and not all. Any suggestions would be greatly appreciated.
A bit more idiomatic:
board.one? { |row| row.all? { |item| item == 'x' } }
As simple as board.map { |row| row.uniq.count == 1 } will do
#=> [false, true, false, false, false]
uniq returns unique elements in an array. map here is iterating over your array and passing one row at a time to the block. It will return true for cases where all elements in an array are same (['x', 'x', 'x', 'x', 'x'].uniq #=> ['x'] whose length is 1)
If you just want to check if any row in board has all duplicate elements, ruby has just a function. Guess what? any?. Just change above one-liner with any? as:
board.any? { |row| row.uniq.count == 1 } #=> true
If you want to find out which row(s) has/have all the duplicates, and what duplicate it has:
board.each.with_index.select { |row, index| row.uniq.count == 1 }
#=> [[["x", "x", "x", "x", "x"], 1]], where 1 is index.
Pure Ruby awesomeness.
if all elements are same in an array, that means maximum and minimum is equal.
for your board you can find index of desired sub-array with this one line
board.each {|b| puts board.index(b) if b.max == b.min}
or just replace x.include?("x") with x.min == x.max in your function for true/false result
Assuming all elements of board (rows of the board) are the same size, which seems a reasonable assumption, you could do it thus:
x_row = ['x']*board.first.size
#=> ["x", "x", "x", "x", "x"]
board.any? { |row| row == x_row }
#=> true
Assuming it's always a fixed length array, your method can just be:
def full_row
board.each do |row|
return true if (row.uniq.count == 1) && (row[0] == 'x')
end
return false
end
This could be boiled down to fewer lines, but I hate line wrapping in vim :p

After using a formula in an .each, the number in the array won't change

What I have is:
p(array)
array.each { |c| c=c*y**z-1 ; z=z+1 }
p(array)
The array is:
[35, 35, 35]
y is 36, z is a counter, c is the value in the array.
Before the formula I get:
[35, 35, 35]
[formula happens]
After the formula:
[35, 35, 35]
To modify array itself use the #map!, instead of #each method of Array. Because the #each method is used only for value enumerations for Array, or other classes that include Enumerable module. Therefore, do as follows:
array.map! { |c| c=c*y**z-1 ; z=z+1 ; c }
The each method in the Array class does not mutate an existing instance. It just iterates over it. You should use collect! method instead.

Iterating over a multidimensional array?

class Lod
attr_accessor :lodnr
attr_accessor :lobnr
attr_accessor :stknr
def initialize(lodnr, lobnr, stknr)
#lodnr = lodnr
#lobnr = lobnr
#stknr = stknr.chomp
end
def to_s
"%8s, %5s, %3s" % [#lodnr, #lobnr, #stknr]
end
end
I have an array called sold which contains these four arrays:
[10000, 150, 5]
[500, 10, 1]
[8000, 171, 3]
[45, 92, 4]
The four arrays are objects of a class, imported from at .txt file.
input = File.open("lodsedler.txt", "r")
input.each do |line|
l = line.split(',')
if l[0].to_i.between?(0, 99999) && l[1].to_i.between?(1, 180) && l[2].to_i.between?(1, 10)
sold << Lod.new(l[0], l[1], l[2])
else
next
end
end
I want to count the first value in each array, looking for a randomly selected number which is stored in first.
The error I get is always something like this, whatever i try:
Undefined method xxx for #Lod:0x0000000022e2d48> (NoMethodError)
The problem is that i can't seem to acces the first value in all the arrays.
You could try
a = [[10000, 150, 5], [500, 10, 1],[8000, 171, 3],[45, 92, 4]]
You can access a[0][0] 10000 or a[2][1] 171 or iterate
a.each do |row|
row.each do |column|
puts column
end
end
Edit for comment regarding using braces instead of do:
Sure it's possible but I believe do..end in preferred:
https://stackoverflow.com/a/5587403/514463
a.each { |row|
row.each { |column|
puts column
}
}
An easy way to get the first element of each sub array is to use transpose:
special_number = 45
array = [
[10000, 150, 5],
[500, 10, 1],
[8000, 171, 3],
[45, 92, 4]
]
p array.transpose.first.count(special_number) #=> 1
Edit: Actually simpler and more direct...
p array.map(&:first).count(special_number) #=> 1

Resources