Boa
Boa

Reputation: 331

Any way to convert this for loop with list comprehension?

The reason I am asking is that in incorporates various data structures, dict, list, string.

bffer = ""
dict_size = 128
for character in string:
    appnd_bffer = bffer + character
    if appnd_bffer in codebook:
        bffer = appnd_bffer
    else:
        output_list.append(codebook[bffer])
        codebook[appnd_bffer] = dict_size
        dict_size += 1
        bffer = character

I am new to list comprehensions so I could really use an explanation if it is possible. Cheers.

Upvotes: 1

Views: 218

Answers (3)

PM 2Ring
PM 2Ring

Reputation: 55479

Just to give you an idea of why it's a really bad idea to try and stuff your LZW compressor's loop into a list comprehension, I've actually gone ahead and written some crazy code that does just that.

It uses some rather dodgy tricks, and it won't work on Python 3 because in Python 3 a list comprehension runs in its own scope; in Python 2 a list comprehension runs in the scope of the surrounding code. And that means my trick of passing the original buffer into the the list comp won't work in Python 3.

Firstly, here's your original code wrapped in a function, with a couple of variable name changes, and a few added extras to make it a runnable, testable example.

from __future__ import print_function

def lzw_compress_Boa(data):
    dict_size = 128
    codebook = {chr(i): i for i in range(dict_size)}

    output_list = []
    oldbuff = ""
    for ch in data:
        newbuff = oldbuff + ch
        if newbuff in codebook:
            oldbuff = newbuff
        else:
            output_list.append(codebook[oldbuff])
            codebook[newbuff] = dict_size
            dict_size += 1
            oldbuff = ch
    return output_list

data = 'this data is this data'

output_list = lzw_compress_Boa(data)
print(output_list)
print(len(data), '->', len(output_list))

output

[116, 104, 105, 115, 32, 100, 97, 116, 97, 32, 130, 32, 128, 138, 133]
22 -> 15

We don't actually need to keep the codebook's size in a separate variable. Like all of Python's built-in container types, a dict keeps track of its size, and we can use the len() function to get it. So we can replace these 2 lines:

codebook[newbuff] = dict_size
dict_size += 1

with this line:

codebook[newbuff] = len(codebook)

Now here's the crazy version that uses a list comprehension. Remember kids, please don't try this at home! :)

def lzw_compress_crazy(data):
    dict_size = 128
    codebook = {chr(i): i for i in range(dict_size)}

    def magic(oldbuff, ch):
        newbuff = oldbuff + ch
        if newbuff in codebook:
            return [(newbuff, None)]
        else:
            codebook[newbuff] = len(codebook)
            return [(ch, codebook[oldbuff])]

    oldbuff = ""
    return [result for ch in data
        for oldbuff, result in magic(oldbuff, ch) if result]

Note that this version is not only harder to read than the first one I posted, it's also less efficient. And as I said at the start, it's less portable, and it uses some totally dodgy tricks that should never be used in serious code.

List comprehensions are cool, and once you're used to them they can make your code more concise, which can aid in readability, as long as you don't try to do too much in them. A list comp is slightly more efficient than equivalent code using .append in a "traditional" style for loop, but they aren't magic, and using an incomprehensible list comprehension instead of a a nice clear readable traditional for loop is not Pythonic.

Upvotes: 5

Martijn Pieters
Martijn Pieters

Reputation: 1122552

The loop currently depends on being able to assign to bffer. Because assignment is a statement, and list comprehensions only can contain expressions, converting this to a list comprehension would require a lot of hard-to-follow trickery with mutable objects.

As such, converting this to a list comprehension would result in an unreadable mess, without any clear benefits (any speed benefits from removing the list.append() call will be offset by the mutable object manipulations).

Upvotes: 8

Noah
Noah

Reputation: 1429

No, this cannot be made into a list comprehension because it contains expressions other than the ones that make up the list (e.g. dict_size += 1). Those expressions just have no place to go in a list comprehension, since the only purpose of a comprehension is to create that object. What might help you is to create a separate function containing the logic for a single iteration of the loop, then use that function in a loop.

Upvotes: 2

Related Questions