[QUIZ] Maximum Sub-Array (#131)

H

Henrik Schmidt-Møller

Ruby said:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Harlan

Given an array of integers, find the sub-array with maximum sum. For example:

array: [-1, 2, 5, -1, 3, -2, 1]
maximum sub-array: [2, 5, -1, 3]

Extra Credit:

Given a matrix of integers, find the rectangle with maximum sum.
*# The algorithm for max_subarray is a slightly adapted version of
# the linear solution presented in
# "Programming pearls: algorithm design techniques"
# by Jon Bentley, published in Communications of the ACM,
# volume 27, Issue 9 (september 1984). According to the article, it
# was designed by Jay Kadane (in less than a minute) in 1977.
# The algorithm for max_submatrix was inspired by some of the ideas in
the same
# article and large quantities of coffee.

# Running time: O(n)
def max_subarray(arr)

if (max = arr.max) <= 0
# if all the numbers in the array are less than or equal to zero,
# then the maximum subarray is simply the array
# consisting of the largest value
max_idx = arr.index(max)
return max, max_idx, max_idx
end

# starting index of the maximum subarray
x1 = 0

# ending index of the maximum subarray
x2 = 0

# the maximum value found so far
running_max = 0

# the maximum value of the array ending on the current
# value (in the block below) or zero, if the maximum
# array becomes negative by including the current value
max_ending_here = 0

# the start index of a possible maximum subarray
start_idx = 0

arr.each_with_index do |i, idx|
start_idx = idx if max_ending_here == 0
max_ending_here = [0, max_ending_here + i].max
if max_ending_here > running_max
running_max = max_ending_here
x1 = start_idx
x2 = idx
end
end
return running_max, x1, x2
end

# Running time: O(m^2 * n)
def max_submatrix(matrix)

# We already have a nice linear algorithm for solving
# the problem in one dimension. What we want to do is
# basically to reduce the problem to an array, and then
# solve that problem using max_subarray.
# The problem can be solved this way for any contiguous
# number of rows by simply adding them together, thereby
# "collapsing" them into one row, and then going from there.
# Now, we want to do this efficiently, so we create
# a cumulative matrix, by adding the elements of the columns
# together. That way, we only need to look up one value
# pr. column to get the sums of the columns in any sub matrix.
c_matrix = matrix.inject([]) do |memo, arr|
prev_arr = memo.last
memo << (prev_arr == nil ? arr : Array.new(arr.size) { |i|
prev_arr + arr })
end

# the maximum value found so far
running_max = 0

# starting index of the horizontal maximum subarray
x1 = 0

# ending index of the horizontal maximum subarray
x2 = 0

# starting index of the vertical maximum subarray
y1 = 0

# ending index of the vertical maximum subarray
y2 = 0

c_matrix.each_with_index do |c_arr, vert_iter_end|
0.upto(vert_iter_end) do |vert_iter_start|
arr = c_arr
if vert_iter_start != vert_iter_end
arr = Array.new(c_arr.size) { |i| c_arr -
c_matrix[vert_iter_start] }
end
c_max, hz_s, hz_e = max_subarray(arr)
if c_max > running_max
running_max = c_max
x1, x2, y2 = hz_s, hz_e, vert_iter_end
y1 = vert_iter_start == vert_iter_end ? 0 : vert_iter_start + 1
end
end
end
return running_max, x1, x2, y1, y2
end

array = [-1, 2, 5, -1, 3, -2, 1]
max, x1, x2 = max_subarray(array)
puts "Maximum subarray for #{array.inspect}:
#{array.values_at(x1..x2).inspect}, sum: #{max}"

matrix =
[
[ 1, 5, -3, 4],
[-8, 2, 9, 12],
[ 6, 1, -2, 2],
[-3, -15, 7, -6]
]

max, x1, x2, y1, y2 = max_submatrix(matrix)
max_matrix = matrix.values_at(y1..y2).collect { |arr|
arr.values_at(x1..x2) }
puts "Maximum submatrix for #{matrix.inspect}: #{max_matrix.inspect},
sum: #{max}"
*
 
E

Eric I.

I went for the all-in-one line solution (not including the method
definition boilerplate), with no consideration for the computational
or memory cost. Here are two versons, the first does not require the
'enumerator' library, the second does and is slightly shorter as a
result. Both will favor shorter solutions over longer solutions if
they have the same sum.

In both cases the code generates every possible sub-array and sorts
them by two factors: 1) sum of elements ascending, and 2) size of sub-
array descending. Finally it chooses the solution at the end of this
sorted list.

In the first solution, it generates every sub-array by using all
possible combinations of starting and ending indices.

def max_sub_array(a)
(0...a.size).inject([]) { |c, s| c + (s...a.size).map { |e|
a.slice(s..e) } }.sort_by { |b| [b.inject { |s, e| s + e }, -
b.size] }.last
end

In the second solution, it generates every sub-array by sequencing
through all possible sub-array lengths (1..size) and then calling
enum_cons. If you're not familiar with enum_cons, it takes a size as
a parameter and returns an Enumerable for every sub-array of that size
(e.g., [1, 2, 3, 4].enum_cons(2) would provide an Enumerable that
would encode the sequence [1, 2], [2, 3], and [3, 4]).

def max_sub_array2(a)
(1..a.size).inject([]) { |l, s| l + a.enum_cons(s).to_a }.sort_by { |
b| [b.inject { |s, e| s + e }, -b.size] }.last
end

Eric

====
Are you interested in on-site Ruby training that uses well-designed,
real-world, hands-on exercises? http://LearnRuby.com
 
A

Alexandru E. Ungur

--pWyiEgJYm5f9v55/
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi all,

Here's my solution. It has no other merits than showing me
that I really need to get back to studying algorythms more
in depth :) as it's quite slow (needs about 2 minutes for
an 1000 elements array).

There are just two optimisations I came up with:
drop sub arrays starting or ending with negative numbers
and breaking out of loop when the sum of the positive numbers
left is smaller than the maximum we already have.

Cheers,
Alex

--pWyiEgJYm5f9v55/
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="quiz131.rb"

class Array
def sum() inject{|s,v|s+=v} end

def max_sub_array
max = { :sum => 0, :arr => [] }
max_max = select{|x| x>0}.sum
0.upto(size-2) do |i|
next if self < 0
max_max -= self
break if max[:sum] > max_max
i.upto(size-1) do |j|
next if self[j] < 0
if (tmp_sum = (tmp_arr = self[i..j]).sum) > max[:sum]
max[:sum] = tmp_sum
max[:arr] = tmp_arr
end
end
end
max
end
end

if $0 == __FILE__
ARRR = Array.new(100) { rand(200) - 100 }
p ARRR.max_sub_array
end

--pWyiEgJYm5f9v55/--
 
H

Harry Kakueki

Extra Credit:

Given a matrix of integers, find the rectangle with maximum sum.
#
# Here is my matrix solution.
# It does the Matrix extra credit.
# If there are multiple rectangles that equal max sum, it prints all of them.
#
# Since the quiz did not specify how to input,
# I just hard coded a sample Matrix at the beginning.
#

require 'matrix'
mat = Matrix[[77,-1,2,3,-4],[-7,8,-22,10,11],[3,15,16,17,-18],[4,22,-23,-24,-25]]
s = []
(0...mat.row_size).each do |a|
(0...mat.column_size).each do |b|
(1..mat.row_size).each do |x|
(1..mat.column_size).each do |y|
s << mat.minor(a,x,b,y)
end
end
end
end

tot = s.uniq.map {|x| x.to_a}
big = tot.max{|x,y| x.flatten.inject(0) {|a,b| a+b} <=>
y.flatten.inject(0) {|c,d| c+d}}
subs = tot.select {|r| r.flatten.inject(0) {|a,b| a+b} ==
big.flatten.inject(0) {|c,d| c+d}}
puts "Original Matrix"
(0...mat.row_size).each do |x|
print mat.row(x).to_a.map{|m| m.to_s.rjust(tot.flatten.max.to_s.length+2)},"\n"
end
puts
puts "Solutions"
subs.each do |x|
puts
x.each {|y| print y.map{|m| m.to_s.rjust(tot.flatten.max.to_s.length+2)},"\n"}
end

# Harry
 
M

Morton Goldberg

My solution to Quiz 131. It does a straight-forward O(N**2) search. I
did add a constraint: the algorithm should return a sub-array of
minimal length. This is because I strongly prefer [0] to [0, 0, 0, 0,
0, 0, 0].

My submission also shows my preference for code that is readable/
maintainable rather than golfed/obfuscated. (This is not intended as
a shot at those who enjoy code golf -- I'm just saying it's not for me.)

<code>
# Return the non-empty sub-array of minimal length that maximizes the
sum
# of its elements.
def max_sub_array(arry)
max_sum = arry.inject { |sum, n| sum += n }
min_length = arry.size
result = arry
(1...arry.size).each do |i|
(i...arry.size).each do |j|
sub = arry[i..j]
sum = sub.inject { |sum, n| sum += n }
next if sum < max_sum
next if sum == max_sum && sub.size >= min_length
max_sum, min_length, result = sum, sub.size, sub
end
end
result
end
</code>

Regards, Morton
 
J

Jesse Merriman

--Boundary-00=_WEkmGPTTaZjZ1bO
Content-Type: Multipart/Mixed;
boundary="Boundary-00=_WEkmGPTTaZjZ1bO"

--Boundary-00=_WEkmGPTTaZjZ1bO
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

I didn't try any golfing, but here are 3 attempts.

The first and second are straightforward approaches, their only difference is
that the second prefers shorter arrays. Both are (n**2 + n) / 2.

The third is my clever solution. I think it should have lower complexity (if
still a factor of n**2), despite having much more setup code, but I'm not sure
what it is exactly. Here's a copy of the main descriptive comment I put in the
code:

# Try to be clever. First, remove any leading or trailing non-positive numbers,
# since including them can only lower the sum. Then, split the array up into
# "islands" of same-sign numbers. Zeros will be including in the group to their
# left. Map each island to its sum to get an array of alternating +,-,+,-,...,+
# numbers. This is really the fundamental form of an instance of the problem.
# It could be run though another max-subarray algorithm, but instead I tried
# to take advantage of its structure by only looking at even-number indices.
# Then just find the maximum subarray's indices, and map back to the originali
# array.

An example in case thats not clear enough:

Start with: [-1, 0, 2, 4, -1, 5, 0, 6, -2]
Trim ends: [2, 4, -1, 5, 0, 6]
Sign switches: [0, 2, 3]
Islands: [[2, 4], [-1], [5, 0, 6]]
new_arr: [6, -1, 11]
Try each index pair: [0, 0], [0, 2], [2, 2]
Best is: [0, 2]
Map back: [2 4 -1 5 0 6]

Only 3 index pairs were tested, as opposed to (9**2 + 9)/ 2 = 45 for the others.

--
Jesse Merriman
(e-mail address removed)
http://www.jessemerriman.com/

--Boundary-00=_WEkmGPTTaZjZ1bO
Content-Type: application/x-ruby;
name="max_subarray.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="max_subarray.rb"

#!/usr/bin/env ruby

require 'enumerator'

class Array
# Return the index of the first element that returns true when yielded, or
# ifnone if no elements pass (like detect, except returning the index instead
# of the element itself).
def first_index ifnone = nil
each_with_index { |e, i| return i if yield(e) }
ifnone
end

# Like first_index, except the last index is looked for.
def last_index ifnone = nil
i = reverse.first_index(ifnone) { |x| yield x }
i.nil? ? ifnone : size - 1 - i
end
end

class Numeric
# Return 1 if self is positive, 0 if zero, -1 if negative.
def sign; zero? ? 0 : self / self.abs; end
end

# My first, straightforward attempt. If there is a tie, the first found will be
# returned (this can be changed to the last found by changing 'sum > best_sum'
# to 'sum >= best_sum'). (n**2 + n) / 2
def max_1_first arr
best_arr, best_sum = [], -1.0/0

(0...arr.size).each do |i|
(i...arr.size).each do |j|
sum = arr[i..j].inject { |sum,x| sum+x }
best_sum, best_arr = sum, arr[i..j] if sum > best_sum
end
end

best_arr
end

# A slight adjustment to the first that prefers shorter arrays. Still
# (n**2 + n) / 2.
def max_2_prefer_short arr
best_arr, best_sum = [], -1.0/0

(0...arr.size).each do |i|
(i...arr.size).each do |j|
sum = arr[i..j].inject { |sum,x| sum+x }
best_sum, best_arr = sum, arr[i..j] if sum > best_sum or
(sum == best_sum and
arr[i..j].size < best_arr.size)
end
end

best_arr
end

# Try to be clever. First, remove any leading or trailing non-positive numbers,
# since including them can only lower the sum. Then, split the array up into
# "islands" of same-sign numbers. Zeros will be including in the group to their
# left. Map each island to its sum to get an array of alternating +,-,+,-,...,+
# numbers. This is really the fundamental form of an instance of the problem.
# It could be run though another max-subarray algorithm, but instead I tried
# to take advantage of its structure by only looking at even-number indices.
# Then just find the maximum subarray's indices, and map back to the originali
# array.
def max_3_clever arr
# Remove leading/trailing elements <= 0.
# Return nil for an empty arr.
# If all are non-positive, return an array containing only the maximum value.
first_pos_i = arr.first_index { |x| x > 0 }
if first_pos_i.nil?
return (arr.empty? ? [] : [arr.max])
end
arr = arr[first_pos_i..arr.last_index { |x| x > 0 }]

# Find the indices of all places where the sign switches.
switches, sign = [], nil
arr.each_with_index do |x, i|
next if x.zero? or sign == x.sign
switches << i
sign = x.sign
end

# Break arr up into "sign islands"
islands = []
switches.each_cons(2) do |s1, s2|
islands << arr[s1...s2]
end
islands << arr[switches.last..-1]

# Create a new array containing the sums of each of the islands.
# This will alternate positive, negative, ..., positive.
new_arr = islands.map { |is| is.inject { |sum, x| sum + x } }

# Here, we could run another maximum-subarray algorithm on new_arr, and then
# find the associated islands to build back the answer to the original
# problem, but instead, lets save a bit of time by taking advantage of their
# +,-,+,-,...,+ structure.

best_indices, best_sum = nil, -1.0/0

# Try every range over new_arr that starts and stops on an even index (because
# ending on an odd index ends on a negative number, which is never optimal).
0.step(new_arr.size-1, 2) do |i|
i.step(new_arr.size-1, 2) do |j|
sum = new_arr[i..j].inject { |sum, x| sum + x }
best_sum, best_indices = sum, [i,j] if sum > best_sum
end
end

islands[best_indices.first..best_indices.last].flatten
end

if __FILE__ == $0
arr = ARGV.map { |x| x.to_i }

#max = max_1_first(arr)
#max = max_2_prefer_short(arr)
max = max_3_clever(arr)

puts max.join(' ')
end

--Boundary-00=_WEkmGPTTaZjZ1bO--
--Boundary-00=_WEkmGPTTaZjZ1bO--
 
C

Carl Porth

#!/usr/bin/env ruby

require "rubygems"
require "facets"
require "enumerable/each_unique_pair"
require "enumerable/sum"

class Array
# all contiguous subarrays
def sub_arrays
[*0..self.size].to_enum:)each_unique_pair).map { |a,b|
self[a..b-1] }
end
end

array = [-1, 2, 5, -1, 3, -2, 1]

# I find this easy on the eyes
array.sub_arrays.max { |a,b| a.sum <=> b.sum } # => [2, 5, -1, 3]

# but if you didn't want to recompute the sums you could do this
array.sub_arrays.map { |a| [a.sum,a] }.max.last # => [2, 5, -1, 3]
 
T

Todd Benson

Just like many others, I went for brute force with some readability.
Golf solution at the bottom.


class Array
def sum
inject {|sum, elem| sum + elem}
end
def sub_arrays
subs = []
0.upto(size-1) { |i| i.upto(size-1) { |j| subs << self[i..j] } }
subs
end
end

foo = Array.new(42) { rand(42) - 21 } # build array; choice of
numbers here is arbitrary
p foo << "\n" # show the array
# now show maximum sub-array ...
p foo.sub_arrays.inject([foo.max]) { |max, elem| elem.sum > max.sum ?
elem : max }


Golf solution (3 lines, 120 chars not including array initialization).
I'm sure it could be shorter, though :(

a = Array.new(42){rand(42)-21}

v=[]
0.upto(b=a.size-1){|i|i.upto(b){|j|v<<a[i..j]}}
p v.inject([a.max]){|z,m|z.inject{|s,i|s+i}>m.inject{|s,i|s+i}?z:m}


Todd
 
S

Sammy Larbi

Morton Goldberg wrote, On 7/15/2007 10:44 AM:
My solution to Quiz 131. It does a straight-forward O(N**2) search. I
did add a constraint: the algorithm should return a sub-array of
minimal length. This is because I strongly prefer [0] to [0, 0, 0, 0,
0, 0, 0].

My submission also shows my preference for code that is
readable/maintainable rather than golfed/obfuscated. (This is not
intended as a shot at those who enjoy code golf -- I'm just saying
it's not for me.)

<code>
# Return the non-empty sub-array of minimal length that maximizes the sum
# of its elements.
def max_sub_array(arry)
max_sum = arry.inject { |sum, n| sum += n }
min_length = arry.size
result = arry
(1...arry.size).each do |i|
(i...arry.size).each do |j|
sub = arry[i..j]
sum = sub.inject { |sum, n| sum += n }
Doesn't this basically make it order n cubed?
 
M

Michael Glaesemann

Here's a solution which iterates over the array just once. Looks like =20=

I came up with a variant of the algorithm presented by Henrik Schmidt-=20=

M=F8ller, though I'm storing the local max sub array rather than just =20=

its delimiting indices. I'm not happy with the calls to slice =20
(especially as they require calculating the size of the array), but =20
I'm pleased that I came up with a solution using recursion.

class Array
def max_sub_array
self.max_sub_arrayr[0]
end

def max_sub_arrayr
ary =3D self.clone
sub_ary =3D Array.new.push(ary.shift)
sum =3D sub_ary[0]
max_sub_ary =3D sub_ary.dup
max_sum =3D sum
done =3D false
ary.each_with_index do |n,i|
if sum > 0
if sum + n > 0
sum +=3D n
sub_ary.push(n)
else
sub_ary, sum =3D ary.slice(i+1..(ary.size-1)).max_sub_arrayr
done =3D true
end
elsif sum <=3D n
sub_ary, sum =3D ary.slice(i..(ary.size-1)).max_sub_arrayr
done =3D true
end
if sum > max_sum
max_sum =3D sum
max_sub_ary =3D sub_ary.dup
break if done
end
end
return max_sub_ary, max_sum
end
end

Michael Glaesemann
grzm seespotcode net
 
M

Matthew Moss

I see I was not the only one to borrow inspiration from Programming
Pearls. This is O(N) code... some few examples included for testing:

def max_subarray_last_index(arr)
a = b = x = 0
arr.each_with_index do |e, i|
b = [b + e, 0].max
unless a > b
a, x = b, i
end
end
return x
end

def max_subarray(arr)
i = arr.size - max_subarray_last_index(arr.reverse) - 1
j = max_subarray_last_index(arr)
return arr[i..j]
end


p max_subarray( [-1, 2, 5, -1, 3, -2, 1] )
p max_subarray( [31, -41, 59, 26, -53, 58, 97, -93, -23, 84] )
p max_subarray( [] )
p max_subarray( [-10] )
p max_subarray( [10] )
p max_subarray( [-5, 5] )
p max_subarray( [5, -5] )
 
M

Morton Goldberg

Morton Goldberg wrote, On 7/15/2007 10:44 AM:
My solution to Quiz 131. It does a straight-forward O(N**2)
search. I did add a constraint: the algorithm should return a sub-
array of minimal length. This is because I strongly prefer [0] to
[0, 0, 0, 0, 0, 0, 0].

My submission also shows my preference for code that is readable/
maintainable rather than golfed/obfuscated. (This is not intended
as a shot at those who enjoy code golf -- I'm just saying it's not
for me.)

<code>
# Return the non-empty sub-array of minimal length that maximizes
the sum
# of its elements.
def max_sub_array(arry)
max_sum = arry.inject { |sum, n| sum += n }
min_length = arry.size
result = arry
(1...arry.size).each do |i|
(i...arry.size).each do |j|
sub = arry[i..j]
sum = sub.inject { |sum, n| sum += n }
Doesn't this basically make it order n cubed?

next if sum < max_sum
next if sum == max_sum && sub.size >= min_length
max_sum, min_length, result = sum, sub.size, sub
end
end
result
end
</code>

Yes, the inject introduces another factor of N. I missed that.

Regards, Morton
 
J

Jesse Merriman

Here's a solution which iterates over the array just once. Looks like =20
I came up with a variant of the algorithm presented by Henrik Schmidt-=20
M=F8ller, though I'm storing the local max sub array rather than just =20
its delimiting indices. I'm not happy with the calls to slice =20
(especially as they require calculating the size of the array), but =20
I'm pleased that I came up with a solution using recursion.
=20
<snip>

Not to be the downer who points out everyone's problems, but here's a bug:

irb(main):008:0> arr =3D [46, -8, 43, -38, -34, -14, 10, -26, -9, -19, -36,=
-6,
-20, -4, -23, -25, 48, -22, 22, 5, -21, -33, 37, 39,
-22, 11, -44, -40, -37, -26]
irb(main):009:0> arr.max_sub_array
=3D> [37, 39, 10]

That sequence does not appear in arr.


=2D-=20
Jesse Merriman
(e-mail address removed)
http://www.jessemerriman.com/
 
M

Michael Glaesemann

Not to be the downer who points out everyone's problems, but here's
a bug:

No problem at all! Thanks for taking a look at my work :)
irb(main):008:0> arr = [46, -8, 43, -38, -34, -14, 10, -26, -9,
-19, -36, -6,
-20, -4, -23, -25, 48, -22, 22, 5, -21, -33,
37, 39,
-22, 11, -44, -40, -37, -26]
irb(main):009:0> arr.max_sub_array
=> [37, 39, 10]

This should be a bit more pleasing :)

irb(main):002:0> arr = [46, -8, 43, -38, -34, -14, 10, -26, -9, -19,
-36, -6,
-20, -4, -23, -25, 48, -22, 22, 5, -21,
-33, 37, 39,
-22, 11, -44, -40, -37, -26]
irb(main):005:0> arr.max_sub_array
=> [46, -8, 43]

Updated code follows below signature:

Michael Glaesemann
grzm seespotcode net

class Array
def max_sub_array
return [] if self.empty?
self.max_sub_arrayr[0]
end

def max_sub_arrayr
ary = self.clone
sub_ary = Array.new.push(ary.shift)
sum = sub_ary[0]
max_sub_ary = sub_ary.dup
max_sum = sum
done = false
ary.each_with_index do |n,i|
if sum > 0
if sum + n > 0
sum += n
sub_ary.push(n)
else
sub_ary, sum = ary.dup.slice(i..(ary.size-1)).max_sub_arrayr
done = true
end
elsif sum <= n
sub_ary, sum = ary.dup.slice(i..(ary.size-1)).max_sub_arrayr
done = true
end
if sum > max_sum
max_sum = sum
max_sub_ary = sub_ary.dup
end
break if done
end
return max_sub_ary, max_sum
end
end
 
R

Rob Biedenharn

This was a nice diversion on Friday morning at the start of my kids'
championship swim meet. I had to work Saturday and Sunday so the
last two test cases had to wait until now (yes, I should be sleeping!).

-Rob

Rob Biedenharn http://agileconsultingllc.com
(e-mail address removed)


class Array
# Given an Array of numbers, return the contiguous sub-array
having the
# maximum sum of its elements. Longer sub-arrays are preferred.
#
#--
# (or members of any Ring having operations '+' (binary,
associative and
# commutative) and '-' (unary, giving the inverse with respect to
'+'))
#++
def sub_max identity=0
return self if size < 2 # nothing to sum!

ms = Array.new(size) { Array.new(size) {identity} }
mx, range = self[0], 0..0
0.upto(size-1) do |e|
e.downto(0) do |s|
current = ms[e] = if s == e
self
else
ms[e-1] + ms[s+1][e] + (- ms[s+1]
[e-1])
end
if current > mx || current == mx && (e - s + 1) > (range.end
- range.begin + 1)
mx = current
range = s..e
end
end
end
self[range]
end
end

if __FILE__ == $0
require 'test/unit'

class Array
def put2d
print '[ '
each do |row|
row.put1d
print ",\n "
end
puts ']'
end

def put1d
print '[ '
each do |item|
print("%3d, " % item)
end
print ']'
end
end

class SubMaxTest < Test::Unit::TestCase
def test_quiz_example
input = [-1, 2, 5, -1, 3, -2, 1]
expected = [2, 5, -1, 3]

assert_equal expected, input.sub_max
end

def test_empty
assert_equal [], [].sub_max
end
def test_single
assert_equal [ 0], [ 0].sub_max
assert_equal [-1], [-1].sub_max
assert_equal [ 1], [ 1].sub_max
end
def test_all_positive
input = [ 1, 2, 4, 8 ]
assert_equal input, input.sub_max
end
def test_all_non_negative
input = [ 1, 2, 0, 4 ]
assert_equal input, input.sub_max
end
def test_all_negative
input = [ -1, -2, -3, -9 ]
assert_equal [-1], input.sub_max
input = [ -2, -1, -3, -9 ]
assert_equal [-1], input.sub_max, 'need to test diagonal'
end
def test_prefer_length_earlier
input = [ -1, 0, 1, -2, -9 ]
assert_equal [0, 1], input.sub_max, "if actual is [1], need to
add a length test on range"
end
def test_prefer_length_later
input = [ -1, 1, 0, -2, -9 ]
assert_equal [1, 0], input.sub_max, "if actual is [1], need to
add a length test on range"
end

def test_prefer_length_multiple_options
input = [ 1, 2, 3, -6, 6 ]
# options
# [6]
# [1,2,3]
expected = [ 1, 2, 3, -6, 6 ]
assert_equal expected, input.sub_max, "if [6] or [1,2,3] you
can do better"
end
end
end
__END__
 
D

Drew Olson

Here's my solution to the maximum sub-array problem. I'm sure my
algorithm is not optimal, but it's readable and concise:

# file: max_sub_array.rb
# author: Drew Olson

class Array

alias :eek:rig_to_s :to_s

# sum the integer values of array contents
def int_sum
self.inject(0){|sum,i| sum+i.to_i}
end

# find the maximum sub array in an array
def max_sub_array
(0...self.size).inject([self.first]) do |max_sub,i|
(i...self.size).each do |j|
if max_sub.int_sum < self[i..j].int_sum
max_sub = self[i..j]
end
end
max_sub
end
end

# pretty printing for array
def to_s
self.inject("[") do |str,i|
str + "#{i}, "
end[0...-2] + "]"
end
end

# test example
if __FILE__ == $0
my_arr = [-1, 2, 5, -1, 3, -2, 1]
puts "array: #{my_arr}"
puts "maximum sub-array: #{my_arr.max_sub_array}"
end
 
K

Kyle Schmitt

A late late entry (I was busy yesterday).
Yes it does an exhaustive search, but for even moderate sized sets,
it's feasibly fast enough.


class Array
def sum()
s=0
each{|i| s+=i}
s
end
end

array=[-6,-5,-4,-3,-2,-1,0,1,2,3,-5,4,5,6]
maxIndex = array.length-1
sizeByRange = {}
0.upto(maxIndex) do
|start|
start.upto(maxIndex) do
|endI|
sizeByRange.store(array[start..endI].sum,start..endI)
#puts "subarray #{start} to #{endI} sums to #{array[start..endI].sum}"
end
end

puts "Minimum array is [#{array[sizeByRange.min[1]].join(',')}]"
puts "Maximum array is [#{array[sizeByRange.max[1]].join(',')}]"
 
M

Matthew Moss

Just a note on my "solution" I sent in earlier... Well, it's not
entirely correct... I've been shown at least one case that fails.
It's probably not that far off, but unfortunately I don't have time
this week to correct it... Just so no summaries go using it.
 
J

James Edward Gray II

Just a note on my "solution" I sent in earlier... Well, it's not
entirely correct... I've been shown at least one case that fails.
It's probably not that far off, but unfortunately I don't have time
this week to correct it... Just so no summaries go using it.

Next week's quiz: debug Matthew's solution. ;)

James Edward Gray II
 
H

Harry Kakueki

Extra Credit:

Given a matrix of integers, find the rectangle with maximum sum.
#
# This is roughly the same matrix solution I posted before.
# Sorry for the double post. Two lines in the middle of the program were
# too long and got wrapped in the post.
# I made the lines shorter and tried again.
#
# Here is my matrix solution.
# It does the Matrix extra credit.
# If there are multiple rectangles that equal max sum, it prints all of them.
#
# Since the quiz did not specify how to input,
# I just hard coded a sample Matrix at the beginning.


require 'matrix'
mat=Matrix[[7,-1,2,3,-4],[-7,8,-22,10,11],[3,15,16,17,-18],[4,22,-23,-24,-25]]
s = []
(0...mat.row_size).each do |a|
(0...mat.column_size).each do |b|
(1..mat.row_size).each do |x|
(1..mat.column_size).each do |y|
s << mat.minor(a,x,b,y)
end
end
end
end

tot = s.uniq.map {|x| x.to_a}
bg=tot.max{|x,y|x.flatten.inject(0){|a,b|a+b}<=>y.flatten.inject(0){|c,d|c+d}}
sb=tot.select{|r|r.flatten.inject(0){|a,b|a+b}==bg.flatten.inject(0){|c,d|c+d}}
puts "Original Matrix"
(0...mat.row_size).each do |x|
print mat.row(x).to_a.map{|m| m.to_s.rjust(tot.flatten.max.to_s.length+2)},"\n"
end
puts
puts "Solutions"
sb.each do |x|
puts
x.each {|y| print y.map{|m| m.to_s.rjust(tot.flatten.max.to_s.length+2)},"\n"}
end

# Harry
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,882
Messages
2,569,948
Members
46,267
Latest member
TECHSCORE

Latest Threads

Top