Related
Has someone an idea how to solve the following problem?
Take the numbers 1,...,100000 and permute them in some way. At first you can make a swap of two numbers. Then you have to compute how many rounds it would take to collect numbers in ascending order. You have to collect numbers by every round by going left to right. In how many ways you can swap two numbers at the beginning to collect numbers in ascending order with minimum number of rounds?
For example, if numbers are from one to five and those at the beginning in order 3, 1, 5, 4, 2, then you can collect them in three rounds: On first round you collect 1, 2, on the second round 3, 4 and finally 5. But you can do one swap in three different ways to collect numbers in two rounds, namely
3, 4, 5, 1, 2
3, 1, 4, 5, 2
3, 1, 2, 4, 5
Five number sequence can be solved easily by brute force and I found an algorithm to collect 1000 numbers, but 100000 numbers needs maybe some kind of trick to compute fast how a specific swap at the beginning affects how many rounds it takes to collect numbers.
Another example:
Take 10 numbers in order [6, 1, 4, 10, 7, 2, 3, 9, 5, 8]. You can swap 4 and 9 to collect numbers in three rounds. But my code returns that there are 3 ways to make a swap. Where is my mistake?
from bisect import bisect_left, bisect_right
from functools import cmp_to_key
def longest_subsequence(seq, mode='strictly', order='increasing',
key=None, index=False):
bisect = bisect_left if mode.startswith('strict') else bisect_right
# compute keys for comparison just once
rank = seq if key is None else map(key, seq)
if order == 'decreasing':
rank = map(cmp_to_key(lambda x,y: 1 if x<y else 0 if x==y else -1), rank)
rank = list(rank)
if not rank: return []
lastoflength = [0] # end position of subsequence with given length
predecessor = [None] # penultimate element of l.i.s. ending at given position
for i in range(1, len(seq)):
# seq[i] can extend a subsequence that ends with a lesser (or equal) element
j = bisect([rank[k] for k in lastoflength], rank[i])
# update existing subsequence of length j or extend the longest
try: lastoflength[j] = i
except: lastoflength.append(i)
# remember element before seq[i] in the subsequence
predecessor.append(lastoflength[j-1] if j > 0 else None)
# trace indices [p^n(i), ..., p(p(i)), p(i), i], where n=len(lastoflength)-1
def trace(i):
if i is not None:
yield from trace(predecessor[i])
yield i
indices = trace(lastoflength[-1])
return list(indices) if index else [seq[i] for i in indices]
def computerounds(lines):
roundnumber = 1
for i in range(len(lines)-1):
if lines[i] > lines[i + 1]:
roundnumber += 1
return roundnumber
if __name__ == '__main__':
lines = [[3,1,5,4,2],[6, 1, 4, 10, 7, 2, 3, 9, 5, 8]]
case = 1
ways_to_change = len(longest_subsequence(lines[case], mode='strictly', order='decreasing',
key=None, index=False))
print(len(lines[case]), computerounds(lines[case]), ways_to_change)
# Should return 10 3 1
Effort 1:
I guess the hardest part is to find a permutation that guarantees you collect the numbers with minimum number of moves. I also heard that Dilworth's theorem tells me that the minimal decomposition into ascending subsequences is equal to the size of the maximal descending subsequence. https://artofproblemsolving.com/community/c163h1906044_an_algorithm_to_collect_numbers_in_ascending_order
Effort 2:
I tried to run the code by jferard and solve the problem for the case junar9.in found in https://www.ohjelmointiputka.net/tiedostot/junar.zip. The file contains fir the number of numbers in the first line and then the rest of the lines gives the numbers as in original order. It looks it takes too much memory. The output was in Linux Mint:
(base) jaakko#jaakko-Aspire-E1-572:~/.config/spyder-py3$ python3 temp.py
Killed
Here is the code from temp.py
# -*- coding: utf-8 -*-
"""
Spyder Editor
This is a temporary script file.
"""
import os.path
import requests
import zipfile
import warnings
def invert(L):
M = [None] + [0 for _ in range(len(L))]
for i, k in enumerate(L):
M[k] = i
return M
def perform_data(read_data):
s = ""
for i in range(len(read_data)):
if read_data[i].isnumeric():
s += read_data[i]
else:
s += " "
s = s[:-1]
s = s.split(" ")
tmp = []
for i in range(1, len(s)):
if s[i] != '':
tmp.append(int(s[i]))
return tmp
def download_zipfile(url):
if not os.path.isfile('/tmp/junar.zip'):
with open('/tmp/junar.zip', 'wb') as out:
out.write(requests.get(url).content)
def read_zipfile_item(filename):
with zipfile.ZipFile('/tmp/junar.zip') as zip_file:
with zip_file.open(filename) as f:
return f.read().decode('utf8')
def generate_original_rounds(A):
B =[0]*(len(A)-1)
print(A)
roundno = 1
for i in range(1,len(A)):
if A.index(i) < A.index(i+1):
B[i-1] = roundno
else:
roundno += 1
B[i-1] = roundno
print(roundno)
return B
def classify_moves(L):
M = invert(L)
N = len(L)
good_moves, bad_moves = [None], [None]
for k in range(1, N+1):
good_move, bad_move = find_moves(k, L, M, N)
good_moves.append(good_move)
bad_moves.append(bad_move)
return good_moves, bad_moves
def find_moves(k, L, M, N):
def in_range(a, b):
return set(L[j] for j in range(a, b))
good_move = set()
bad_move = set()
if k == 1:
if M[k+1] < M[k]:
good_move |= in_range(0, M[k+1]+1)
else: # M[k] < M[k+1]
bad_move |= in_range(M[k+1], N)
elif k == N:
if M[k] < M[k-1]:
good_move |= in_range(M[k-1], N)
else: # M[k-1] < M[k]
bad_move |= in_range(0, M[k-1]+1)
elif M[k-1] < M[k+1]:
if M[k] < M[k-1]:
good_move |= in_range(M[k-1], M[k+1])
elif M[k+1] < M[k]:
good_move |= in_range(M[k-1]+1, M[k+1]+1)
if M[k-1] < M[k]:
bad_move |= in_range(0, M[k-1]+1)
if M[k] < M[k+1]:
bad_move |= in_range(M[k+1], N)
else: # M[k+1] < M[k-1]
if M[k+1] < M[k] < M[k-1]:
good_move |= in_range(0, M[k+1]+1) | in_range(M[k-1], N)
elif M[k] < M[k+1]:
bad_move |= in_range(M[k+1], M[k-1])
else: # M[k-1] < M[k]:
bad_move |= in_range(M[k+1]+1, M[k-1]+1)
return good_move, bad_move
def collate_moves_aux(L):
good_moves, bad_moves = classify_moves(L)
N = len(L)
swaps_by_removed = {}
for i in range(1, N+1):
for j in range(i+1, N+1):
removed = 0
if j in good_moves[i]:
if i in good_moves[j]:
removed = 2
elif i not in bad_moves[j]:
removed = 1
elif j not in bad_moves[i] and i in good_moves[j]:
removed = 1
if abs(i-j) <= 1: # don't count twice
removed -= 1
if removed > 0:
swaps_by_removed.setdefault(removed, []).append((i,j))
return swaps_by_removed
def collate_moves(L):
swaps_by_removed = collate_moves_aux(L)
if __name__ == '__main__':
# Testing
url = 'https://www.ohjelmointiputka.net/tiedostot/junar.zip'
download_zipfile(url=url)
rawdata = read_zipfile_item('junar9.in')
data = perform_data(rawdata)
numbers = data
A = collate_moves(numbers)
print(A)
Idea 1: Is it helpful to compute permutation inversions somehow, http://mathworld.wolfram.com/PermutationInversion.html ? There are some algorithms to compute all permutation inversions in https://www.geeksforgeeks.org/counting-inversions/ but does this helps solve the problem? I think it is somehow related to compute the permutation inversions of the form (n,n+1).
Effort 3:
I tried to apply the idea from jferard's answer. I think it computest wrong answer how many rounds it takes to collect numbers [6, 1, 4, 10, 7, 2, 3, 9, 5, 8]. It returns 4 but it takes five rounds, first 1, 2, 3, second 4, 5, third 6, 7, 8, fourth 9, and fifth 10.
def compute_original_rounds(M):
c = 1
for i in range(2, len(M)):
if M[i] < M[i-1]:
c += 1
return c
if __name__ == '__main__':
lines = [[3,1,5,4,2],[6, 1, 4, 10, 7, 2, 3, 9, 5, 8]]
verygoods = 0
lista = lines[1]
best = 0
drops = [0,0,0]
for k in range(2,len(lista)):
a = lista.index(k-1)<lista.index(k)
b = lista.index(k)<lista.index(k+1)
c = lista.index(k-1)<lista.index(k+1)
if a and b:
print("Zero inversions")
drops[0] += 1
if (not a and c) or (c and not b) or (b and not c) or (a and not c):
print("One inversion")
best = max(best,1)
drops[1] += 1
if not b and not a:
print("Two inversions")
best = max(best,2)
drops[2] += 1
ways = drops[2]
if ways == 0:
ways = drops[1]
if ways == 0:
ways = drops[0]
original_rounds = compute_original_rounds(lista)
print(original_rounds)
print(len(lista),original_rounds - best, ways)
I don't see how the longest decreasing subsequence will give you the number of swaps. According to Dilworth's theorem, the longest antichain (subsequence of decreasing numbers) will give you the width of your list, that is the minimum number of chains (sequence of increasing numbers) you can have in partition of the list.
Note that Dilworth's theorem might not be applicable here because the chains (sequences of numbers in your case) should be ordered and the numbers have to be consecutives ([6, 1, 4, 10, 7, 2, 3, 9, 5, 8] is a counter-example: 3 Dilworth's chains but 5 rounds).
Here's an attempt. The solution is complicated and I hope that more straightforward answer exists, but I didn't find it. I cannot say for sure that it is bug free.
Compute the number of rounds
To compute the number of rounds in O(n), let's follow this method:
Start with 1, 2, 3, ... until you find a k having idx(k+1) < idx(k) where idx is the index in the original list (let's call this an inversion).
The first round is finished, and the second starts with k+1, k+2, ... until you find a l having idx(l+1) < idx(l),
and so on until the list is exhausted.
Hence the formula: number of rounds = 1 + |{k in L | pos(k+1)<pos(k)}|. Example with 3,1,5,4,2: idx(3)<idx(2) and idx(5)<idx(4), thus the number of rounds is 3.
In Python:
def invert(L):
M = [None] + [0 for _ in range(len(L))]
for i, k in enumerate(L):
M[k] = i
return M
def rounds(M):
c = 1
for i in range(2, len(M)):
if M[i] < M[i-1]:
c += 1
return c
>>> rounds(invert([3, 1, 5, 4, 2]))
3
>>> rounds(invert([6, 1, 4, 10, 7, 2, 3, 9, 5, 8]))
5
Good and bad moves
That was the easy part. Now focus on a given k in L. You have six possibilities:
... k ... k-1 ... k+1 ... : 1 inversion
... k-1 ... k ... k+1 ... : 0 inversion
... k-1 ... k+1 ... k ... : 1 inversion
... k ... k+1 ... k-1 ... : 1 inversion
... k+1 ... k ... k-1 ... : 2 inversions
... k+1 ... k-1 ... k ... : 1 inversion
We call a "good move" a move from a situation with 1 inversion to a situation with 0 inversion, or from 2 inversions to 1 inversion. Conversely, a "bad move" is a move from a situation with 0 inversion to 1 inversion or 1 inversion to 2 inversions. When performing a swap, we wan't to avoid bad moves and do good moves. The best we can do is to do two good moves at once, reducing the number of rounds by 2.
First, we will compute, for every k, the good and the bad moves. We have to deal with edges cases (k == 1 or k == N), and with the two main possibilities (pos(k-1) < pos(k+1) and pos(k+1) < pos(k-1)). The swaps between k and k-1 or k+1 should be considered too. That gives the cumbrersome piece of code below:
def classify_moves(L):
M = invert(L)
N = len(L)
good_moves, bad_moves = [None], [None]
for k in range(1, N+1):
good_move, bad_move = find_moves(k, L, M, N)
good_moves.append(good_move)
bad_moves.append(bad_move)
return good_moves, bad_moves
def find_moves(k, L, M, N):
def in_range(a, b):
return set(L[j] for j in range(a, b))
good_move = set()
bad_move = set()
if k == 1:
if M[k+1] < M[k]:
good_move |= in_range(0, M[k+1]+1)
else: # M[k] < M[k+1]
bad_move |= in_range(M[k+1], N)
elif k == N:
if M[k] < M[k-1]:
good_move |= in_range(M[k-1], N)
else: # M[k-1] < M[k]
bad_move |= in_range(0, M[k-1]+1)
elif M[k-1] < M[k+1]:
if M[k] < M[k-1]:
good_move |= in_range(M[k-1], M[k+1])
elif M[k+1] < M[k]:
good_move |= in_range(M[k-1]+1, M[k+1]+1)
if M[k-1] < M[k]:
bad_move |= in_range(0, M[k-1]+1)
if M[k] < M[k+1]:
bad_move |= in_range(M[k+1], N)
else: # M[k+1] < M[k-1]
if M[k+1] < M[k] < M[k-1]:
good_move |= in_range(0, M[k+1]+1) | in_range(M[k-1], N)
elif M[k] < M[k+1]:
bad_move |= in_range(M[k+1], M[k-1])
else: # M[k-1] < M[k]:
bad_move |= in_range(M[k+1]+1, M[k-1]+1)
return good_move, bad_move
>>> classify_moves([3, 1, 5, 4, 2])
([None, set(), set(), set(), {1, 5}, {2, 4}], [None, {2}, {1}, {4}, {3}, set()])
That means that, for instance, from 4 point of view, a swap with 1 or 5 are good, and a swap with 3 would be bad.
Choosing the swaps
Now, we have to collate all those good and bad moves into a list of acceptable swaps. the idea is simple: for every couple (i,j), if i is a good move from j and j a good move from i, then we can remove two rounds. If i is a good move from j and i is not a bad move from j, then we can remove one round. There is again some subtle tricks: 1) we have a list of swaps removing 1 round, but we throw away those swaps as soon as we find a swap removing 2 rounds (the best we can do). 2) when k is a good move from k+1 and k+1 a good move from k, we don't remove two rounds but only one (the good move was counted twice by the classify_moves function).
def collate_moves_aux(L):
good_moves, bad_moves = classify_moves(L)
N = len(L)
swaps_by_removed = {}
for i in range(1, N+1):
for j in range(i+1, N+1):
removed = 0
if j in good_moves[i]:
if i in good_moves[j]:
removed = 2
elif i not in bad_moves[j]:
removed = 1
elif j not in bad_moves[i] and i in good_moves[j]:
removed = 1
if abs(i-j) <= 1: # don't count twice
removed -= 1
if removed > 0:
swaps_by_removed.setdefault(removed, []).append((i,j))
return swaps_by_removed
def collate_moves(L):
swaps_by_removed = collate_moves_aux(L)
return max(swaps_by_removed.items(), key=lambda i: i[0])
>>> collate_moves_aux([3, 1, 5, 4, 2])
{1: [(1, 4), (2, 5), (4, 5)]}
>>> collate_moves([3, 1, 5, 4, 2])
(1, [(1, 4), (2, 5), (4, 5)])
And:
>>> collate_moves_aux([6, 1, 4, 10, 7, 2, 3, 9, 5, 8])
{1: [(3, 8), (5, 10), (8, 9), (9, 10)], 2: [(4, 9)]}
>>> collate_moves([6, 1, 4, 10, 7, 2, 3, 9, 5, 8])
(2, [(4, 9)])
The complexity of the algorithm is O(N^2) amortized: invert is O(N), classify_moves is O(N^2) because find_moves is O(N) (build sets having a cardinal < N) and collate_moves is O(N^2) (amortized).
Hope someone's produce a simple version of this!!!
I would consider a structure like the following. Dashes show us where there is a switch between output sequence rounds. Candidates that need replacement are in the square brackets; these are the indexes immediately before and after the round switch. In parentheses, we have ranges of increasing index sequences that neighbour the candidates.
i: 1 2 3 4 5 6 7 8 9 10
A: 2 1 6 7 8 9 3 4 5 10
indexed, ordered output:
2 1 7 8 9 3 4 5 6 10
- - (round switches)
[2,1](7..8)[9,3](4..10)
Now we insert the candidates, as well as the lower and upper bounds of each range into a binary search tree (or just a sorted array on which we can binary search), where each node also points to their position in the indexed, sorted output list. For each candidate index, we would like to test other indexes in the tree that fit. Once found, we can perform a linear search from the index to gather more possibilities. Notice that candidates that warrant replacing are the ones that if removed would offer an increasing sequence between their neighbours in the input.
2: find x ≤ 1: result 1
1: find 2 ≤ x ≤ 7: result 3
(linear search is stopped by 9 and 4)
9: invalid candidate
3: invalid candidate
Swaps are therefore indexes (2,1) or (1,3).
Example 1 from the question:
i: 1 2 3 4 5
A: 3 1 5 4 2
indexed, ordered output:
2 5 1 4 3
- - (round switches)
(2)[5,1][4,3]
candidates:
5: invalid candidate
1: invalid candidate
4: find 1 ≤ x ≤ 3: results 2, 3
3: find x ≥ 4: result 5
swaps: (4,2) (4,3) (3,5)
Example 2 from the question:
i: 1 2 3 4 5 6 7 8 9 10
A: 6 1 4 10 7 2 3 9 5 8
indexed, ordered output:
2 6 7 3 9 1 5 10 8 4
- - - - (round switches)
(2..6)[7,3][9,1](5)[10,8,4]
candidates:
7: invalid
3: find 7 ≤ x ≤ 9: result 8 (9 wouldn't reduce rounds)
9: invalid
1: invalid
10: find 5 ≤ x ≤ 8: results 7, 8
8: invalid
4: find x ≥ 8: results 8, 9 (10 wouldn't reduce rounds)
original rounds:
(1 2 3)(4 5)(6 7 8)(9)(10)
swaps:
(3,8) -> (1 2 3 4 5)(6 7 8)(9 10)
(10,7) -> (1 2 3)(4 5)(6 7 8 9)(10)
(10,8) -> (1 2 3)(4 5)(6 7 8 9)(10)
(4,8) -> (1 2 3)(4 5)(6 7 8)(9 10)
(4,9) -> (1 2 3)(4 5)(6 7 8)(9 10)
Given an m x n matrix of positive integers representing the height of each unit cell in a 2D elevation map suspended in space, compute the volume of water it is able to trap after unlimited supply of water from top . 0 represents a leakage ( matrix is suspended in space )
Note:
Both m and n are less than 110. The height of each unit cell is greater than 0 and is less than 20,000. have a list of lists where 0 in the sublists represents a leakage.
Below are described example :
[[3, 3, 3, 3, 5, 3],
[3, 0, 2, 3, 1, 3],
[3, 1, 2, 3, 1, 3],
[3, 3, 3, 1, 3, 3]]
ans = 4
We can only fill 4 units of water in this case. That's is 2 units in both 1's in 5th column, because if more than 2 units is filled it would overflow. while the water in the left valley that is left of columns 0 to 3 would leak through 0.
Eg 2
[[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]]
Ans = 4
How to proceed?
Below is the logic of treating 0 as on the ground. I want to modify this to the case of 0 representing leakage/drainage.
def trapRainWater(self, heightMap):
"""
:type heightMap: List[List[int]]
:rtype: int
"""
m = len(heightMap)
if not m:
return 0
n = len(heightMap[0])
if not n:
return 0
is_visited = [[False for i in xrange(n)] for j in xrange(m)]
heap = []
for i in xrange(m):
heappush(heap, [heightMap[i][0], i, 0])
is_visited[i][0] = True
heappush(heap, [heightMap[i][n-1], i, n-1])
is_visited[i][n-1] = True
for j in xrange(n):
heappush(heap, [heightMap[0][j], 0, j])
is_visited[0][j] = True
heappush(heap, [heightMap[m-1][j], m-1, j])
is_visited[m-1][j] = True
trap = 0
while heap:
height, i, j = heappop(heap)
for (dx, dy) in [(1,0), (-1,0), (0,1), (0,-1)]:
x, y = i+dx, j+dy
if 0 <= x < m and 0 <= y < n and not is_visited[x][y]:
trap += max(0, height - heightMap[x][y])
heappush(heap, [max(height, heightMap[x][y]), x, y])
is_visited[x][y] = True
return trap
Design an algorithm that outputs the number of entries in A that are smaller than or equal to x. Your algorithm should run in O(n) time.
For example in the array below if my target was '5' then I would return 2 b/c 1 and 3 are smaller.
[1, 3, 5]
[2, 6, 9]
[3, 6, 10]
I gave it a shot with the code below which is close to working and I think it's O(n) ... the problem I see is if I don't have the # in my array I am not sure if I am returning the right value?
def findLessX(m,n,x):
i = 0
j = n-1
while (i < n and j >= 0):
if i == n or j == n:
print("n not found")
return (i+1)*(j+1)-1
if (m[i][j] == x):
print(" n Found at ", i , " ", j)
return (i+1)*(j+1)-1
elif (m[i][j] > x):
print(" Moving left one column")
j = j - 1
elif (m[i][j] < x):
print(" Moving down one row")
i = i + 1
print(" n Element not found so return max")
return (i)*(j+1)
# Driver code
x = 5
n = 3
m = [ [1, 3, 5],
[2, 6, 9],
[3, 6, 9]]
print("Count=", findLessX(m, n, x))
Inspect the Count and simple matrix above to see if soln works ~
If both columns and rows are sorted ascending, then for any given border value some stairs line does exist. It divides matrix into two parts - higher (and equal) and lower than border value. That line always goes left and down (if traversal starts from top right corner).
[1, 3, |5]
____|
[2,| 6, 9]
[3,| 6, 10]
So scan from top right corner, find starting cell for that line on the right or top edge, then follow the line, counting elements being left to it.
Complexity is linear, because line never turns back.
P.P.S. I hoped that you could write code with given clues
def countLessX(m,n,x):
col = n-1
count = 0
for row in range(n):
while (col >= 0) and (m[row] [col] >= x):
col = col - 1
count = count + col + 1
if col < 0: #early stop for loop
break
return count
# Driver code
n = 3
m = [ [1, 3, 5],
[2, 6, 9],
[3, 6, 9]]
for x in range(11):
print("x=", x, "Count=", countLessX(m, n, x))
x= 0 Count= 0
x= 1 Count= 0
x= 2 Count= 1
x= 3 Count= 2
x= 4 Count= 4
x= 5 Count= 4
x= 6 Count= 5
x= 7 Count= 7
x= 8 Count= 7
x= 9 Count= 7
x= 10 Count= 9
As mentioned in my comment your problem is not solveable in O(n) for most matrices. Some other thoughts:
Why count j downwards?
i and j can never become n
Here is a solution in O(n) that perhaps fullfills your needs.
Here is the adapted code:
def findLessX(m,n,x):
i = 0
j = 0
while True:
if i+1<n and m[i+1][j]<x:
i=i+1
elif j+1<n and m[i][j+1]<x:
j=j+1
else:
print("n found at ", i+1 , " ", j+1, "or element not found so return max")
return (i+1)*(j+1)
Both answers suggested above will result in O(n^2).
In worst case, the algorithms will inspect all n^2 elements in the matrix.
Me and my mate were trying to create a fun game in python where the elements entered in the array are accessed in a spiral manner. I have tried few methods like one given below (source).
def spiral(X, Y):
x = y = 0
dx = 0
dy = -1
for i in range(max(X, Y)**2):
if (-X/2 < x <= X/2) and (-Y/2 < y <= Y/2):
print (x, y)
# DO STUFF...
if x == y or (x < 0 and x == -y) or (x > 0 and x == 1-y):
dx, dy = -dy, dx
x, y = x+dx, y+dy
The above statement accesses the elements in spiral loop and prints them for a defined array AE. I would like to know how can I transform a given array AE to a spiral one
You can build a spiral by starting near the center of the matrix and always turning right unless the element has been visited already:
#!/usr/bin/env python
NORTH, S, W, E = (0, -1), (0, 1), (-1, 0), (1, 0) # directions
turn_right = {NORTH: E, E: S, S: W, W: NORTH} # old -> new direction
def spiral(width, height):
if width < 1 or height < 1:
raise ValueError
x, y = width // 2, height // 2 # start near the center
dx, dy = NORTH # initial direction
matrix = [[None] * width for _ in range(height)]
count = 0
while True:
count += 1
matrix[y][x] = count # visit
# try to turn right
new_dx, new_dy = turn_right[dx,dy]
new_x, new_y = x + new_dx, y + new_dy
if (0 <= new_x < width and 0 <= new_y < height and
matrix[new_y][new_x] is None): # can turn right
x, y = new_x, new_y
dx, dy = new_dx, new_dy
else: # try to move straight
x, y = x + dx, y + dy
if not (0 <= x < width and 0 <= y < height):
return matrix # nowhere to go
def print_matrix(matrix):
width = len(str(max(el for row in matrix for el in row if el is not None)))
fmt = "{:0%dd}" % width
for row in matrix:
print(" ".join("_"*width if el is None else fmt.format(el) for el in row))
Example:
>>> print_matrix(spiral(5, 5))
21 22 23 24 25
20 07 08 09 10
19 06 01 02 11
18 05 04 03 12
17 16 15 14 13
Introductory remarks
The question is closely related to a problem of printing an array in spiral order. In fact, if we already have a function which does it, then the problem in question is relatively simple.
There is a multitude of resources on how to produce a spiral matrix or how to loop or print an array in spiral order. Even so, I decided to write my own version, using numpy arrays. The idea is not original but use of numpy makes the code more concise.
The other reason is that most of examples of producing a spiral matrix I found (including the code in the question and in the other answers) deal only with square matrices of size n x n for odd n. Finding the start (or end) point in matrices of other sizes may be tricky. For example, for a 3x5 matrix it can't be the middle cell. The code below is general and the position of the starting (ending) point depends on the choice of the function spiral_xxx.
Code
The first function unwraps an array in spiral order clockwise:
import numpy as np
def spiral_cw(A):
A = np.array(A)
out = []
while(A.size):
out.append(A[0]) # take first row
A = A[1:].T[::-1] # cut off first row and rotate counterclockwise
return np.concatenate(out)
We can write this function on eight different ways depending on where we start and how we rotate the matrix. I'll give another one, which is consistent (it will be evident later) with the matrix transformation in the image in the question. So, further on, I will be using this version:
def spiral_ccw(A):
A = np.array(A)
out = []
while(A.size):
out.append(A[0][::-1]) # first row reversed
A = A[1:][::-1].T # cut off first row and rotate clockwise
return np.concatenate(out)
How it works:
A = np.arange(15).reshape(3,5)
print(A)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
print(spiral_ccw(A))
[ 4 3 2 1 0 5 10 11 12 13 14 9 8 7 6]
Note that the end (or start) point is not the middle cell. This function works for all type of matrices but we will need a helper function that generates spiral indices:
def base_spiral(nrow, ncol):
return spiral_ccw(np.arange(nrow*ncol).reshape(nrow,ncol))[::-1]
For example:
print(base_spiral(3,5))
[ 6 7 8 9 14 13 12 11 10 5 0 1 2 3 4]
Now come the two main functions. One transforms a matrix to a spiral form of the same dimensions, the other reverts the transformation:
def to_spiral(A):
A = np.array(A)
B = np.empty_like(A)
B.flat[base_spiral(*A.shape)] = A.flat
return B
def from_spiral(A):
A = np.array(A)
return A.flat[base_spiral(*A.shape)].reshape(A.shape)
Examples
Matrix 3 x 5:
A = np.arange(15).reshape(3,5)
print(A)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
print(to_spiral(A))
[[10 11 12 13 14]
[ 9 0 1 2 3]
[ 8 7 6 5 4]]
print(from_spiral(to_spiral(A)))
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
Matrix from the question:
B = np.arange(1,26).reshape(5,5)
print(B)
[[ 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]]
print(to_spiral(B))
[[21 22 23 24 25]
[20 7 8 9 10]
[19 6 1 2 11]
[18 5 4 3 12]
[17 16 15 14 13]]
print(from_spiral(to_spiral(B)))
[[ 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]]
Remark
If you are going to work only with fixed size matrices, for example 5x5, then it's worth replacing base_spiral(*A.shape) in definitions of the functions with a fixed matrix of indices, say Ind (where Ind = base_spiral(5,5)).
Below is python3 code which transforms:
[[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24]]
to
[[20, 19, 18, 17, 16],
[21, 6, 5, 4, 15],
[22, 7, 0, 3, 14],
[23, 8, 1, 2, 13],
[24, 9, 10, 11, 12]]
You can easily change implementation in such way how do you want...
def spiral(X, Y):
x = y = 0
dx = 0
dy = -1
for i in range(max(X, Y) ** 2):
if (-X / 2 < x <= X / 2) and (-Y / 2 < y <= Y / 2):
yield x, y
# print(x, y)
# DO STUFF...
if x == y or (x < 0 and x == -y) or (x > 0 and x == 1 - y):
dx, dy = -dy, dx
x, y = x + dx, y + dy
spiral_matrix_size = 5
my_list = list(range(spiral_matrix_size**2))
my_list = [my_list[x:x + spiral_matrix_size] for x in range(0, len(my_list), spiral_matrix_size)]
print(my_list)
for i, (x, y) in enumerate(spiral(spiral_matrix_size, spiral_matrix_size)):
diff = int(spiral_matrix_size / 2)
my_list[x + diff][y + diff] = i
print(my_list)
Here's a solution using itertools and virtually no maths, just observations about what the spiral looks like. I think it's elegant and pretty easy to understand.
from math import ceil, sqrt
from itertools import cycle, count, izip
def spiral_distances():
"""
Yields 1, 1, 2, 2, 3, 3, ...
"""
for distance in count(1):
for _ in (0, 1):
yield distance
def clockwise_directions():
"""
Yields right, down, left, up, right, down, left, up, right, ...
"""
left = (-1, 0)
right = (1, 0)
up = (0, -1)
down = (0, 1)
return cycle((right, down, left, up))
def spiral_movements():
"""
Yields each individual movement to make a spiral:
right, down, left, left, up, up, right, right, right, down, down, down, ...
"""
for distance, direction in izip(spiral_distances(), clockwise_directions()):
for _ in range(distance):
yield direction
def square(width):
"""
Returns a width x width 2D list filled with Nones
"""
return [[None] * width for _ in range(width)]
def spiral(inp):
width = int(ceil(sqrt(len(inp))))
result = square(width)
x = width // 2
y = width // 2
for value, movement in izip(inp, spiral_movements()):
result[y][x] = value
dx, dy = movement
x += dx
y += dy
return result
Usage:
from pprint import pprint
pprint(spiral(range(1, 26)))
Output:
[[21, 22, 23, 24, 25],
[20, 7, 8, 9, 10],
[19, 6, 1, 2, 11],
[18, 5, 4, 3, 12],
[17, 16, 15, 14, 13]]
Here's the same solution shortened:
def stretch(items, counts):
for item, count in izip(items, counts):
for _ in range(count):
yield item
def spiral(inp):
width = int(ceil(sqrt(len(inp))))
result = [[None] * width for _ in range(width)]
x = width // 2
y = width // 2
for value, (dx, dy) in izip(inp,
stretch(cycle([(1, 0), (0, 1), (-1, 0), (0, -1)]),
stretch(count(1),
repeat(2)))):
result[y][x] = value
x += dx
y += dy
return result
I've ignored the fact that you want the input to be a 2D array since it makes much more sense for it to be any 1D iterable. You can easily flatten the input 2D array if you want. I've also assumed the output should be a square since I can't think what you'd sensibly want otherwise. It may go over the edge and raise an error if the square has even length and the input is too long: again, I don't know what the alternative would be.
You can fill an array with somehing like this :
#!/usr/bin/python
class filler:
def __init__(self, srcarray):
self.size = len(srcarray)
self.array = [[None for y in range(self.size)] for y in range(self.size)]
self.xpos, self.ypos = 0, 0
self.directions = [self.down, self.right, self.up, self.left]
self.direction = 0
self.fill(srcarray)
def fill(self, srcarray):
for row in reversed(srcarray):
for elem in reversed(row):
self.array[self.xpos][self.ypos] = elem
self.go_to_next()
def check_next_pos(self):
np = self.get_next_pos()
if np[1] in range(self.size) and np[0] in range(self.size):
return self.array[np[0]][np[1]] == None
return False
def go_to_next(self):
i = 0
while not self.check_next_pos() and i < 4:
self.direction = (self.direction + 1) % 4
i += 4
self.xpos, self.ypos = self.get_next_pos()
def get_next_pos(self):
return self.directions[self.direction](self.xpos, self.ypos)
def down(self, x, y):
return x + 1, y
def right(self, x, y):
return x, y + 1
def up(self, x, y):
return x - 1, y
def left(self, x, y):
return x, y - 1
def print_grid(self):
for row in self.array:
print(row)
f = filler([[x+y*5 for x in range(5)] for y in range(5)])
f.print_grid()
The output of this will be :
[24, 9, 10, 11, 12]
[23, 8, 1, 2, 13]
[22, 7, 0, 3, 14]
[21, 6, 5, 4, 15]
[20, 19, 18, 17, 16]
def counter(n):
for i in range(1,n*n):
yield i+1
n = 11
a = [[1 for x in range(n)] for y in range(n)]
x = y = n//2
val = counter(n)
for i in range(2, n, 2):
y += 1
x -= 1
for k in range(i):
x += 1
a[x][y] = next(val)
for k in range(i):
y -= 1
a[x][y] = next(val)
for k in range(i):
x -= 1
a[x][y] = next(val)
for k in range(i):
y += 1
a[x][y] = next(val)
for i in range(n):
for j in range(n):
print (a[i][j] , end="")
print (" " , end="")
print("\n")
I'm just doing something about generating various spiral indexing of a array and I add some simple modifications to the answer of ptrj to make the function more general. The modified function supports beginning the indexing from the four corners with clockwise and counter-clockwise directions.
def spiral_ind(A,start,direction):
if direction == 'cw':
if start == 'right top':
A = np.rot90(A)
elif start == 'left bottom':
A = np.rot90(A,k=3)
elif start == 'right bottom':
A = np.rot90(A,k=2)
elif direction == 'ccw':
if start == 'left top':
A = np.rot90(A,k=3)
elif start == 'left bottom':
A = np.rot90(A,k=2)
elif start == 'right bottom':
A = np.rot90(A)
out = []
while(A.size):
if direction == 'cw':
out.append(A[0])
A = A[1:].T[::-1]
elif direction == 'ccw':
out.append(A[0][::-1])
A = A[1:][::-1].T
return np.concatenate(out)
def spiral(m):
a=[]
t=list(zip(*m)) # you get the columns by zip function
while m!=[]:
if m==[]:
break
m=list(zip(*t)) # zip t will give you same m matrix. It is necessary for iteration
a.extend(m.pop(0)) # Step1 : pop first row
if m==[]:
break
t=list(zip(*m))
a.extend(t.pop(-1)) # Step 2: pop last column
if m==[]:
break
m=list(zip(*t))
a.extend(m.pop(-1)[::-1]) # Step 3: pop last row in reverse order
if m==[]:
break
t=list(zip(*m))
a.extend(t.pop(0)[::-1]) # Step 4: pop first column in reverse order
return a
This solution is O(n); just one while loop; much faster and can be used for much bigger dimensions of matrices
I had a related problem: I have two digital elevation models that might not be exactly aligned. To check how many cells they're miss-aligned by, I wanted a list of (x,y) offset tuples, starting with the smallest offsets first. I solved the problem by coding a spiral walk that creates square spirals of any size. It can travel either clockwise or counterclockwise. Commented code is below. Similar idea to some of the other solutions, but commented and with a bit less repeated code.
#Generates a list of offsets to check starting with the smallest offsets
#first. Seed the list with (0,0)
to_check = [(0,0)]
#Current index of the "walker"
cur_ind = np.array([0,0])
#Direction to start move along the sides
move_step = 1
#Controls the direction of the spiral
#any odd integer = counter clockwise
#any even integer = clockwise
ctr = 0
#The size of each side of the spiral to be created
size = 5
#Iterate the through the number of steps to take along each side
for i in range(1,size+1):
#Toggle the direction of movement along the sides
move_step *= -1
#Step along each of the two sides that has the same number of
#elements
for _ in range(2):
#Increment the counter (changes whether the x or y index in
#cur_ind is incremented)
ctr += 1
for ii in range(i):
#Move the "walker" in the direction indicated by move_step
#along the side indicated
cur_ind[ctr%2] += move_step
#Add the current location of the water to the list of index
#tuples
to_check.append((cur_ind[0],cur_ind[1]))
#Truncate the list to just the indices to create the spiral size
#requested
to_check = to_check[:size**2]
#Check that the spiral is working
#Create an empty array
arr = np.zeros([size,size])*np.nan
ctr = 1
#for each x,y offset pair:
for dx,dy in to_check:
#Starting at the approximate center of the array, place the ctr
#at the index indicated by the offset
arr[int(size/2)+dx,int(size/2)+dy]=ctr
ctr+=1
print(arr)
The last few lines just display the spiral:
[[13. 14. 15. 16. 17.]
[12. 3. 4. 5. 18.]
[11. 2. 1. 6. 19.]
[10. 9. 8. 7. 20.]
[25. 24. 23. 22. 21.]]
1How would I traverse an array from the centre outwards, visiting each cell only once?
I can do it with a breadthfirst traversal by finding unvisited neighbours, but how could I do it with some kind of edit-distance traversal? I've been trying to figure it out on paper, but can't wrap my head around it.
eg, in an array
[
[5 6 8 9 0]
[1 2 4 5 6]
[5 4 0 2 1]
[1 2 3 4 5]
[1 2 3 4 5]]
starting from the zero in the centre, we would visit the 4 at [1][2] then the 2 at [2][3] then the 3 at [3][2] then the 4 at [2][1] then the 8 at [0][2]and then the 5 at the [1][3] etc etc
I've tried this, which gets close, but misses some.
def traversalOrder(n): #n is size of array (always square)
n = n/2
t = []
for k in range(1,n+1):
t += [(i,j) for i in range(n-k,n+k+1) for j in range(n-k,n+k+1) if (i,j) not in t and (i-j == k or j-i == k) ]
I have an open source library pixelscan that does this sort of spatial pattern traversal on a grid. The library provides various scan functions and coordinate transformations. For example,
x0, y0, r1, r2 = 0, 0, 0, 2
for x, y in ringscan(x0, y0, r1, r2, metric=manhattan):
print x, y
where
x0 = Circle x center
y0 = Circle y center
r1 = Initial radius
r2 = Final radius
metric = Distance metric
produces the following points in a diamond:
(0,0) (0,1) (1,0) (0,-1) (-1,0) (0,2) (1,1) (2,0) (1,-1) (0,-2) (-1,-1) (-2,0) (-1,1)
You can apply a translation to start at any center point you want.
It seems that you could use some sort of priority queue to sort the elements based on an edit distance, like you suggested. While my edit distance doesn't give you the EXACT order you are looking for, it might be a starting point. I used heapq.
import heapq
grid = [
[5, 6, 8, 9, 0],
[1, 2, 4, 5, 6],
[5, 4, 0, 2, 1],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5]]
rows = len(grid)
cols = len(grid[0])
heap_list = []
for row in xrange(rows):
for col in xrange(cols):
edit_distance = abs(row - rows/2) + abs(col - cols/2)
#heappush(heap, (priority, object))
heapq.heappush(heap_list, (edit_distance, grid[row][col]))
for i in xrange(len(heap_list)):
print heapq.heappop(heap_list)
# prints (distance, value)
# (0, 0)
# (1, 2)
# (1, 3)
# (1, 4)
# (1, 4)
# (2, 1)
# etc...
I think the easiest way to do this is with three nested loops. The outer most loop is over the expanding radiuses of your diamonds. The next loop is the four sides of a given diamond, described by a starting point and a vector the move along. The innermost loop is over the points along that side.
def traversal(n):
h = n//2
yield h, h # center tile doesn't get handled the by the loops, so yield it first
for r in range(1, n):
for x0, y0, dx, dy in [(h, h-r, 1, 1),
(h+r, h, -1, 1),
(h, h+r, -1, -1),
(h-r, h, 1, -1)]:
for i in range(r):
x = x0 + dx*i
y = y0 + dy*i
if 0 <= x < n and 0 <= y < n:
yield x, y
If n is always odd, you can improve the performance of the inner loop a bit by limiting i rather than computing all the points and doing bounds tests to skip the ones that are outside your grid. Switching range(r) to range(max(0, r-h), max(r, h+1)) and getting rid of the if before the yield should do it. I'll leave the version above however, since its logic is much more clear.