Làm thế nào để phân tích hiệu quả các tệp có chiều rộng cố định?


84

Tôi đang cố gắng tìm một cách hiệu quả để phân tích cú pháp tệp chứa các đường có độ rộng cố định. Ví dụ: 20 ký tự đầu tiên đại diện cho một cột, từ 21:30 là một cột khác, v.v.

Giả sử rằng dòng chứa 100 ký tự, thì đâu sẽ là cách hiệu quả để phân tích cú pháp một dòng thành nhiều thành phần?

Tôi có thể sử dụng cắt chuỗi trên mỗi dòng, nhưng sẽ hơi xấu nếu dòng lớn. Có phương pháp nhanh nào khác không?

Câu trả lời:


73

Sử dụng structmô-đun của thư viện chuẩn Python sẽ khá dễ dàng cũng như cực kỳ nhanh chóng vì nó được viết bằng C.

Đây là cách nó có thể được sử dụng để làm những gì bạn muốn. Nó cũng cho phép bỏ qua các cột ký tự bằng cách chỉ định giá trị âm cho số ký tự trong trường.

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))

Đầu ra:

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

Các sửa đổi sau sẽ điều chỉnh nó hoạt động trong Python 2 hoặc 3 (và xử lý đầu vào 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()))

Đây là một cách để làm điều đó với các lát chuỗi, như bạn đã xem xét nhưng lo ngại rằng nó có thể trở nên quá xấu xí. Điều tốt đẹp về nó là, bên cạnh việc không xấu xa như vậy, là nó hoạt động không thay đổi trong cả Python 2 và 3, cũng như có thể xử lý các chuỗi Unicode. Tất nhiên, tốc độ khôn ngoan là chậm hơn so với các phiên bản dựa trên structmô-đun, nhưng có thể tăng tốc một chút bằng cách loại bỏ khả năng có các trường đệm.

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))

Đầu ra:

format: '2s 10x 24s', rec size: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')

+1 thật tuyệt. Theo một cách nào đó, tôi nghĩ điều này tương tự với cách tiếp cận của tôi (ít nhất là khi bạn đang nhận được kết quả), nhưng rõ ràng là nhanh hơn.
Reiner Gerecke

1
Điều đó sẽ hoạt động như thế nào với unicode? Hoặc, một chuỗi được mã hóa utf-8? struct.unpackdường như hoạt động trên dữ liệu nhị phân. Tôi không thể làm cho nó hoạt động.
Reiner Gerecke

3
@Reiner Gerecke: Mô-đun struct được thiết kế để hoạt động trên dữ liệu nhị phân. Các tệp có trường độ rộng cố định là các công việc kế thừa cũng có nhiều khả năng được cập nhật trước UTF-8 (trong tâm trí, nếu không theo trình tự thời gian). Các byte được đọc từ các tệp là dữ liệu nhị phân. Bạn không có unicode trong tệp. Bạn cần giải mã byte để lấy unicode.
John Machin

1
@Reiner Gerecke: Làm rõ: Trong các định dạng tệp kế thừa đó, mỗi trường là một số byte cố định, không phải là một số ký tự cố định. Mặc dù không có khả năng được mã hóa bằng UTF-8, chúng có thể được mã hóa trong một mã hóa có số byte thay đổi trên mỗi ký tự, ví dụ như gbk, big5, euc-jp, shift-jis, v.v. Nếu bạn muốn làm việc trong unicode, bạn không thể giải mã toàn bộ bản ghi cùng một lúc; bạn cần giải mã từng trường.
John Machin

1
Điều này hoàn toàn bị hỏng khi bạn cố gắng áp dụng điều này cho các giá trị Unicode (như trong Python 3) với văn bản bên ngoài bộ ký tự ASCII và trong đó 'chiều rộng cố định' có nghĩa là 'số ký tự cố định ', không phải byte.
Martijn Pieters

69

Tôi không thực sự chắc chắn liệu điều này có hiệu quả hay không, nhưng nó sẽ có thể đọc được (trái ngược với việc cắt theo cách thủ công). Tôi đã định nghĩa một hàm slicesnhận độ dài chuỗi và cột, và trả về các chuỗi con. Tôi đã làm cho nó một máy phát điện, vì vậy đối với những dòng thực sự dài, nó không xây dựng một danh sách tạm thời các chuỗi con.

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

Thí dụ

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')

Nhưng tôi nghĩ lợi thế của một máy phát điện sẽ bị mất đi nếu bạn cần tất cả các cột cùng một lúc. Nơi mà người ta có thể hưởng lợi là khi bạn muốn xử lý từng cột một, hãy nói trong một vòng lặp.


2
AFAICT, phương pháp này chậm hơn struct, nhưng nó có thể đọc được và dễ xử lý hơn. Tôi đã thực hiện một số bài kiểm tra bằng cách sử dụng mô-đun slices function, structmô-đun và cả remô-đun của bạn và nó hóa ra đối với các tệp lớn, structlà tệp nhanh nhất, ređứng thứ hai (chậm hơn 1,5 lần) và slicesthứ ba (chậm hơn 2 lần). Tuy nhiên, có một chi phí nhỏ khi sử dụng structđể bạn slices functioncó thể nhanh hơn trên các tệp nhỏ hơn.
YeO

27

Hai tùy chọn khác dễ dàng hơn và đẹp hơn các giải pháp đã đề cập:

Đầu tiên là sử dụng gấu trúc:

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)

Và tùy chọn thứ hai sử dụng numpy.loadtxt:

import numpy as np

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

Nó thực sự phụ thuộc vào cách bạn muốn sử dụng dữ liệu của mình.


Điều này có cạnh tranh với câu trả lời được chấp nhận về tốc độ không?
asachet

1
Chưa thử nghiệm nó, nhưng nó sẽ nhanh hơn rất nhiều so với câu trả lời được chấp nhận.
Tom M

1
Gấu trúc có thể làm phát hiện automagic ngày của riêng mình nếu bạn thiết lập colspecs='infer' pandas.pydata.org/pandas-docs/stable/generated/...
James Paul Mason

14

Đoạn mã dưới đây cung cấp một bản phác thảo về những gì bạn có thể muốn làm nếu bạn có một số xử lý tệp có chiều rộng cột cố định nghiêm trọng phải làm.

"Serious" = nhiều loại bản ghi trong mỗi loại tệp, bản ghi lên đến 1000 byte, người xác định bố cục và người tiêu dùng / nhà sản xuất "đối lập" là một cơ quan chính phủ có thái độ, thay đổi bố cục dẫn đến các cột không sử dụng, lên đến một triệu bản ghi trong một tệp, ...

Tính năng: Biên dịch trước các định dạng cấu trúc. Bỏ qua các cột không mong muốn. Chuyển đổi chuỗi đầu vào thành kiểu dữ liệu bắt buộc (phác thảo bỏ qua xử lý lỗi). Chuyển đổi các bản ghi thành các thể hiện đối tượng (hoặc phân đoạn, hoặc các bộ giá trị được đặt tên nếu bạn thích).

Mã:

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

Đầu ra:

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

Làm cách nào để cập nhật mã này để phân tích cú pháp các bản ghi lớn hơn 1000 byte? Tôi đang gặp lỗi này:struct.error: unpack_from requires a buffer of at least 1157 bytes
chris__allen

4
> str = '1234567890'
> w = [0,2,5,7,10]
> [ str[ w[i-1] : w[i] ] for i in range(1,len(w)) ]
['12', '345', '67', '890']

1

Đây là một mô-đun đơn giản cho Python 3, dựa trên câu trả lời của John Machin - hãy điều chỉnh khi cần :)

"""
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__)

1

Đây là cách tôi đã giải quyết với một từ điển có chứa các trường bắt đầu và kết thúc. Việc cung cấp điểm bắt đầu và điểm kết thúc cũng giúp tôi quản lý các thay đổi ở độ dài của cột.

# 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'}

1

Đây là những gì NumPy sử dụng ẩn (được đơn giản hóa nhiều, nhưng vẫn còn - mã này được tìm thấy trong LineSplitter classbên trong _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]

Nó không xử lý dấu phân cách âm để bỏ qua cột vì vậy nó không linh hoạt bằng struct, nhưng nó nhanh hơn.


0

Việc cắt chuỗi không cần phải xấu đi miễn là bạn giữ nó có tổ chức. Xem xét lưu trữ độ rộng trường của bạn trong từ điển và sau đó sử dụng các tên được liên kết để tạo đối tượng:

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

0

Vì công việc cũ của tôi thường xử lý 1 triệu dòng dữ liệu cố định, nên tôi đã nghiên cứu về vấn đề này khi bắt đầu sử dụng Python.

Có 2 loại Chiều rộng cố định

  1. Chiều rộng cố định ASCII (chiều dài ký tự ascii = 1, chiều dài ký tự mã hóa byte kép = 2)
  2. Chiều rộng cố định Unicode (ký tự ascii & độ dài ký tự mã hóa byte kép = 1)

Nếu chuỗi tài nguyên bao gồm tất cả các ký tự ascii, thì ASCII FixedWidth = Unicode FixedWidth

May mắn thay, chuỗi và byte khác nhau trong py3, điều này làm giảm nhiều sự nhầm lẫn khi xử lý các ký tự được mã hóa byte kép (eggbk, big5, euc-jp, shift-jis, v.v.).
Đối với quá trình xử lý "ASCII FixedWidth", Chuỗi thường được chuyển đổi thành Byte và sau đó chia nhỏ.

Không nhập mô-đun của bên thứ ba
totalLineCount = 1 triệu, lineLength = 800 byte, FixedWidthArgs = (10,25,4, ....), tôi chia Dòng theo khoảng 5 cách và nhận được kết luận sau:

  1. struct là nhanh nhất (1x)
  2. Chỉ lặp lại, không xử lý trước FixedWidthArgs là chậm nhất (5x +)
  3. slice(bytes) nhanh hơn slice(string)
  4. Chuỗi nguồn là kết quả kiểm tra byte: struct (1x), operator.itemgetter (1,7x), sliceObject & list hiểu một cách biên dịch trước (2,8x), đối tượng re.patten (2,9x)

Khi xử lý các tệp lớn, chúng tôi thường sử dụng with open ( file, "rb") as f:.
Phương thức truyền qua một trong các tệp trên, khoảng 2,4 giây.
Tôi nghĩ rằng trình xử lý thích hợp, xử lý 1 triệu hàng dữ liệu, chia mỗi hàng thành 20 trường và mất ít hơn 2,4 giây.

Tôi chỉ thấy vậy stuctitemgetterđáp ứng yêu cầu

ps: Để hiển thị bình thường, tôi đã chuyển đổi unicode str thành byte. Nếu bạn đang ở trong môi trường byte kép, bạn không cần phải làm điều này.

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}")

Đầu ra:

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_parserlà 4x make_parser(toàn bộ danh sách + lát cắt)


Trong quá trình nghiên cứu, người ta nhận thấy rằng khi tốc độ cpu càng nhanh thì có vẻ như hiệu quả của rephương pháp này tăng nhanh hơn.
Vì tôi không có nhiều máy tính hơn và tốt hơn để kiểm tra, hãy cung cấp mã kiểm tra của tôi, nếu ai quan tâm có thể kiểm tra bằng máy tính nhanh hơn.

Môi trường chạy:

  • hệ điều hành: win10
  • trăn: 3.7.2
  • CPU: amd Athlon x3 450
  • HD: seagate 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]))

Đầu ra:

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

@MartijnPieters Chức năng hiệu quả hơn
notback
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.