В вашем коде есть несколько недоразумений и большая неэффективность. Давайте начнем с недоразумений.
Поскольку firm.ori
открывается в двоичном режиме (rb
), результатом s = f.read()
является bytes
объект. Несмотря на то, что методы похожи на строку, это не строка! Содержит байты, а не символы. При его отображении выходные данные \x...
не указывают, что объект bytes
содержит обратную косую черту ASCII и xes. Вместо этого каждый \x...
является escape-последовательностью, используемой для представления шестнадцатеричного значения данного байта, которое не соответствует печатному символу ASCII.
Внутри вашего цикла вы имеете дело исключительно со строками: hexstr = '\\x'.join('{:02x}'.format(x) for x in i)
берет вашу перестановку и форматирует ее так, чтобы она выглядела как строковое представление объекта bytes
. Надеюсь, вы поняли из предыдущего параграфа, почему это не сработает.
s.find(b'hexstrfinal')
ищет буквенный массив ASCII b'hexstrfinal'
, а не переменную с именем hexstrfinal
. Последнее, конечно, не сработает, потому что hexstrfinal
имеет тип str
, а не bytes
. Если бы вы конвертировали его в bytes
, используя простой hexstrfinal.encode('ascii')
, вы бы получили b'\\x...'
, что совсем не то, что вы хотите. Правильный путь будет
s.find(hexstrfinal.encode('ascii').decode('unicode-escape').encode('latin1'))
Надеюсь, вы понимаете, почему излишне неэффективно преобразовывать строку три раза, чтобы получить bytes
, который вы хотите. Каждый раз, когда вы начинаете использовать строки в качестве опоры для манипулирования числами, самое время оценить ваш подход. Это начинает наше обсуждение неэффективности в вашем коде.
В настоящее время вы пытаетесь перебрать все возможные перестановки 0-7, а не искать перестановки, которые на самом деле есть. Учитывая, что размер файла составляет всего 200 КБ, было бы неразумно ожидать, что в нем появятся все или даже большинство перестановок. Кроме того, вы ищете во всем файле для каждой возможной перестановки. Для перестановок файлов размером N
и K
ваш код выполняется за O(N * K)
времени, хотя это можно сделать за один проход через файл, или O(N)
. При наличии соответствующих структур данных даже цикл, написанный на простом Python, скорее всего будет работать быстрее, чем оптимизированная версия текущего кода.
Стратегия проста. Итерация по s
. Если текущий персонаж и следующие семь составляют правильную перестановку, начните вечеринку. В противном случае продолжайте искать:
N = 8
allowed = set(range(N))
for index, b in enumerate(s):
if b in allowed and set(s[index:index + N]) == allowed:
print(f'Found sequence {s[index:index + N]} at offset {index}')
Здесь возможен целый ряд возможных оптимизаций, и вы могли бы сделать все это более эффективно с помощью numpy или scipy.
Вещи также станут более сложными, если вы разрешите повторения в последовательности. В этом случае вам придется отсортировать последовательности:
allowed = sorted(...)
N = len(allowed)
for index, b in enumerate(s):
if b in allowed and sorted(s[index:index + N]) == allowed:
print(f'Found sequence {s[index:index + N]} at offset {index}')
Если вы собираетесь искать кусочки, все становится еще сложнее. Я бы полностью отбросил чек b in allowed
и просто написал пользовательский чек, который можно было бы применить на каждом полушаге:
N = 8
def build_set(items):
output = set()
for b in items:
output.add(b & 0xF)
output.add((b >> 4) & 0xF)
return output
def matches(seq):
if len(seq) == N // 2:
return build_set(seq) == allowed
elif len(seq) == N // 2 + 1:
check = build_set(seq[1:-1])
check.add(seq[0] & 0xF)
check.add((seq[-1] >> 4) & 0xF)
return check == allowed
else:
return False
allowed = set(range())
for index, b in enumerate(s):
if matches(s[index:index + N // 2]):
print(f'Found sequence {s[index:index + N // 2]} at offset {index}.0')
if matches(s[index:index + N // 2 + 1]):
print(f'Found sequence {s[index:index + N // 2 + 1]]} at offset {index}.5')
Здесь build_set
просто разбивает кусочки на множество. matches
проверяет либо массив из 8 полубайтов, выровненный по байту (4 элемента), либо массив из 8 полубайтов, смещенный на половину байта (5 элементов). Об обоих случаях сообщается независимо.