HackerRank: Encryption Given a two-dimensional list, say some_list = ["ABCD", "EFGH", "IJ"], I am supposed to encrypt it by reading each column top to bottom, thus:


A B C D

E F G H

I J



Would become:


AEI BFJ CG DH



My initial try had “index out of range” problem so I did this:


def encrypt_list(some_list):
    encrpted_list = []
    for i in range(c):
        string = ""
        for j in range(f):
            string += my_list[j][i:i + 1]
        encrpted_list.append(string)
    return encrpted_list



Which looks like a hack. Is there a more Pythonic way?

(This is essentially the Encryption problem from HackerRank.)

Complete working code:

import math

s = "feedthedog"
length_s = len(s)

f = math.floor(math.sqrt(length_s))
c = math.ceil(math.sqrt(length_s))

if c * f &lt; length_s:
    f = c


def chunks(string, split_point):
    for start in range(0, len(string), split_point):
        yield s[start:start + split_point]


split_list = [chunk for chunk in chunks(s, c)]


def encrypt_list(my_list):
    encrpted_list = []
    for i in range(c):
        string = ""
        for j in range(f):
            string += my_list[j][i:i + 1]
        encrpted_list.append(string)
    return encrpted_list


solution_list = encrypt_list(split_list)


for item in solution_list:
    print(item, end=" ")
There's an app for that

The nice thing about the python standard library, especially itertools is that there's always a function for what you want to do. In this case, you want to take chunks like:

['ABCD', 'EFGH', 'IJ']


and turn them into chunks like:

['AEI', 'BFJ', 'CG', 'DH']


That is, we're round robining through each row. There's a recipe for that:

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --&gt; A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))


So ultimately, we have just a couple steps:


Get our chunks.
Round robin through them.
Join the result

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i+n]

def encrypt(s):
    n = int(math.sqrt(len(s))
    chunked = chunks(s, n)
    return ''.join(roundrobin(*chunked))



Prefer to join

In your solution, you have:

string = ""
for j in range(f):
    string += my_list[j][i:i + 1]
encrpted_list.append(string)


This is inefficient though. When we're building up strings, it's much better to join them:

encrpted_list.append(''.join(my_list[j][i:i+1] for j in range(f)))


Evaluate lazily

This is inefficient:

split_list = [chunk for chunk in chunks(s, c)]


You likely only evaluated out chunks due to the way you wrote your encrypter, but you want to keep things as generators for as long as possible. It's more efficient to pass in chunks(s,c) into the zip_longest() than it would be to fully evaluate it. Though if you do, it's shorter to just write:

split_list = list(chunks(s,c))


Minor points


Excessive amounts of vertical spacing here.
encrpted_list is misspelled: should be encrypted_list.
Why do you have encrypt_list that takes a list and returns a list? You should have encrypt that takes a string and returns a string.
You don't need f,c... you can use int to get the floor, and add 1 if necessary. And avoid single-letter names.