Вот более быстрое решение с использованием словаря кортежей для 256 возможных символов:
bits = [1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0]
chars = { tuple(map(int,f"{n:08b}"[::-1])):chr(n) for n in range(0,256) }
def toChars(bits):
return "".join(chars[tuple(bits[i:i+8])] for i in range(0,len(bits),8) )
примерно в 3 раза быстрее, чем оригинальное решение
[EDIT] и еще быстрее, используя байты и zip:
chars = { tuple(map(int,f"{n:08b}")):n for n in range(256) }
def toChars(bits):
return bytes(chars[b] for b in zip(*(bits[7-i::8] for i in range(8)))).decode()
примерно в 2 раза быстрее предыдущего (в длинных списках)
[EDIT2] немного объяснений этому последний ...
b
в понимании списка будет кортеж из 8 бит chars[b]
вернет целое число, соответствующее 8 битам bytes(...).decode()
преобразует список целых чисел в строку на основе chr (n) каждого значения zip(*(... 8 bit iterators...))
распаковывает 8 последовательных последовательных битов, каждый из которых имеет свою начальную точку
Стратегия с распакованным zip-кодом заключается в go через биты с шагом 8. Например, если бы мы проходили 8 параллельных диапазонов, мы получили бы это:
bits[7::8] -> [ 0, 0, ... ] zip returns: (0,1,0,0,0,1,1)
bits[6::8] -> [ 1, 1, ... ] (0,1,1,0,1,1,1)
bits[5::8] -> [ 0, 1, ... ] ...
bits[4::8] -> [ 0, 0, ... ]
bits[3::8] -> [ 0, 1, ... ]
bits[2::8] -> [ 0, 1, ... ]
bits[1::8] -> [ 1, 1, ... ]
bits[0::8] -> [ 1, 1, ... ]
Функция zip примет один столбец этого за итерацию и возвратит это как кортеж битов.