Suppose I have a string with a length always a multiple of four:
'CodeReviewSE'
My goal is to create a permutation. First, I arrange the string into a matrix (where each row has a length of 4):
C o d e
R e v i
e w S E
And then I simply read down, starting from the top-left corner. I get four segments: CRe
, oew
, dvs
and eiE
. I concatenate them, to get the following string:
'CReoewdvSeiE'
Reversing the operation is just reversing the steps, kinda. It follows the same steps, except for row length are len(text) / 4
, and we read down again, to look like this:
C R e
o e w
d v S
e i E
I've implemented the following code in Python:
import collections
def permute(text):
# type: (str) -> str
rows = collections.deque()
output = ''
# Create matrix
for i in xrange(0, len(text), 4):
rows.append(text[i:i+4])
# Read matrix downwards
for counter in xrange(4):
for row in rows:
output += row[counter]
return output
def unpermute(text):
# type: (str) -> str
rows = collections.deque()
output = ''
length = len(text) / 4
# Create matrix
for i in xrange(0, len(text), length):
rows.append(text[i:i+length])
# Now read downwards
for counter in xrange(length):
for row in rows:
output += row[counter]
return output
I also ran a performance tests using the timeit
module, using random strings of different lengths (where n
is the length):
n = 32 Best: | Avg Permute: (10,000 loops, best of 10): 0.06563 0.07080 Unpermute: (10,000 loops, best of 10): 0.06337 0.06655 n = 64 Best: | Avg Permute: (10,000 loops, best of 10): 0.11592 0.11939 Unpermute: (10,000 loops, best of 10): 0.10358 0.10530 n = 256 Best: | Avg Permute: (10,000 loops, best of 10): 0.43404 0.44310 Unpermute: (10,000 loops, best of 10): 0.37202 0.38102 n = 1024 Best: | Avg Permute: (10,000 loops, best of 10): 1.82779 1.85647 Unpermute: (10,000 loops, best of 10): 1.72195 1.78491
1 Answer 1
Most important comment: permute
is just unpermute
with length = 4
.
There seems to be no reason to favor collections.deque
over a normal list
here. It is only an advantage of you do a lot of queue.popleft()
calls.
For the creation of rows
you can just use a list comprehension:
rows = (text[i:i+4] for i in xrange(0, len(text), 4))
I would use a generator expression here to save memory
You could simplify your un-/permute to: something like:
def permute(text, n=4, reverse=False):
n = len(text)/n if reverse else n
matrix = (text[i:i+n] for i in range(0, len(text), n))
matrix_t = zip(*matrix)
return "".join("".join(x) for x in matrix_t)
where (text[i:i+n] for i in range(0, len(text), n))
makes a generator object that is like your rows
, zip(*list)
transposes a list of lists and the two joins first join each row and then the rows.
This will not even create a lot of overhead because they are all generator objects (and therefore it is not one possibly big list being read into memory).
You could even inline all of it:
def permute(text, n):
return "".join("".join(x) for x in zip(*(text[i:i+n] for i in range(0, len(text), n))))
-
\$\begingroup\$ "permute is just unpermute with length = 4" should be bolded / underlined in my opinion, merging these functions avoids massive repetition. \$\endgroup\$Caridorc– Caridorc2016年09月01日 11:48:45 +00:00Commented Sep 1, 2016 at 11:48
-
\$\begingroup\$ Oh wow. This answer is awesome. I never thought about using
zip
here, and it does exactly what I want to do. The generator objects are better too. Thank you so much :) \$\endgroup\$Zizouz212– Zizouz2122016年09月01日 16:44:17 +00:00Commented Sep 1, 2016 at 16:44 -
\$\begingroup\$ Nah, it's waaay better! With random strings of length 1024, permuting only takes
0.868s
on my machine, way faster than the1.828s
I had before. \$\endgroup\$Zizouz212– Zizouz2122016年09月01日 16:47:39 +00:00Commented Sep 1, 2016 at 16:47 -
\$\begingroup\$ Yay, that wasnt even my goal :-) \$\endgroup\$Graipher– Graipher2016年09月01日 21:24:12 +00:00Commented Sep 1, 2016 at 21:24