কীভাবে স্থির প্রস্থের ফাইলগুলি দক্ষতার সাথে পার্স করবেন?


84

আমি স্থির প্রস্থের রেখা ধারণ করে এমন ফাইলগুলি পার্স করার একটি কার্যকর উপায় সন্ধান করার চেষ্টা করছি। উদাহরণস্বরূপ, প্রথম 20 টি অক্ষর একটি কলামকে প্রতিনিধিত্ব করে, 21:30 থেকে অন্য একটি এবং এর মতো।

অনুমান করে যে লাইনটি 100 টি অক্ষর ধারণ করেছে, একটি লাইনকে বিভিন্ন উপাদানকে পার্স করার কার্যকর উপায় কী হবে?

আমি প্রতি লাইনে স্ট্রিং স্লাইসিং ব্যবহার করতে পারি, তবে লাইনটি বড় হলে এটি কিছুটা কুৎসিত। অন্য কোন দ্রুত পদ্ধতি আছে?

উত্তর:


73

পাইথন স্ট্যান্ডার্ড লাইব্রেরির structমডিউলটি ব্যবহার করা মোটামুটি সহজ পাশাপাশি অত্যন্ত দ্রুত হবে কারণ এটি সিতে লেখা হয়েছে written

আপনি যা চান তা এটি কীভাবে ব্যবহৃত হতে পারে তা এখানে। ক্ষেত্রের অক্ষরের সংখ্যার জন্য নেতিবাচক মান উল্লেখ করে অক্ষরগুলির কলামগুলি এড়িয়ে যাওয়ার অনুমতি দেয়।

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

নিম্নলিখিত পরিবর্তনগুলি পাইথন 2 বা 3 এ এটি কার্যকর করবে (এবং ইউনিকোড ইনপুট পরিচালনা করবে):

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

স্ট্রিং স্লাইসগুলি সহ এটি করার একটি উপায় এখানে আপনি যেমন বিবেচনা করেছিলেন কিন্তু উদ্বিগ্ন ছিলেন যে এটি খুব কদর্য হতে পারে। এটি সম্পর্কে দুর্দান্ত জিনিসটি হ'ল সমস্ত কুরুচিপূর্ণ না হওয়া ছাড়াও এটি পাইথন 2 এবং 3 উভয় ক্ষেত্রে অপরিবর্তিত কাজ করে, পাশাপাশি ইউনিকোড স্ট্রিংগুলি পরিচালনা করতে সক্ষম হয়। গতি অনুসারে এটি 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')

+1 এটি দুর্দান্ত। এক উপায়ে, আমি মনে করি এটি আমার পদ্ধতির সাথে সাদৃশ্যপূর্ণ (কমপক্ষে আপনি যখন ফলাফল পাচ্ছেন) তবে স্পষ্টতই দ্রুতগতিতে চলে যাবেন।
রিনার গেরেক

4
ইউনিকোড দিয়ে কীভাবে কাজ করবে? বা, একটি utf-8 এনকোড স্ট্রিং? struct.unpackবাইনারি তথ্য ব্যবহার করে বলে মনে হচ্ছে। আমি এই কাজ পেতে পারি না।
রিনার গেরেক

4
@ রেইনার গেরেক: স্ট্রাক্ট মডিউলটি বাইনারি ডেটা পরিচালনা করতে ডিজাইন করা হয়েছে । স্থির-প্রস্থ ক্ষেত্রযুক্ত ফাইলগুলি হ'ল উত্তরাধিকার সংক্রান্ত চাকরি যা প্রি-ডেট ইউটিএফ -8 হওয়ার সম্ভাবনা খুব বেশি (মনে রাখবেন, কালানুক্রমিকভাবে না থাকলে)। ফাইলগুলি থেকে পড়া বাইটগুলি হ'ল বাইনারি ডেটা। আপনার ফাইলগুলিতে ইউনিকোড নেই। ইউনিকোড পেতে আপনাকে বাইটগুলি ডিকোড করতে হবে।
জন মাচিন 21

4
@ রাইনার গেরেক: স্পষ্টকরণ: এই উত্তরাধিকারী ফাইল ফর্ম্যাটগুলিতে প্রতিটি ক্ষেত্র একটি নির্দিষ্ট সংখ্যক বাইট , অক্ষরের নির্দিষ্ট সংখ্যা নয়। ইউটিএফ -8 এ এনকোড হওয়ার সম্ভাবনা কম হলেও, এগুলি একটি এনকোডিং এ এনকোড করা যেতে পারে যা চরিত্র অনুসারে বিবিধ সংখ্যক বাইট রয়েছে যেমন জিবিকে, বিগ 5, ইউক-জেপি, শিফট-জিস ইত্যাদি। আপনি যদি ইউনিকোডে কাজ করতে চান তবে আপনি একবারে পুরো রেকর্ডটি ডিকোড করতে পারে না; আপনার প্রতিটি ক্ষেত্রের ডিকোড করা দরকার।
জন মাচিন

4
ASCII অক্ষর সেটের বাইরের পাঠ্য এবং যেখানে 'নির্দিষ্ট প্রস্থ' এর অর্থ ' অক্ষরের নির্দিষ্ট সংখ্যা ', বাইট নয়, আপনি যখন ইউনিকোড মানগুলির জন্য (পাইথন 3-তে) এটি প্রয়োগ করার চেষ্টা করবেন তখন এটি পুরোপুরি বিচ্ছিন্ন হয়ে যায় ।
মার্টিজন পিটারস

69

এটি কার্যকর কিনা আমি সত্যিই নিশ্চিত নই তবে এটি পাঠযোগ্য হওয়া উচিত (ম্যানুয়ালি কাটা কাটানোর বিপরীতে)। আমি একটি ফাংশন সংজ্ঞায়িত করেছি 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')

তবে আমি মনে করি আপনার যদি একবারে সমস্ত কলামের প্রয়োজন হয় তবে কোনও জেনারেটরের সুবিধা হারাবে। আপনি যখন কলামগুলি একে একে প্রক্রিয়া করতে চান তখন কোনওটি উপকার করতে পারে, একটি লুপে বলুন।


4
আফিক্স, এই পদ্ধতিটি তুলনায় ধীর struct, তবে এটি পাঠযোগ্য এবং হ্যান্ডেল করা সহজ। আমি আপনার slices function, structমডিউল এবং এছাড়াও reমডিউলটি ব্যবহার করে কিছু পরীক্ষা করেছি এবং এটি বড় ফাইলগুলির সন্ধান করে, structএটি দ্রুততম, reদ্বিতীয় (1.5x ধীর) এবং slicesতৃতীয় (2x ধীর) হয়। সেখানে একটি ছোট ওভারহেড ব্যবহার করা হচ্ছে structযাতে আপনার slices functionছোট ফাইলগুলিতে আরও দ্রুত হতে পারে।
ইয়েও

27

ইতিমধ্যে উল্লিখিত সমাধানগুলির চেয়ে আরও দুটি বিকল্প যা সহজ এবং প্রাকৃতিক:

প্রথমটি পান্ডাস ব্যবহার করছে:

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)

আপনি কীভাবে আপনার ডেটা ব্যবহার করতে চান তা নির্ভর করে।


এটি কি গতির দিক দিয়ে গৃহীত উত্তরের সাথে প্রতিযোগিতামূলক?
asachet

4
এটি পরীক্ষা করা হয়নি, তবে এটি গৃহীত উত্তরের চেয়ে অনেক দ্রুত হওয়া উচিত।
টম এম


14

নীচের কোডটি আপনার কিছু মারাত্মক ফিক্সড-কলাম-প্রস্থের ফাইল হ্যান্ডলিং করতে থাকলে আপনি কী করতে চান তার একটি স্কেচ দেয়।

"সিরিয়াস" = একাধিক ফাইল প্রকারের একাধিক রেকর্ড টাইপ, 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

1000 বাইটের বেশি রেকর্ড বিশ্লেষণ করতে এই কোডটি কীভাবে আপডেট করা হবে? আমি এই ত্রুটির মধ্যে দৌড়াচ্ছি:struct.error: unpack_from requires a buffer of at least 1157 bytes
chris__allen


1

জন ম্যাচিনের উত্তরের উপর ভিত্তি করে পাইথন 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__)

1

এইভাবে আমি এমন একটি অভিধান দিয়ে সমাধান করেছি যেখানে ক্ষেত্রগুলি শুরু এবং শেষ থাকে। শুরু এবং শেষ পয়েন্ট দেওয়া আমাকে কলামটির দৈর্ঘ্যে পরিবর্তনগুলি পরিচালনা করতে সহায়তা করেছিল।

# 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

এখানে কি 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, তবে এটি আরও দ্রুত।


0

স্ট্রিং স্লাইসিং যতক্ষণ আপনি এটিকে সংগঠিত রাখেন ততক্ষণ কুশ্রী হওয়ার দরকার নেই। আপনার ক্ষেত্রের প্রস্থ একটি অভিধানে সংরক্ষণ করুন এবং তারপরে কোনও বিষয় তৈরি করতে যুক্ত নামগুলি ব্যবহার করে বিবেচনা করুন:

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

যেহেতু আমার পুরানো কাজটি প্রায়শই 1 মিলিয়ন লাইন ফিক্সউইথ ডেটা পরিচালনা করে, আমি পাইথন ব্যবহার শুরু করার পরে এই বিষয়ে গবেষণা করেছি।

এখানে 2 ধরণের ফিক্সডউইথ থাকে

  1. এএসসিআইআই ফিক্সডউইথ (আসকি চরিত্রের দৈর্ঘ্য = 1, ডাবল-বাইট এনকোডেড অক্ষরের দৈর্ঘ্য = 2)
  2. ইউনিকোড ফিক্সড ওয়াইড (আসকি চরিত্র এবং ডাবল-বাইট এনকোডেড অক্ষরের দৈর্ঘ্য = 1)

যদি রিসোর্স স্ট্রিংগুলি সমস্ত এএসসিআই অক্ষর সমন্বিত থাকে, তবে ASCII ফিক্সডওয়েথ = ইউনিকোড ফিক্সড উইথ

ভাগ্যক্রমে, স্ট্রিং এবং বাইট পাই 3-তে পৃথক, যা ডাবল-বাইট এনকোডেড অক্ষর (ডিম্বক, বিগ 5, ইউক-জেপি, শিফট-জিস, ইত্যাদি) নিয়ে কাজ করার সময় প্রচুর বিভ্রান্তি হ্রাস করে।
"এএসসিআইআই ফিক্সড উইথ" প্রক্রিয়াকরণের জন্য স্ট্রিংটি সাধারণত বাইটে রূপান্তরিত হয় এবং তারপরে বিভক্ত হয়।

তৃতীয় পক্ষের মডিউলগুলি
মোট লাইনেকাউন্ট = 1 মিলিয়ন, লাইনলিংথ = 800 বাইট, ফিক্সড ওয়েইথআরগস = (10,25,4, ....) আমদানি না করে আমি লাইনটি প্রায় 5 উপায়ে বিভক্ত করে নীচের উপসংহারটি পেয়েছি:

  1. কাঠামো দ্রুততম (1x)
  2. কেবল লুপ, প্রাক-প্রক্রিয়াজাতকরণ নয় স্থিরউইথআরগস সবচেয়ে ধীর (5x +)
  3. slice(bytes) তুলনায় দ্রুত slice(string)
  4. উত্সের স্ট্রিংটি বাইট পরীক্ষার ফলাফল: স্ট্রাক্ট (1x), অপারেটর.ইটমেজেটার (1.7x), প্রাক্পম্পাইল্ড স্লাইসঅবজেক্ট এবং তালিকা অনুধাবন (2.8x), রি.প্যাটেন অবজেক্ট (2.9x)

বড় ফাইলগুলির সাথে কাজ করার সময় আমরা প্রায়শই ব্যবহার করি with open ( file, "rb") as f:
পদ্ধতিটি উপরের ফাইলগুলির মধ্যে প্রায় ২.৪ সেকেন্ডকে অনুসরণ করে।
আমি মনে করি উপযুক্ত হ্যান্ডলার, যা 1 মিলিয়ন সারি ডেটা প্রক্রিয়া করে, প্রতিটি সারি 20 টি ক্ষেত্রগুলিতে বিভক্ত হয় এবং 2.4 সেকেন্ডেরও কম সময় নেয়।

আমি কেবল এটি পাই stuctএবং itemgetterপ্রয়োজনীয়তাগুলি পূরণ করি

PS: সাধারণ প্রদর্শনের জন্য, আমি ইউনিকোড স্ট্রকে বাইটে রূপান্তর করি। আপনি যদি ডাবল-বাইট পরিবেশে থাকেন তবে আপনার এটি করার দরকার নেই।

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_parser4x make_parser(তালিকা বোঝা + স্লাইস)


গবেষণার সময় দেখা গেল, সিপিইউর গতি যখন দ্রুত হয় তখন মনে হয় যে reপদ্ধতিটির দক্ষতা দ্রুত বৃদ্ধি পায়।
যেহেতু আমার কাছে পরীক্ষার জন্য আরও বেশি ভাল কম্পিউটার নেই, তাই আমার পরীক্ষার কোড সরবরাহ করুন, যদি কেউ আগ্রহী হন তবে আপনি এটি দ্রুত কম্পিউটারের মাধ্যমে পরীক্ষা করতে পারেন।

রান পরিবেশ:

  • OS: win10
  • অজগর: 3.7.2
  • সিপিইউ: এমডি অ্যাথলন এক্স 3 450
  • এইচডি: সিগেট 1 টি
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

@MartijnPieters আরও দক্ষ ফাংশন
notback

0

আমি নিয়মিত এক্সপ্রেশন ব্যবহার করে স্থির প্রস্থ ক্ষেত্রগুলি সহ পাঠ্য ফাইলগুলি প্রক্রিয়া করতে চাই । আরও নির্দিষ্টভাবে, নামকৃত ক্যাপচার গ্রুপগুলি ব্যবহার করে । এটি দ্রুত, বড় গ্রন্থাগার আমদানির প্রয়োজন হয় না এবং এটি বেশ বর্ণনামূলক এবং সুবিধাজনক (আমার মতে)।

আমি এই বিষয়টিও পছন্দ করি যে নামকৃত ক্যাপচার গ্রুপগুলি মূলত ডেটা ফর্ম্যাটটিকে স্বতঃ-ডকুমেন্টিং করে, এক ধরণের ডেটা স্পেসিফিকেশন হিসাবে কাজ করে , যেহেতু প্রতিটি ক্যাপচার গ্রুপকে প্রতিটি ক্ষেত্রের নাম, ডেটা টাইপ এবং দৈর্ঘ্য নির্ধারণ করতে লেখা যেতে পারে।

এখানে সহজ উদাহরণ ...

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

অনলাইন রেজেক্স টেস্টার এবং ডিবাগারের মতো সহায়ক সরঞ্জামগুলি পাওয়া যায় যদি আপনি পাইথন নিয়মিত এক্সপ্রেশন বা নামযুক্ত ক্যাপচার গ্রুপগুলির সাথে পরিচিত (বা আরামদায়ক) না হন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.