Nano Hash - криптовалюты, майнинг, программирование

Как эффективно анализировать файлы фиксированной ширины?

Я пытаюсь найти эффективный способ анализа файлов, который содержит строки фиксированной ширины. Например, первые 20 символов представляют столбец, с 21:30 — еще один и так далее.

Предполагая, что строка содержит 100 символов, как можно эффективно разбить строку на несколько компонентов?

Я мог бы использовать нарезку строк на строку, но это немного некрасиво, если строка большая. Есть ли другие быстрые методы?

06.02.2011

Ответы:


1

Использование модуля стандартной библиотеки Python struct было бы довольно просто, поскольку а также очень быстро, так как он написан на C.

Вот как это можно использовать, чтобы делать то, что вы хотите. Это также позволяет пропускать столбцы символов, указывая отрицательные значения количества символов в поле.

import struct

fieldwidths = (2, -10, 24)  # negative widths represent ignored padding fields
fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's')
                        for fw in fieldwidths)
fieldstruct = struct.Struct(fmtstring)
parse = fieldstruct.unpack_from
print('fmtstring: {!r}, recsize: {} chars'.format(fmtstring, fieldstruct.size))

line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fields = parse(line)
print('fields: {}'.format(fields))

Вывод:

fmtstring: '2s 10x 24s', recsize: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')

Следующие модификации адаптируют его для работы в Python 2 или 3 (и обработают ввод Unicode):

import struct
import sys

fieldstruct = struct.Struct(fmtstring)
if sys.version_info[0] < 3:
    parse = fieldstruct.unpack_from
else:
    # converts unicode input to byte string and results back to unicode string
    unpack = fieldstruct.unpack_from
    parse = lambda line: tuple(s.decode() for s in unpack(line.encode()))

Вот способ сделать это с помощью фрагментов строк, как вы рассматривали, но опасались, что это может стать слишком уродливым. Преимущество этого заключается в том, что помимо того, что он не такой уродливый, он работает без изменений как в Python 2, так и в 3, а также может обрабатывать строки Unicode. С точки зрения скорости он, конечно, медленнее, чем версии, основанные на модуле struct, но его можно немного ускорить, убрав возможность иметь поля заполнения.

try:
    from itertools import izip_longest  # added in Py 2.6
except ImportError:
    from itertools import zip_longest as izip_longest  # name change in Py 3.x

try:
    from itertools import accumulate  # added in Py 3.2
except ImportError:
    def accumulate(iterable):
        'Return running totals (simplified version).'
        total = next(iterable)
        yield total
        for value in iterable:
            total += value
            yield total

def make_parser(fieldwidths):
    cuts = tuple(cut for cut in accumulate(abs(fw) for fw in fieldwidths))
    pads = tuple(fw < 0 for fw in fieldwidths) # bool values for padding fields
    flds = tuple(izip_longest(pads, (0,)+cuts, cuts))[:-1]  # ignore final one
    parse = lambda line: tuple(line[i:j] for pad, i, j in flds if not pad)
    # optional informational function attributes
    parse.size = sum(abs(fw) for fw in fieldwidths)
    parse.fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's')
                                                for fw in fieldwidths)
    return parse

line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fieldwidths = (2, -10, 24)  # negative widths represent ignored padding fields
parse = make_parser(fieldwidths)
fields = parse(line)
print('format: {!r}, rec size: {} chars'.format(parse.fmtstring, parse.size))
print('fields: {}'.format(fields))

Вывод:

format: '2s 10x 24s', rec size: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')
06.02.2011
  • +1 это приятно. В некотором смысле, я думаю, что это похоже на мой подход (по крайней мере, когда вы получаете результаты), но, очевидно, намного быстрее. 06.02.2011
  • Как это будет работать с юникодом? Или строка в кодировке utf-8? struct.unpack похоже работает с двоичными данными. Я не могу заставить это работать. 06.02.2011
  • @Reiner Gerecke: модуль struct предназначен для работы с двоичными данными. Файлы с полями фиксированной ширины являются устаревшими заданиями, которые также, скорее всего, будут старше UTF-8 (если не в хронологии, то в уме). Байты, прочитанные из файлов, являются двоичными данными. У вас нет юникода в файлах. Вам нужно декодировать байты, чтобы получить юникод. 07.02.2011
  • @Reiner Gerecke: Уточнение: в этих устаревших форматах файлов каждое поле представляет собой фиксированное количество байтов, а не фиксированное количество символов. Хотя вряд ли они будут закодированы в UTF-8, они могут быть закодированы в кодировке с переменным количеством байтов на символ, например. gbk, big5, euc-jp, shift-jis и т. д. Если вы хотите работать в юникоде, вы не можете сразу декодировать всю запись; вам нужно расшифровать каждое поле. 07.02.2011
  • @John Machin Спасибо, я понял, что вы имели в виду. По какой-то причине я не рассматривал это как подход к устаревшим форматам файлов, поэтому я спрашиваю о многобайтовых символах. (И да, думать о юникоде было глупо) 07.02.2011
  • Кроме того, что-то, что меня сбило с толку, fieldwidths - это ширина каждой извлеченной части строки, и они должны составлять общую длину строки, они не обязательно являются индексом, по которому будет нарезана проанализированная строка. (2,10,24) тебя не достанет ('AB','CDEFGHIJ','KLMNOPQRSTUVWX','YZ0123456789') 18.03.2013
  • @dnfehren: Точно. Отчасти поэтому я выделил их и дал описательное имя fieldwidths (вместо того, чтобы просто поместить литерал кортежа в выражение генератора, используемое в вызове join()). Они не должны составлять общую длину строки, по крайней мере, столько (это показано в примере, где завершающий \n игнорируется). 18.03.2013
  • Интересно, кажется, намного быстрее, чем использование срезов. Но что, если нам не нужны все поля, допустим, мы хотим извлечь только ('AB', 'MNOPQRSTUVWXYZ0123456789'). Есть ли способ указать это в fmtstring? Спасибо 09.10.2013
  • @Mannaggia: извините за задержку с ответом. Да, модуль struct имеет символ формата 'x' для байтов заполнения. Таким образом, если бы fmtstring было '2s 10x 24s' с тестовой строкой, результат был бы просто ('AB', 'MNOPQRSTUVWXYZ0123456789'). На самом деле я думаю, что это было бы хорошим улучшением, и я добавил эту функцию в свой ответ. Спасибо! 15.11.2013
  • @Reiner Gerecke: Только что перечитал ваш давний комментарий об использовании модульного подхода struct для строк в кодировке utf-8 и хотел предупредить вас о том факте, что Обновление 1 решает эту проблему (хотя это было сделано для совместимости с Python 3). 13.02.2014
  • Это полностью ломается, когда вы пытаетесь применить это для значений Unicode (например, в Python 3) с текстом вне набора символов ASCII и где «фиксированная ширина» означает «фиксированное количество символов», а не байтов. 21.11.2014
  • @MartijnPieters: я думаю, что обновление № 2 прекрасно обрабатывает значения Unicode в Python 3 (с использованием ширины символьного поля). 24.05.2016
  • @martineau: спасибо за обновление! Да, использование среза должно заставить это работать с шириной поля, основанной на кодовых точках, а не на байтах. :-) 24.05.2016

  • 2

    Я не совсем уверен, что это эффективно, но оно должно быть читабельным (в отличие от нарезки вручную). Я определил функцию slices, которая получает длину строки и столбца и возвращает подстроки. Я сделал его генератором, поэтому для очень длинных строк он не создает временный список подстрок.

    def slices(s, *args):
        position = 0
        for length in args:
            yield s[position:position + length]
            position += length
    

    Пример

    In [32]: list(slices('abcdefghijklmnopqrstuvwxyz0123456789', 2))
    Out[32]: ['ab']
    
    In [33]: list(slices('abcdefghijklmnopqrstuvwxyz0123456789', 2, 10, 50))
    Out[33]: ['ab', 'cdefghijkl', 'mnopqrstuvwxyz0123456789']
    
    In [51]: d,c,h = slices('dogcathouse', 3, 3, 5)
    In [52]: d,c,h
    Out[52]: ('dog', 'cat', 'house')
    

    Но я думаю, что преимущество генератора теряется, если вам нужны все столбцы сразу. Где можно извлечь выгоду, так это когда вы хотите обрабатывать столбцы один за другим, скажем, в цикле.

    06.02.2011
  • AFAICT, этот метод медленнее, чем struct, но он удобочитаем и прост в обращении. Я провел несколько тестов с использованием вашего модуля slices function, struct, а также модуля re, и оказалось, что для больших файлов struct является самым быстрым, re занимает второе место (в 1,5 раза медленнее) и slices третье (в 2 раза медленнее). Однако при использовании struct возникают небольшие накладные расходы, поэтому slices function может работать быстрее с файлами меньшего размера. 14.05.2018

  • 3

    Еще два варианта, которые проще и красивее уже упомянутых решений:

    Первый использует панд:

    import pandas as pd
    
    path = 'filename.txt'
    
    # Using Pandas with a column specification
    col_specification = [(0, 20), (21, 30), (31, 50), (51, 100)]
    data = pd.read_fwf(path, colspecs=col_specification)
    

    И второй вариант с использованием numpy.loadtxt:

    import numpy as np
    
    # Using NumPy and letting it figure it out automagically
    data_also = np.loadtxt(path)
    

    Это действительно зависит от того, как вы хотите использовать свои данные.

    29.04.2014
  • Является ли это конкурентоспособным с принятым ответом с точки зрения скорости? 14.03.2016
  • Не проверял, но это должно быть намного быстрее, чем принятый ответ. 14.03.2016
  • Pandas может выполнять автоматическое обнаружение, если вы установите colspecs='infer' pandas. pydata.org/pandas-docs/stable/generated/ 25.12.2018

  • 4

    Приведенный ниже код дает набросок того, что вы можете захотеть сделать, если у вас есть серьезная работа с файлами с фиксированной шириной столбца.

    «Серьезный» = несколько типов записей в каждом из нескольких типов файлов, записи до 1000 байт, определитель макета и «противоположный» производитель/потребитель — это государственное учреждение с отношением, изменения макета приводят к неиспользуемым столбцам, до миллиона записей в файле...

    Особенности: Предварительно компилирует форматы структур. Игнорирует нежелательные столбцы. Преобразует входные строки в требуемые типы данных (в эскизе отсутствует обработка ошибок). Преобразует записи в экземпляры объектов (или словари, или именованные кортежи, если хотите).

    Код:

    import struct, datetime, io, pprint
    
    # functions for converting input fields to usable data
    cnv_text = rstrip
    cnv_int = int
    cnv_date_dmy = lambda s: datetime.datetime.strptime(s, "%d%m%Y") # ddmmyyyy
    # etc
    
    # field specs (field name, start pos (1-relative), len, converter func)
    fieldspecs = [
        ('surname', 11, 20, cnv_text),
        ('given_names', 31, 20, cnv_text),
        ('birth_date', 51, 8, cnv_date_dmy),
        ('start_date', 71, 8, cnv_date_dmy),
        ]
    
    fieldspecs.sort(key=lambda x: x[1]) # just in case
    
    # build the format for struct.unpack
    unpack_len = 0
    unpack_fmt = ""
    for fieldspec in fieldspecs:
        start = fieldspec[1] - 1
        end = start + fieldspec[2]
        if start > unpack_len:
            unpack_fmt += str(start - unpack_len) + "x"
        unpack_fmt += str(end - start) + "s"
        unpack_len = end
    field_indices = range(len(fieldspecs))
    print unpack_len, unpack_fmt
    unpacker = struct.Struct(unpack_fmt).unpack_from
    
    class Record(object):
        pass
        # or use named tuples
    
    raw_data = """\
    ....v....1....v....2....v....3....v....4....v....5....v....6....v....7....v....8
              Featherstonehaugh   Algernon Marmaduke  31121969            01012005XX
    """
    
    f = cStringIO.StringIO(raw_data)
    headings = f.next()
    for line in f:
        # The guts of this loop would of course be hidden away in a function/method
        # and could be made less ugly
        raw_fields = unpacker(line)
        r = Record()
        for x in field_indices:
            setattr(r, fieldspecs[x][0], fieldspecs[x][3](raw_fields[x]))
        pprint.pprint(r.__dict__)
        print "Customer name:", r.given_names, r.surname
    

    Вывод:

    78 10x20s20s8s12x8s
    {'birth_date': datetime.datetime(1969, 12, 31, 0, 0),
     'given_names': 'Algernon Marmaduke',
     'start_date': datetime.datetime(2005, 1, 1, 0, 0),
     'surname': 'Featherstonehaugh'}
    Customer name: Algernon Marmaduke Featherstonehaugh
    
    06.02.2011
  • Как можно обновить этот код для анализа записей размером более 1000 байт? Я сталкиваюсь с этой ошибкой: struct.error: unpack_from requires a buffer of at least 1157 bytes 04.04.2018

  • 5

    Вот как я решил со словарем, который содержит, где поля начинаются и заканчиваются. Предоставление начальной и конечной точек помогло мне также управлять изменениями длины столбца.

    # fixed length
    #      '---------- ------- ----------- -----------'
    line = '20.06.2019 myname  active      mydevice   '
    SLICES = {'date_start': 0,
             'date_end': 10,
             'name_start': 11,
             'name_end': 18,
             'status_start': 19,
             'status_end': 30,
             'device_start': 31,
             'device_end': 42}
    
    def get_values_as_dict(line, SLICES):
        values = {}
        key_list = {key.split("_")[0] for key in SLICES.keys()}
        for key in key_list:
           values[key] = line[SLICES[key+"_start"]:SLICES[key+"_end"]].strip()
        return values
    
    >>> print (get_values_as_dict(line,SLICES))
    {'status': 'active', 'name': 'myname', 'date': '20.06.2019', 'device': 'mydevice'}
    
    20.06.2019

    6

    Вот что NumPy использует под капотом (значительно упрощенно, но все же — этот код находится в LineSplitter class внутри _iotools module):

    import numpy as np
    
    DELIMITER = (20, 10, 10, 20, 10, 10, 20)
    
    idx = np.cumsum([0] + list(DELIMITER))
    slices = [slice(i, j) for (i, j) in zip(idx[:-1], idx[1:])]
    
    def parse(line):
        return [line[s] for s in slices]
    

    Он не обрабатывает отрицательные разделители для игнорирования столбца, поэтому он не так универсален, как struct, но быстрее.

    14.05.2018

    7

    Вот простой модуль для Python 3, основанный на ответе Джона Мачина - адаптируйтесь по мере необходимости :)

    """
    fixedwidth
    
    Parse and iterate through a fixedwidth text file, returning record objects.
    
    Adapted from https://stackoverflow.com/a/4916375/243392
    
    
    USAGE
    
        import fixedwidth, pprint
    
        # define the fixed width fields we want
        # fieldspecs is a list of [name, description, start, width, type] arrays.
        fieldspecs = [
            ["FILEID", "File Identification", 1, 6, "A/N"],
            ["STUSAB", "State/U.S. Abbreviation (USPS)", 7, 2, "A"],
            ["SUMLEV", "Summary Level", 9, 3, "A/N"],
            ["LOGRECNO", "Logical Record Number", 19, 7, "N"],
            ["POP100", "Population Count (100%)", 30, 9, "N"],
        ]
    
        # define the fieldtype conversion functions
        fieldtype_fns = {
            'A': str.rstrip,
            'A/N': str.rstrip,
            'N': int,
        }
    
        # iterate over record objects in the file
        with open(f, 'rb'):
            for record in fixedwidth.reader(f, fieldspecs, fieldtype_fns):
                pprint.pprint(record.__dict__)
    
        # output:
        {'FILEID': 'SF1ST', 'LOGRECNO': 2, 'POP100': 1, 'STUSAB': 'TX', 'SUMLEV': '040'}
        {'FILEID': 'SF1ST', 'LOGRECNO': 3, 'POP100': 2, 'STUSAB': 'TX', 'SUMLEV': '040'}    
        ...
    
    """
    
    import struct, io
    
    
    # fieldspec columns
    iName, iDescription, iStart, iWidth, iType = range(5)
    
    
    def get_struct_unpacker(fieldspecs):
        """
        Build the format string for struct.unpack to use, based on the fieldspecs.
        fieldspecs is a list of [name, description, start, width, type] arrays.
        Returns a string like "6s2s3s7x7s4x9s".
        """
        unpack_len = 0
        unpack_fmt = ""
        for fieldspec in fieldspecs:
            start = fieldspec[iStart] - 1
            end = start + fieldspec[iWidth]
            if start > unpack_len:
                unpack_fmt += str(start - unpack_len) + "x"
            unpack_fmt += str(end - start) + "s"
            unpack_len = end
        struct_unpacker = struct.Struct(unpack_fmt).unpack_from
        return struct_unpacker
    
    
    class Record(object):
        pass
        # or use named tuples
    
    
    def reader(f, fieldspecs, fieldtype_fns):
        """
        Wrap a fixedwidth file and return records according to the given fieldspecs.
        fieldspecs is a list of [name, description, start, width, type] arrays.
        fieldtype_fns is a dictionary of functions used to transform the raw string values, 
        one for each type.
        """
    
        # make sure fieldspecs are sorted properly
        fieldspecs.sort(key=lambda fieldspec: fieldspec[iStart])
    
        struct_unpacker = get_struct_unpacker(fieldspecs)
    
        field_indices = range(len(fieldspecs))
    
        for line in f:
            raw_fields = struct_unpacker(line) # split line into field values
            record = Record()
            for i in field_indices:
                fieldspec = fieldspecs[i]
                fieldname = fieldspec[iName]
                s = raw_fields[i].decode() # convert raw bytes to a string
                fn = fieldtype_fns[fieldspec[iType]] # get conversion function
                value = fn(s) # convert string to value (eg to an int)
                setattr(record, fieldname, value)
            yield record
    
    
    if __name__=='__main__':
    
        # test module
    
        import pprint, io
    
        # define the fields we want
        # fieldspecs are [name, description, start, width, type]
        fieldspecs = [
            ["FILEID", "File Identification", 1, 6, "A/N"],
            ["STUSAB", "State/U.S. Abbreviation (USPS)", 7, 2, "A"],
            ["SUMLEV", "Summary Level", 9, 3, "A/N"],
            ["LOGRECNO", "Logical Record Number", 19, 7, "N"],
            ["POP100", "Population Count (100%)", 30, 9, "N"],
        ]
    
        # define a conversion function for integers
        def to_int(s):
            """
            Convert a numeric string to an integer.
            Allows a leading ! as an indicator of missing or uncertain data.
            Returns None if no data.
            """
            try:
                return int(s)
            except:
                try:
                    return int(s[1:]) # ignore a leading !
                except:
                    return None # assume has a leading ! and no value
    
        # define the conversion fns
        fieldtype_fns = {
            'A': str.rstrip,
            'A/N': str.rstrip,
            'N': to_int,
            # 'N': int,
            # 'D': lambda s: datetime.datetime.strptime(s, "%d%m%Y"), # ddmmyyyy
            # etc
        }
    
        # define a fixedwidth sample
        sample = """\
    SF1ST TX04089000  00000023748        1 
    SF1ST TX04090000  00000033748!       2
    SF1ST TX04091000  00000043748!        
    """
        sample_data = sample.encode() # convert string to bytes
        file_like = io.BytesIO(sample_data) # create a file-like wrapper around bytes
    
        # iterate over record objects in the file
        for record in reader(file_like, fieldspecs, fieldtype_fns):
            # print(record)
            pprint.pprint(record.__dict__)
    
    26.09.2017

    8

    Нарезка строк не должна быть уродливой, если вы держите ее организованной. Подумайте о том, чтобы сохранить ширину полей в словаре, а затем использовать связанные имена для создания объекта:

    from collections import OrderedDict
    
    class Entry:
        def __init__(self, line):
    
            name2width = OrderedDict()
            name2width['foo'] = 2
            name2width['bar'] = 3
            name2width['baz'] = 2
    
            pos = 0
            for name, width in name2width.items():
    
                val = line[pos : pos + width]
                if len(val) != width:
                    raise ValueError("not enough characters: \'{}\'".format(line))
    
                setattr(self, name, val)
                pos += width
    
    file = "ab789yz\ncd987wx\nef555uv"
    
    entry = []
    
    for line in file.split('\n'):
        entry.append(Entry(line))
    
    print(entry[1].bar) # output: 987
    
    30.07.2018

    9

    Поскольку моя старая работа часто обрабатывает 1 миллион строк данных фиксированной ширины, я исследовал эту проблему, когда начал использовать Python.

    Есть 2 типа FixedWidth

    1. ASCII FixedWidth (длина символа ascii = 1, длина символа в двухбайтовой кодировке = 2)
    2. Unicode FixedWidth (длина символа ascii и двухбайтового кодированного символа = 1)

    Если вся строка ресурса состоит из символов ascii, то ASCII FixedWidth = Unicode FixedWidth

    К счастью, строка и байт в py3 различаются, что избавляет от путаницы при работе с двухбайтовыми закодированными символами (eggbk, big5, euc-jp, shift-jis и т. д.).
    Для обработки " ASCII FixedWidth», строка обычно преобразуется в байты, а затем разбивается.

    Без импорта сторонних модулей
    totalLineCount = 1 миллион, lineLength = 800 byte , FixedWidthArgs=(10,25,4,....), я разделил Line примерно на 5 способов и получил следующий вывод:

    1. структура самая быстрая (1x)
    2. Только цикл, без предварительной обработки. FixedWidthArgs является самым медленным (5x+)
    3. slice(bytes) быстрее, чем slice(string)
    4. Исходная строка представляет собой результат проверки байтов: struct(1x), operator.itemgetter(1.7x), предварительно скомпилированный sliceObject и список включений (2.8x), объект re.patten (2.9x)

    При работе с большими файлами мы часто используем with open ( file, "rb") as f:.
    Метод проходит один из вышеуказанных файлов примерно за 2,4 секунды.
    Я думаю, что соответствующий обработчик, который обрабатывает 1 миллион строк данных, разбивает каждую строку на 20 полей. и занимает менее 2,4 секунды.

    Я считаю, что только stuct и itemgetter соответствуют требованиям

    ps: для нормального отображения я преобразовал unicode str в байты. Если вы находитесь в двухбайтовой среде, вам не нужно этого делать.

    from itertools import accumulate
    from operator import itemgetter
    
    def oprt_parser(sArgs):
        sum_arg = tuple(accumulate(abs(i) for i in sArgs))
        # Negative parameter field index
        cuts = tuple(i for i,num in enumerate(sArgs) if num < 0)
        # Get slice args and Ignore fields of negative length
        ig_Args = tuple(item for i, item in enumerate(zip((0,)+sum_arg,sum_arg)) if i not in cuts)
        # Generate `operator.itemgetter` object
        oprtObj =itemgetter(*[slice(s,e) for s,e in ig_Args])
        return oprtObj
    
    lineb = b'abcdefghijklmnopqrstuvwxyz\xb0\xa1\xb2\xbb\xb4\xd3\xb5\xc4\xb6\xee\xb7\xa2\xb8\xf6\xba\xcd0123456789'
    line = lineb.decode("GBK")
    
    # Unicode Fixed Width
    fieldwidthsU = (13, -13, 4, -4, 5,-5) # Negative width fields is ignored
    # ASCII Fixed Width
    fieldwidths = (13, -13, 8, -8, 5,-5) # Negative width fields is ignored
    # Unicode FixedWidth processing
    parse = oprt_parser(fieldwidthsU)
    fields = parse(line)
    print('Unicode FixedWidth','fields: {}'.format(tuple(map(lambda s: s.encode("GBK"), fields))))
    # ASCII FixedWidth processing
    parse = oprt_parser(fieldwidths)
    fields = parse(lineb)
    print('ASCII FixedWidth','fields: {}'.format(fields))
    line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
    fieldwidths = (2, -10, 24)
    parse = oprt_parser(fieldwidths)
    fields = parse(line)
    print(f"fields: {fields}")
    

    Вывод:

    Unicode FixedWidth fields: (b'abcdefghijklm', b'\xb0\xa1\xb2\xbb\xb4\xd3\xb5\xc4', b'01234')
    ASCII FixedWidth fields: (b'abcdefghijklm', b'\xb0\xa1\xb2\xbb\xb4\xd3\xb5\xc4', b'01234')
    fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')
    

    oprt_parser равно 4x make_parser(список включений + срез)


    В ходе исследования было обнаружено, что чем выше скорость процессора, тем быстрее увеличивается эффективность метода re.
    Поскольку у меня нет большего количества и лучших компьютеров для тестирования, предоставьте мой тестовый код, если кто-то интересно, вы можете проверить это на более быстром компьютере.

    Выполнить среду:

    • ОС: Win10
    • питон: 3.7.2
    • Процессор: амд атлон х3 450
    • HD: сигейт 1T
    import timeit
    import time
    import re
    from itertools import accumulate
    from operator import itemgetter
    
    def eff2(stmt,onlyNum= False,showResult=False):
        '''test function'''
        if onlyNum:
            rl = timeit.repeat(stmt=stmt,repeat=roundI,number=timesI,globals=globals())
            avg = sum(rl) / len(rl)
            return f"{avg * (10 ** 6)/timesI:0.4f}"
        else:
            rl = timeit.repeat(stmt=stmt,repeat=10,number=1000,globals=globals())
            avg = sum(rl) / len(rl)
            print(f"【{stmt}】")
            print(f"\tquick avg = {avg * (10 ** 6)/1000:0.4f} s/million")
            if showResult:
                print(f"\t  Result = {eval(stmt)}\n\t  timelist = {rl}\n")
            else:
                print("")
    
    def upDouble(argList,argRate):
        return [c*argRate for c in argList]
    
    tbStr = "000000001111000002222真2233333333000000004444444QAZ55555555000000006666666ABC这些事中文字abcdefghijk"
    tbBytes = tbStr.encode("GBK")
    a20 = (4,4,2,2,2,3,2,2, 2 ,2,8,8,7,3,8,8,7,3, 12 ,11)
    a20U = (4,4,2,2,2,3,2,2, 1 ,2,8,8,7,3,8,8,7,3, 6 ,11)
    Slng = 800
    rateS = Slng // 100
    
    tStr = "".join(upDouble(tbStr , rateS))
    tBytes = tStr.encode("GBK")
    spltArgs = upDouble( a20 , rateS)
    spltArgsU = upDouble( a20U , rateS)
    
    testList = []
    timesI = 100000
    roundI = 5
    print(f"test round = {roundI} timesI = {timesI} sourceLng = {len(tStr)} argFieldCount = {len(spltArgs)}")
    
    
    print(f"pure str \n{''.ljust(60,'-')}")
    # ==========================================
    def str_parser(sArgs):
        def prsr(oStr):
            r = []
            r_ap = r.append
            stt=0
            for lng in sArgs:
                end = stt + lng 
                r_ap(oStr[stt:end])
                stt = end 
            return tuple(r)
        return prsr
    
    Str_P = str_parser(spltArgsU)
    # eff2("Str_P(tStr)")
    testList.append("Str_P(tStr)")
    
    print(f"pure bytes \n{''.ljust(60,'-')}")
    # ==========================================
    def byte_parser(sArgs):
        def prsr(oBytes):
            r, stt = [], 0
            r_ap = r.append
            for lng in sArgs:
                end = stt + lng
                r_ap(oBytes[stt:end])
                stt = end
            return r
        return prsr
    Byte_P = byte_parser(spltArgs)
    # eff2("Byte_P(tBytes)")
    testList.append("Byte_P(tBytes)")
    
    # re,bytes
    print(f"re compile object \n{''.ljust(60,'-')}")
    # ==========================================
    
    
    def rebc_parser(sArgs,otype="b"):
        re_Args = "".join([f"(.{{{n}}})" for n in sArgs])
        if otype == "b":
            rebc_Args = re.compile(re_Args.encode("GBK"))
        else:
            rebc_Args = re.compile(re_Args)
        def prsr(oBS):
            return rebc_Args.match(oBS).groups()
        return prsr
    Rebc_P = rebc_parser(spltArgs)
    # eff2("Rebc_P(tBytes)")
    testList.append("Rebc_P(tBytes)")
    
    Rebc_Ps = rebc_parser(spltArgsU,"s")
    # eff2("Rebc_Ps(tStr)")
    testList.append("Rebc_Ps(tStr)")
    
    
    print(f"struct \n{''.ljust(60,'-')}")
    # ==========================================
    
    import struct
    def struct_parser(sArgs):
        struct_Args = " ".join(map(lambda x: str(x) + "s", sArgs))
        def prsr(oBytes):
            return struct.unpack(struct_Args, oBytes)
        return prsr
    Struct_P = struct_parser(spltArgs)
    # eff2("Struct_P(tBytes)")
    testList.append("Struct_P(tBytes)")
    
    print(f"List Comprehensions + slice \n{''.ljust(60,'-')}")
    # ==========================================
    import itertools
    def slice_parser(sArgs):
        tl = tuple(itertools.accumulate(sArgs))
        slice_Args = tuple(zip((0,)+tl,tl))
        def prsr(oBytes):
            return [oBytes[s:e] for s, e in slice_Args]
        return prsr
    Slice_P = slice_parser(spltArgs)
    # eff2("Slice_P(tBytes)")
    testList.append("Slice_P(tBytes)")
    
    def sliceObj_parser(sArgs):
        tl = tuple(itertools.accumulate(sArgs))
        tl2 = tuple(zip((0,)+tl,tl))
        sliceObj_Args = tuple(slice(s,e) for s,e in tl2)
        def prsr(oBytes):
            return [oBytes[so] for so in sliceObj_Args]
        return prsr
    SliceObj_P = sliceObj_parser(spltArgs)
    # eff2("SliceObj_P(tBytes)")
    testList.append("SliceObj_P(tBytes)")
    
    SliceObj_Ps = sliceObj_parser(spltArgsU)
    # eff2("SliceObj_Ps(tStr)")
    testList.append("SliceObj_Ps(tStr)")
    
    
    print(f"operator.itemgetter + slice object \n{''.ljust(60,'-')}")
    # ==========================================
    
    def oprt_parser(sArgs):
        sum_arg = tuple(accumulate(abs(i) for i in sArgs))
        cuts = tuple(i for i,num in enumerate(sArgs) if num < 0)
        ig_Args = tuple(item for i,item in enumerate(zip((0,)+sum_arg,sum_arg)) if i not in cuts)
        oprtObj =itemgetter(*[slice(s,e) for s,e in ig_Args])
        return oprtObj
    
    Oprt_P = oprt_parser(spltArgs)
    # eff2("Oprt_P(tBytes)")
    testList.append("Oprt_P(tBytes)")
    
    Oprt_Ps = oprt_parser(spltArgsU)
    # eff2("Oprt_Ps(tStr)")
    testList.append("Oprt_Ps(tStr)")
    
    print("|".join([s.split("(")[0].center(11," ") for s in testList]))
    print("|".join(["".center(11,"-") for s in testList]))
    print("|".join([eff2(s,True).rjust(11," ") for s in testList]))
    
    

    Вывод:

    Test round = 5 timesI = 100000 sourceLng = 744 argFieldCount = 20
    ...
    ...
       Str_P | Byte_P | Rebc_P | Rebc_Ps | Struct_P | Slice_P | SliceObj_P|SliceObj_Ps| Oprt_P | Oprt_Ps
    -----------|-----------|-----------|-----------|-- ---------|-----------|-----------|-----------|---- -------|-----------
         9.6315| 7.5952| 4.4187| 5.6867| 1.5123| 5.2915| 4.2673| 5.7121| 2.4713| 3.9051
    
    18.02.2019
  • @MartijnPieters Более эффективная функция 20.02.2019

  • 10

    Мне нравится обрабатывать текстовые файлы, содержащие поля фиксированной ширины, с помощью регулярных выражений. В частности, с помощью именованных групп захвата. Он быстрый, не требует импорта больших библиотек и достаточно нагляден и удобен (на мой взгляд).

    Мне также нравится тот факт, что именованные группы захвата в основном автоматически документируют формат данных, действуя как своего рода спецификация данных, поскольку каждая группа захвата может быть записана для определения имени каждого поля, типа данных и длина.

    Вот простой пример...

    import re
    
    data = [
        "1234ABCDEFGHIJ5", 
        "6789KLMNOPQRST0"
    ]
    
    record_regex = (
        r"^"
        r"(?P<firstnumbers>[0-9]{4})"
        r"(?P<middletext>[a-zA-Z0-9_\-\s]{10})"
        r"(?P<lastnumber>[0-9]{1})"
        r"$"
    )
    
    records = []
    
    for line in data:
        match = re.match(record_regex, line)
        if match:
            records.append(match.groupdict())
    
    print(records)
    

    ... что дает удобный словарь каждой записи:

    [
        {'firstnumbers': '1234', 'lastnumber': '5', 'middletext': 'ABCDEFGHIJ'},
        {'firstnumbers': '6789', 'lastnumber': '0', 'middletext': 'KLMNOPQRST'}
    ]
    

    Полезные инструменты, такие как онлайн-тестер и отладчик регулярных выражений, доступны, если вы не знакомы (или не знакомы) с обычным Python. выражения или именованные группы захвата.

    30.11.2020
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..