Suppose I have 10x10 matrix with the following data:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 _ 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
My position is in [4][4]. How can I list the diagonal values from this position?
For example, the expected outcome would be:
[56, 67, 78, 89, 100, 1, 12, 23, 34]
[54, 63, 72, 81, 9, 18, 27, 36]
My current solution
def next?(index, row, size)
(((row + index) % size) + 1 ) % size
end
(1...chess_size).each do |l|
next_el, curr_el = next?(l, row, chess_size), (row + l) % chess_size
# this gets me the first diagnonal. Note that it prints out wrong value
tmp[0] << chess[curr_el][curr_el]
# this gets me the values from current row below to up
tmp[1] << chess[(row + l) % chess_size][row]
tmp[2] << chess[-l][l]
tmp[3] << chess[row][(row + l) % chess_size]
end
Our matrix will always have the same number of rows and columns.
Generally to get the diagonal values from i and j, you can iterate over i and j at the same time up to one of them would be zero. Hence, the main diagonal is (i-1, j-1), (i-2, j-2), ... up to i, j >= 0 and (i + 1, j + 1), (i +2, j + 2), ... up to i, j <= n. For the antidiagonal is (i - 1, j + 1), (i - 2, j + 2), ... up to i >= 0 and j <= n, and (i + 1, j-1), (i + 2, j - 2), ... up to i <= n and j >= 0.
This is a solution to the Hackerrank Queen's attack problem.
Code
def count_moves(n, obs, qrow, qcol)
qdiff = qrow-qcol
qsum = qrow+qcol
l = u = -1
r = d = n
ul = qdiff >= 0 ? qrow-qcol-1 : -1
dr = qdiff >= 0 ? n : qrow+n-qcol
ur = qsum < n ? -1 : qrow-n+qcol
dl = qsum < n ? qrow+qcol+1 : n
obs.uniq.each do |i,j|
case i <=> qrow
when -1 # up
case j <=> qcol
when -1 # up-left
ul = [ul,i].max
when 0 # up same col
u = [u,i].max
when 1 # up-right
ur = [ur,i].max
end
when 0 # same row
j < qcol ? (l = [l,j].max) : r = [r,j].min
else # down
case j <=> qcol
when -1 # down-left
dl = [dl,i].min
when 0 # down same col
d = [d,i].min
when 1 # down-right
dr = [dr,i].min
end
end
end
r + dl + d + dr - l - ul -u - ur - 8
end
Example
Suppose the chess board has 9 rows and columns, with the queen's location shown by the character q and each obstruction shown with the letter o. All other locations are represented by the letter x. We see that the queen has 16 possible moves (7 up and down, 6 left and right, 1 on the up-left to down-right diagonal and 2 on the up-right to down-left diagonal.
arr = [
%w| x x x x x x x x x |, # 0
%w| o x x x x x x x x |, # 1
%w| x o x x x x x x x |, # 2
%w| x x o x x x x x o |, # 3
%w| x x x o x x x x x |, # 4
%w| x x x x x x o x x |, # 5
%w| o o x x x q x x x |, # 6
%w| x x x x x x o x x |, # 7
%w| x x x x x o x x x | # 8
# 0 1 2 3 4 5 6 7 8
]
qrow = qcol = nil
obs = []
n = arr.size
arr.each_with_index do |a,i|
a.each_with_index do |c,j|
case c
when 'o'
obs << [i,j]
when 'q'
qrow=i
qcol=j
end
end
end
qrow
#=> 6
qcol
#=> 5
obs
#=> [[1, 0], [2, 1], [3, 2], [3, 8], [4, 3], [5, 6], [6, 0], [6, 1], [7, 6], [8, 5]]
count_moves(n, obs, qrow, qcol)
#=> 16
Explanation
l is the largest column index of an obstruction in the queen's row that is less than the queen's column index;
r is the smalles column index of an obstruction in the queens that is greater than the queen's column index;
u is the largest largest row index of an obstruction in the queen's column that is less than the queen's row index;
d is the smallest row index of an obstruction in the queen's column that is greater than the queen's row index;
ul is the greatest row index of an obstruction on the queen's top-left to bottom-right diagonal that is less than the queen's row index;
dr is the smallest row index of an obstruction on the queen's top-left to bottom-right diagonal that is greater than the queen's row index;
ur is the greatest row index of an obstruction on the queen's top-right to bottom-left diagonal that is less than the queen's row index; and
dl is the smallest row index of an obstruction on the queen's top-right to bottom-left diagonal that is greater than the queen's row index.
For the example above, before obstructions are taken into account, these variables are set to the following values.
l = 0
r = 9
ul = 0
u = -1
ur = 2
dl = 9
d = 9
dr = 9
Note that if the queen has row and column indices qrow and qcol,
i - j = qrow - qcol for all locations [i, j] on the queen's top-left to bottom- right diagonal; and
i + j = grow + gcol for all locations [i, j] on the queen's top-right to bottom- left diagonal
We then loop through all (unique) obstructions, determining, for each, whether it is in the queen's row, queen's column, or one of the queen's diagonals and then replaces the value of the applicable variable with it's row or column index if it is "closer" to the queen than the previously-closest location.
If, for example, the obstruction is in the queen's row and its column index j is less than the queen's column index, the following calculation is made:
l = [l, j].max
Similarly, if the obstruction is on the queen's top-left to bottom-right diagonal and its row index i is less than the queen's row index, the calculation would be:
ul = [ul, i].max
After all obstructions from the above example have been considered the variables have the following values.
l #=> 1
r #=> 9
ul #=> 4
u #=> -1
ur #=> 5
dl #=> 9
d #=> 8
dr #=> 7
Lastly, we compute the total number of squares to which the queen may move.
qcol - l - 1 + # left
r - qcol - 1 + # right
u - qrow - 1 + # up
grow - d - 1 + # down
ul - qrow - 1 + # up-left
ur - qrow - 1 + # up-right
qrow - dl - 1 + # down-left
qrow - dr - 1 # down-right
which simplifies to
r + dl + d + dr - l - ul -u - ur - 8
#=> 9 + 9 + 8 + 7 - 1 - 4 + 1 - 5 - 8 => 16
I've applied the logic that #OmG provided. Not sure how efficient it would be.
def stackOverflow(matrixSize, *args)
pos, obstacles = *args
chess = (1..(matrixSize * matrixSize)).each_slice(matrixSize).to_a
obstacles.each do |l| chess[l[0]][l[1]] = '_' end
row, col = pos[:row] - 1, pos[:col] - 1
chess[row][col] = '♙'
directions = [[],[],[],[],[],[],[],[]]
(1...matrixSize).each do |l|
directions[0] << chess[row + l][col + l] if (row + l) < matrixSize && (col + l) < chess_size
directions[1] << chess[row - l][col - l] if (row - l) >= 0 && (col - l) >= 0
directions[2] << chess[row + l][col - l] if (row + l) < matrixSize && (col - l) >= 0
directions[3] << chess[row - l][col + l] if (row - l) >= 0 && (col + l) < matrixSize
directions[4] << chess[row + l][col] if row + l < matrixSize
directions[5] << chess[row - l][col] if row - l >= 0
directions[6] << chess[row][col + l] if col + l < matrixSize
directions[7] << chess[row][col - l] if col - l >= 0
end
end
stackOverflow(5, 3, {row: 4, col: 3}, [[4,4],[3,1],[1,2]] )
#CarySwoveland It seems #Jamy is working on another problem from hackerrank queens-attack.
The problem is quite hard because the idea is to never create a matrix in the first place. That is, the test cases become very large, and thus the space complexity will be an issue.
I've changed my implementation, yet still, fail because of timeout issue (this is because test cases become very large). I'm not sure how to make it performant.
Before I show the code. Let me explain what I'm trying to do using illustration:
This is our chess:
---------------------------
| 1 2 3 4 5 |
| 6 7 8 9 10 |
| 11 12 13 14 15 |
| 16 17 18 19 20 |
| 21 22 23 24 25 |
---------------------------
And this is where our queen is located: queen[2][3]
---------------------------
| 1 2 3 4 5 |
| 6 7 8 9 10 |
| 11 12 13 ♙ 15 |
| 16 17 18 19 20 |
| 21 22 23 24 25 |
---------------------------
The queen can attack all 8 directions. I.e:
horizontal(x2):
1. from queen position to left : [13, 12, 11]
2. from queen position to right : [15]
vertical(x2):
1. from queen position to top : [9, 4]
2. from queen position to bottom : [19, 24]
diagonal(x2):
1. from queen position to bottom-right : [20]
2. from queen position to top-left : [8, 2]
diagonal(x2):
1. from queen position to bottom-left : [18, 22]
2. from queen position to top-right : [10]
Because there are no obstacles within those 8 paths, the queen can attack a total of 14 attacks.
Say we have some obstacles:
---------------------------
| 1 2 3 4 5 |
| 6 7 x 9 10 |
| 11 x 13 ♙ 15 |
| 16 17 18 19 x |
| 21 x 23 x 25 |
---------------------------
Now the queen can attack a total of 7 attacks: [13, 18, 19, 15, 10, 9, 4]
Code
MAXI = 10 ** 5
def queens_attack(size, number_of_obstacles, queen_pos, obstacles)
# exit the function if...
# size is negative or more than MAXI. Note MAXI has constraint shown in hackerrank
return if size < 0 || size > MAXI
# the obstacles is negative or more than the MAXI
return if number_of_obstacles < 0 || number_of_obstacles > MAXI
# the queen's position is outside of our chess dimension
return if queen_pos[:row] < 1 || queen_pos[:row] > size
return if queen_pos[:col] < 1 || queen_pos[:col] > size
# the queen's pos is the same as one of the obstacles
return if [[queen_pos[:row], queen_pos[:col]]] - obstacles == []
row, col = queen_pos[:row], queen_pos[:col]
# variable to increment how many places the queen can attack
attacks = 0
# the queen can attack on all directions:
# horizontals, verticals and both diagonals. So let us create pointers
# for each direction. Once the obstacle exists in the path, make the
# pointer[i] set to true
pointers = Array.new(8, false)
(1..size).lazy.each do |i|
# this is the diagonal from queen's pos to bottom-right
if row + i <= size && col + i <= size && !pointers[0]
# set it to true if there is no obstacle in the current [row + i, col + i]
pointers[0] = true unless [[row + i, col + i]] - obstacles != []
# now we know the queen can attack this pos
attacks += 1 unless pointers[0]
end
# this is diagonal from queen's pos to top-left
if row - i > 0 && col - i > 0 && !pointers[1]
# set it to true if there is no obstacle in the current [row - i, col - i]
pointers[1] = true unless [[row - i, col - i]] - obstacles != []
# now we know the queen can attack this pos
attacks += 1 unless pointers[1]
end
# this is diagonal from queen's pos to bottom-left
if row + i <= size && col - i > 0 && !pointers[2]
pointers[2] = true unless [[row + i, col - i]] - obstacles != []
attacks += 1 unless pointers[2]
end
# this is diagonal from queen's pos to top-right
if row - i > 0 && col + i <= size && !pointers[3]
pointers[3] = true unless [[row - i, col + i]] - obstacles != []
attacks += 1 unless pointers[3]
end
# this is verticle from queen's pos to bottom
if row + i <=size && !pointers[4]
pointers[4] = true unless [[row + i, col]] - obstacles != []
attacks += 1 unless pointers[4]
end
# this is verticle from queen's pos to top
if row - i > 0 && !pointers[5]
pointers[5] = true unless [[row - i, col]] - obstacles != []
attacks += 1 unless pointers[5]
end
# this is horizontal from queen's pos to right
if col + i <= size && !pointers[6]
pointers[6] = true unless [[row, col + i]] - obstacles != []
attacks += 1 unless pointers[6]
end
# this is horizontal from queen's pos to left
if col - i > 0 && !pointers[7]
pointers[7] = true unless [[row, col - i]] - obstacles != []
attacks += 1 unless pointers[7]
end
end
p attacks
end
Problem
Now the problem is, I don't know why my code is doing a timeout error from hackerrank. I do know it because of the test case, where the dimension of chess can be 10,000 X 10,000. But dont know what constraint I'm missing.
I've just learned from a comment posted by the OP that I've solved the wrong problem, despite the fact that the OP's question seems quite clear, especially the example, and is consistent with my interpretation. I will leave this solution to the following problem: "Given an array arr such that Matrix(*arr) is an NxM matrix, and a matrix location i,j, return an array [d,a], where elements d and a are elements on the diagonal and antidiagonal that pass through [d,a] but do not include [d,a] and are each rotated so that row index of the first element is i+1 if i < arr.size-1 and is 0 otherwise.
Code
def diagonals(arr, row_idx, col_idx)
ncols = arr.first.size
sum_idx = row_idx+col_idx
diff_idx = row_idx-col_idx
a = Array.new(arr.size * arr.first.size) { |i| i.divmod(ncols) } -[[row_idx, col_idx]]
[a.select { |r,c| r-c == diff_idx }, a.select { |r,c| r+c == sum_idx }].
map do |b| b.sort_by { |r,_| [r > row_idx ? 0:1 , r] }.
map { |r,c| arr[r][c] }
end
end
All elements of the array arr must be equal in size but there is no requirement that arr.size = arr.first.size.
Example
arr = [
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
[31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
[41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
[51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
[61, 62, 63, 64, 65, 66, 67, 68, 69, 70],
[71, 72, 73, 74, 75, 76, 77, 78, 79, 80],
[81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
[91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
]
diagonals(arr, 4, 4)
#=> [[56, 67, 78, 89, 100, 1, 12, 23, 34],
# [54, 63, 72, 81, 9, 18, 27, 36]]
Explanation
Suppose
arr = (1..16).each_slice(4).to_a
#=> [[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12],
# [13, 14, 15, 16]]
row_idx = 2
col_idx = 1
The steps are as follows.
a = Array.new(arr.size) { |i| Array.new(arr.first.size) { |j| [i,j] } }
#=> [[[0, 0], [0, 1], [0, 2], [0, 3]],
# [[1, 0], [1, 1], [1, 2], [1, 3]],
# [[2, 0], [2, 1], [2, 2], [2, 3]],
# [[3, 0], [3, 1], [3, 2], [3, 3]]]
ncols = arr.first.size
#=> 4
sum_idx = row_idx+col_idx
#=> 3
diff_idx = row_idx-col_idx
#=> 1
a = Array.new(arr.size * arr.first.size) { |i| i.divmod(ncols) } - [[row_idx, col_idx]]
#=> [[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3],
# [2, 0], [2, 2], [2, 3], [3, 0], [3, 1], [3, 2], [3, 3]]
Select and sort the locations [r, c] on the top-left to bottom-right diagonal that passes through [row_idx, col_idx].
b = a.select { |r,c| r-c == diff_idx }
#=> [[1, 0], [3, 2]]
c = b.sort_by { |r,_| [r > row_idx ? 0:1 , r] }
#=> [[3, 2], [1, 0]]
Select and sort the locations [r, c] on the top-right bottom-left diagonal that passes through [row_idx, col_idx].
d = a.select { |r,c| r+c == sum_idx }
#=> [[0, 3], [1, 2], [3, 0]]
e = d.sort_by { |r,c| [r > row_idx ? 0:1 , r] }
#=> [[3, 0], [0, 3], [1, 2]]
[c, e].map { |f| f.map { |r,c| arr[r][c] }
#=> [c, e].map { |f| f.map { |r,c| arr[r][c] } }
#=> [[15, 5], [13, 4, 7]]
I've just learned from a comment posted by the OP that I've solved the wrong problem, despite the fact that the OP's question seems quite clear, especially the example, and is consistent with my interpretation. I will leave this solution to the following problem: "Given an array arr such that Matrix(*arr) is an NxM matrix, and a matrix location i,j, return an array [d,a], where elements d and a are elements on the diagonal and antidiagonal that pass through [d,a] but do not include [d,a] and are each rotated so that row index of the first element is i+1 if i < arr.size-1 and is 0 otherwise.
The following approach uses methods from the Matrix class.
Code
require 'matrix'
def diagonals(arr, row_idx, col_idx)
[diag(arr, row_idx, col_idx),
diag(arr.map(&:reverse).transpose, arr.first.size-1-col_idx, row_idx)]
end
def diag(arr, row_idx, col_idx)
nrows, ncols = arr.size, arr.first.size
lr = [ncols-col_idx, nrows-row_idx].min - 1
ul = [col_idx, row_idx].min
m = Matrix[*arr]
[*m.minor(row_idx+1, lr, col_idx+1, lr).each(:diagonal).to_a,
*m.minor(row_idx-ul, ul, col_idx-ul, ul).each(:diagonal).to_a]
end
Example
arr = [
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
[31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
[41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
[51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
[61, 62, 63, 64, 65, 66, 67, 68, 69, 70],
[71, 72, 73, 74, 75, 76, 77, 78, 79, 80],
[81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
[91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
]
diagonals arr, 4, 4
#=> [[56, 67, 78, 89, 100, 1, 12, 23, 34], [54, 63, 72, 81, 9, 18, 27, 36]]
diagonals arr, 4, 5
#=> [[57, 68, 79, 90, 2, 13, 24, 35], [55, 64, 73, 82, 91, 10, 19, 28, 37]]
diagonals arr, 0, 9
#=> [[], [19, 28, 37, 46, 55, 64, 73, 82, 91]]
Explanation
Suppose the array and target location were as follows.
arr = (1..30).each_slice(6).to_a
#=> [[ 1, 2, 3, 4, 5, 6],
# [ 7, 8, 9, 10, 11, 12],
# [13, 14, 15, 16, 17, 18],
# [19, 20, 21, 22, 23, 24],
# [25, 26, 27, 28, 29, 30]]
row_idx = 2
col_idx = 3
Note arr[2][3] #=> 16. We obtain the diagonal with negative slope by computing the diagonals of two matrix minors:
[[23, 24],
[29, 30]]
and
[[2, 3],
[8, 9]]
giving us
[*[23, 30], *[2, 9]]
#=> [23, 30, 2, 9]
To obtain the other diagonal we rotate the array anti-clockwise 90 degrees, adjust row_idx and col_idx and repeat the above procedure.
arr.map(&:reverse).transpose
#=> [[6, 12, 18, 24, 30],
# [5, 11, 17, 23, 29],
# [4, 10, 16, 22, 28],
# [3, 9, 15, 21, 27],
# [2, 8, 14, 20, 26],
# [1, 7, 13, 19, 25]]
ncols = arr.first.size
#=> 6
row_idx, col_idx = ncols-1-col_idx, row_idx
#=> [2, 2]
We now extract the diagonals from the matrix minors
[[21, 27],
[20, 26]]
and
[[6, 12],
[5, 11]]
to obtain the second diagonal:
[21, 26, 6, 11]
def possible_moves(val):
# val is a value between 0 and n*n-1
for i in range(n*n):
if i == val:
board[i // n][i % n] = 'Q'
continue
#mark row and column with a dot
if i % n == val % n or i // n == val // n:
board[i//n][i%n] = '.'
# mark diagonals with a dot
if i % (n + 1) == val % (n + 1) and abs(i % n - val % n) == abs(i // n - val // n):
board[i//n][i%n] = '.'
if i % (n - 1) == val % (n - 1) and abs(i % n - val % n) == abs(i // n - val // n):
board[i//n][i%n] = '.'
n = 10 #board size = n x n
board = [['0' for x in range(n)] for y in range(n)] #initialize board with '0' in every row and col
possible_moves(40)
At the end you will have a 'Q' where the queen s positioned, '0' where the Q cannot movea and '.' where she can moves
The practical application of this problem is group assignment in a psychology study, but the theoretical formulation is this:
I have a matrix (the actual matrix is 27x72, but I'll pick a 4x8 as an example):
1 0 1 0
0 1 0 1
1 1 0 0
0 1 1 0
0 0 1 1
1 0 1 0
1 1 0 0
0 1 0 1
I want to pick half of the rows out of this matrix such that the column totals are equal (thus effectively creating two matrices with equivalent column totals). I cannot rearrange values within the rows.
I have tried some brute force solutions, but my matrix is too large for that to be effective, even having chosen some random restrictions first. It seems to me that the search space could be constrained with a better algorithm, but I haven't been able to think of one thus far. Any ideas? It is also possible that there is no solution, so an algorithm would have to be able to deal with that. I have been working in R, but I could switch to python easily.
Update
Found a solution thanks to ljeabmreosn. Karmarkar-Karp worked great for an algorithm, and converting the rows to base 73 was inspired. I had a surprising hard time finding code that would actually give me the sub-sequences rather than just the final difference (maybe most people are only interested in this problem in the abstract?). Anyway this was the code:
First I converted my rows in to base 73 as the poster suggested. To do this I used the basein package in python, defining an alphabet with 73 characters and then using the basein.decode function to convert to decimel.
For the algorithm, I just added code to print the sub-sequence indices from this mailing list message from Tim Peters: https://mail.python.org/pipermail/tutor/2001-August/008098.html
from __future__ import nested_scopes
import sys
import bisect
class _Num:
def __init__(self, value, index):
self.value = value
self.i = index
def __lt__(self, other):
return self.value < other.value
# This implements the Karmarkar-Karp heuristic for partitioning a set
# in two, i.e. into two disjoint subsets s.t. their sums are
# approximately equal. It produces only one result, in O(N*log N)
# time. A remarkable property is that it loves large sets: in
# general, the more numbers you feed it, the better it does.
class Partition:
def __init__(self, nums):
self.nums = nums
sorted = [_Num(nums[i], i) for i in range(len(nums))]
sorted.sort()
self.sorted = sorted
def run(self):
sorted = self.sorted[:]
N = len(sorted)
connections = [[] for i in range(N)]
while len(sorted) > 1:
bigger = sorted.pop()
smaller = sorted.pop()
# Force these into different sets, by "drawing a
# line" connecting them.
i, j = bigger.i, smaller.i
connections[i].append(j)
connections[j].append(i)
diff = bigger.value - smaller.value
assert diff >= 0
bisect.insort(sorted, _Num(diff, i))
# Now sorted contains only 1 element x, and x.value is
# the difference between the subsets' sums.
# Theorem: The connections matrix represents a spanning tree
# on the set of index nodes, and any tree can be 2-colored.
# 2-color this one (with "colors" 0 and 1).
index2color = [None] * N
def color(i, c):
if index2color[i] is not None:
assert index2color[i] == c
return
index2color[i] = c
for j in connections[i]:
color(j, 1-c)
color(0, 0)
# Partition the indices by their colors.
subsets = [[], []]
for i in range(N):
subsets[index2color[i]].append(i)
return subsets
if not sys.argv:
print "error no arguments provided"
elif sys.argv[1]:
f = open(sys.argv[1], "r")
x = [int(line.strip()) for line in f]
N = 50
import math
p = Partition(x)
s, t = p.run()
sum1 = 0L
sum2 = 0L
for i in s:
sum1 += x[i]
for i in t:
sum2 += x[i]
print "Set 1:"
print s
print "Set 2:"
print t
print "Set 1 sum", repr(sum1)
print "Set 2 sum", repr(sum2)
print "difference", repr(abs(sum1 - sum2))
This gives the following output:
Set 1:
[0, 3, 5, 6, 9, 10, 12, 15, 17, 19, 21, 22, 24, 26, 28, 31, 32, 34, 36, 38, 41, 43, 45, 47, 48, 51, 53, 54, 56, 59, 61, 62, 65, 66, 68, 71]
Set 2:
[1, 2, 4, 7, 8, 11, 13, 14, 16, 18, 20, 23, 25, 27, 29, 30, 33, 35, 37, 39, 40, 42, 44, 46, 49, 50, 52, 55, 57, 58, 60, 63, 64, 67, 69, 70]
Set 1 sum 30309344369339288555041174435706422018348623853211009172L
Set 2 sum 30309344369339288555041174435706422018348623853211009172L
difference 0L
Which provides the indices of the proper subsets in a few seconds. Thanks everybody!
Assuming each entry in the matrix can either be 0 or 1, this problem seems to be in the same family as the Partition Problem which only has a pseudo-polynomial time algorithm. Let r be the number of rows in the matrix and c be the number of columns in the matrix. Then, encode each row to a c-digit number of base r+1. This is to ensure when adding each encoding, there is no need to carry, thus equivalent numbers in this base will equate to two sets of rows whose column sums are equivalent. So in your example, you would convert each row into a 4-digit number of base 9. This would yield the numbers (converted into base 10):
10109 => 73810
01019 => 8210
11009 => 81010
01109 => 9010
00119 => 1010
10109 => 73810
11009 => 81010
01019 => 8210
Although you probably couldn't use the pseudo-polynomial time algorithm with this method, you could use a simple heuristic with some decision trees to try to speed up the bruteforce. Using the numbers above, you could try to use the Karmarkar-Karp heuristic. Implemented below is the first step of algorithm in Python 3:
# Sorted (descending) => 810, 810, 738, 738, 90, 82, 82, 10
from queue import PriorityQueue
def karmarkar_karp_partition(arr):
pqueue = PriorityQueue()
for e in arr:
pqueue.put_nowait((-e, e))
for _ in range(len(arr)-1):
_, first = pqueue.get_nowait()
_, second = pqueue.get_nowait()
diff = first - second
pqueue.put_nowait((-diff, diff))
return pqueue.get_nowait()[1]
Here is the algorithm fully implemented. Note that this method is simply a heuristic and may fail to find the best partition.