JadeSpy
JadeSpy

Reputation: 141

How could I improve the speed of my algorithm, even by the slightest amount

How can I improve the speed of my algorithm, even by the slightest amount. For the reason I made this I don't actually care about speed in the slightest, but I'm wondering what I could change if I did. Even more than pointing out specific things to change, what I really want to know is how you know that there's a more efficient way and how that way works. Basically, what is the track to follow if you want to learn how the low level stuff works so that you can write better code at the higher level.

def decode_all(ob):
    if(type(ob)==list):
        for i in range(len(ob)):
            ob[i] = decode_all(ob[i])
    if(type(ob)==dict):
        for i in ob.copy():
            ob[i] = decode_all(ob[i])
            if(type(i)==bytes):
                ob[i.decode()] = ob[i]
                ob.pop(i)
    if(type(ob)==tuple):
        new_ob = []
        for i in ob:
            new_ob.append(decode_all(i))
        ob = tuple(new_ob)
    if(type(ob)==bytes):
        return ob.decode()
    return ob

Upvotes: 1

Views: 300

Answers (1)

Pierre D
Pierre D

Reputation: 26221

Modified code:

def decode_all(ob):
    if isinstance(ob, list):
        return [decode_all(e) for e in ob]
    if isinstance(ob, dict):
        return {decode_all(k): decode_all(v) for k, v in ob.items()}
    if isinstance(ob, tuple):
        return tuple([decode_all(e) for e in ob])
    if isinstance(ob, bytes):
        return ob.decode()
    return ob

Usually, I agree with the use of if ... elif ... elif ..., but in this case, it's simpler to just return the result right away.

Some notes:

  1. No side effects: nothing in ob or any of its components (at whatever depth) is modified; all new collections and elements are returned, at some (slight) performance cost for their allocation.
  2. Use isinstance(), not if type(x) == y (just a good habit, no particular advantage here with base types).
  3. Use list and dict comprehensions; far simpler, no fear of changing the original, and essentially pythonic.
  4. No need to handle the case where dict keys would be bytes: use your own function instead.
  5. tuple([x for x in list_]) is slightly faster than tuple(x for x in list_), I learned recently.

Some alternatives:

  1. If you prefer if ... elif ...:
def decode_all(ob):
    if isinstance(ob, list):
        ob = [decode_all(e) for e in ob]
    elif isinstance(ob, dict):
        ob = {decode_all(k): decode_all(v) for k, v in ob.items()}
    elif isinstance(ob, tuple):
        ob = tuple(decode_all(e) for e in ob)
    elif isinstance(ob, bytes):
        ob = ob.decode()
    return ob
  1. Fastest (but a bit opaque):
def _fun_bytes(ob):
    return ob.decode()

def _fun_list(ob):
    return [decode_all(e) for e in ob]

def _fun_dict(ob):
    return {decode_all(k): decode_all(v) for k, v in ob.items()}

def _fun_tuple(ob):
    return tuple([decode_all(e) for e in ob])

def _identity(ob):
    return ob

fun_dict = {
    bytes: _fun_bytes,
    list: _fun_list,
    dict: _fun_dict,
    tuple: _fun_tuple,
}

def decode_all(ob):
    return fun_dict.get(type(ob), _identity)(ob)
  1. Most extensible (you can add various .register in various areas in your codebase, e.g. to handle new types):
from functools import singledispatch

@singledispatch
def decode_all(ob):
    return ob

@decode_all.register
def _(ob: bytes):
    return ob.decode()

@decode_all.register
def _(ob: list):
    return [decode_all(e) for e in ob]

@decode_all.register
def _(ob: dict):
    return {decode_all(k): decode_all(v) for k, v in ob.items()}

@decode_all.register
def _(ob: tuple):
    return tuple([decode_all(e) for e in ob])

Upvotes: 4

Related Questions