python সহজ কিভাবে পাইথন সস্তাভাবে লাইন গণনা পেতে?




সহজ ভাষায় পাইথন ৩ pdf (24)

এক লাইন, সম্ভবত বেশ দ্রুত:

num_lines = sum(1 for line in open('myfile.txt'))

পাইথনে একটি বড় ফাইলের (লাইনের হাজার হাজার লাইন) লাইন গণনা দরকার। মেমরি- এবং সময়জ্ঞান উভয় সবচেয়ে কার্যকর উপায় কি?

মুহূর্তে আমি করি:

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

এটা কি কোনো ভালো কাজ করা সম্ভব?


লিনাক্সে পাইথনটিতে লাইন গণনা করতে চাইলে আমি এই পদ্ধতির সুপারিশ করি:

import os
print os.popen("wc -l file_path").readline().split()[0]

file_path উভয় বিমূর্ত ফাইল পাথ বা আপেক্ষিক পাথ হতে পারে। এই সাহায্য করতে পারে আশা করি।


মেশিন / কোর জুড়ে লাইন কাউন্টিং বিতরণ করতে মাল্টিপ্রসেসিং লাইব্রেরি ব্যবহার করার জন্য এখানে একটি পাইথন প্রোগ্রাম রয়েছে। আমার পরীক্ষাটি 8 কোর উইন্ডো 64 সার্ভার ব্যবহার করে ২6 সেকেন্ড থেকে 7 সেকেন্ডের মধ্যে 20 মিলিয়ন লাইন ফাইল গণনা করে। দ্রষ্টব্য: মেমরি ম্যাপিং ব্যবহার করে জিনিসগুলি অনেক ধীর হয়ে যায়।

import multiprocessing, sys, time, os, mmap
import logging, logging.handlers

def init_logger(pid):
    console_format = 'P{0} %(levelname)s %(message)s'.format(pid)
    logger = logging.getLogger()  # New logger at root level
    logger.setLevel( logging.INFO )
    logger.handlers.append( logging.StreamHandler() )
    logger.handlers[0].setFormatter( logging.Formatter( console_format, '%d/%m/%y %H:%M:%S' ) )

def getFileLineCount( queues, pid, processes, file1 ):
    init_logger(pid)
    logging.info( 'start' )

    physical_file = open(file1, "r")
    #  mmap.mmap(fileno, length[, tagname[, access[, offset]]]

    m1 = mmap.mmap( physical_file.fileno(), 0, access=mmap.ACCESS_READ )

    #work out file size to divide up line counting

    fSize = os.stat(file1).st_size
    chunk = (fSize / processes) + 1

    lines = 0

    #get where I start and stop
    _seedStart = chunk * (pid)
    _seekEnd = chunk * (pid+1)
    seekStart = int(_seedStart)
    seekEnd = int(_seekEnd)

    if seekEnd < int(_seekEnd + 1):
        seekEnd += 1

    if _seedStart < int(seekStart + 1):
        seekStart += 1

    if seekEnd > fSize:
        seekEnd = fSize

    #find where to start
    if pid > 0:
        m1.seek( seekStart )
        #read next line
        l1 = m1.readline()  # need to use readline with memory mapped files
        seekStart = m1.tell()

    #tell previous rank my seek start to make their seek end

    if pid > 0:
        queues[pid-1].put( seekStart )
    if pid < processes-1:
        seekEnd = queues[pid].get()

    m1.seek( seekStart )
    l1 = m1.readline()

    while len(l1) > 0:
        lines += 1
        l1 = m1.readline()
        if m1.tell() > seekEnd or len(l1) == 0:
            break

    logging.info( 'done' )
    # add up the results
    if pid == 0:
        for p in range(1,processes):
            lines += queues[0].get()
        queues[0].put(lines) # the total lines counted
    else:
        queues[0].put(lines)

    m1.close()
    physical_file.close()

if __name__ == '__main__':
    init_logger( 'main' )
    if len(sys.argv) > 1:
        file_name = sys.argv[1]
    else:
        logging.fatal( 'parameters required: file-name [processes]' )
        exit()

    t = time.time()
    processes = multiprocessing.cpu_count()
    if len(sys.argv) > 2:
        processes = int(sys.argv[2])
    queues=[] # a queue for each process
    for pid in range(processes):
        queues.append( multiprocessing.Queue() )
    jobs=[]
    prev_pipe = 0
    for pid in range(processes):
        p = multiprocessing.Process( target = getFileLineCount, args=(queues, pid, processes, file_name,) )
        p.start()
        jobs.append(p)

    jobs[0].join() #wait for counting to finish
    lines = queues[0].get()

    logging.info( 'finished {} Lines:{}'.format( time.time() - t, lines ) )

def file_len(full_path):
  """ Count number of lines in a file."""
  f = open(full_path)
  nr_of_lines = sum(1 for line in f)
  f.close()
  return nr_of_lines

আপনি একটি subprocess চালানো এবং wc -l filename চালাতে পারে

import subprocess

def file_len(fname):
    p = subprocess.Popen(['wc', '-l', fname], stdout=subprocess.PIPE, 
                                              stderr=subprocess.PIPE)
    result, err = p.communicate()
    if p.returncode != 0:
        raise IOError(err)
    return int(result.strip().split()[0])

এই কোডটি সংক্ষিপ্ত এবং পরিষ্কার। এটি সম্ভবত সেরা উপায়:

num_lines = open('yourfile.ext').read().count('\n')

আধুনিক subprocess.check_output ফাংশন ব্যবহার করে, এই উত্তরটির অনুরূপ একটি এক-লাইন bash সমাধান:

def line_count(file):
    return int(subprocess.check_output('wc -l {}'.format(file), shell=True).split()[0])

কিভাবে এই সম্পর্কে?

import fileinput
import sys

counter=0
for line in fileinput.input([sys.argv[1]]):
    counter+=1

fileinput.close()
print counter

আপনি os.path মডিউলটি নিম্নোক্ত উপায়ে ব্যবহার করতে পারেন:

import os
import subprocess
Number_lines = int( (subprocess.Popen( 'wc -l {0}'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] )

, যেখানে Filename ফাইলটির পরম পথ।


এক লাইন সমাধান

import os
os.system("wc -l  filename")  

আমার স্নিপেট

os.system ('wc -l * .txt')

0 bar.txt
1000 command.txt
3 test_file.txt
1003 total

আরেকটি সম্ভাবনাঃ

import subprocess

def num_lines_in_file(fpath):
    return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])

কাইলের উত্তর

num_lines = sum(1 for line in open('my_file.txt'))

সম্ভবত সেরা, এই জন্য একটি বিকল্প

num_lines =  len(open('my_file.txt').read().splitlines())

এখানে উভয় কর্মক্ষমতা তুলনা করা হয়

In [20]: timeit sum(1 for line in open('Charts.ipynb'))
100000 loops, best of 3: 9.79 µs per loop

In [21]: timeit len(open('Charts.ipynb').read().splitlines())
100000 loops, best of 3: 12 µs per loop

আমি বিশ্বাস করি যে একটি মেমরি ম্যাপ করা ফাইল দ্রুততম সমাধান হবে। আমি চারটি ফাংশন চেষ্টা করেছি: ফাংশনটি OP ( opcount ) দ্বারা পোস্ট করা হয়েছে; ফাইল ( simplecount ) লাইনের উপর একটি সহজ পুনরাবৃত্তি; একটি মেমরি- mapcount দায়ের (mmap) ( mapcount ) সঙ্গে রেডলাইন; এবং বাফার মিকোলা খেরচ্কো ( bufcount ) দ্বারা প্রস্তাবিত সমাধানটি পড়ুন।

আমি পাঁচবার প্রতিটি ফাংশন চালাচ্ছিলাম, এবং 1.2 মিলিয়ন-লাইন টেক্সট ফাইলের জন্য গড় রান টাইম গণনা করেছি।

উইন্ডোজ এক্সপি, পাইথন 2.5, ২ গিগাবাইট র্যাম, ২ গিগাহার্জ এএমডি প্রসেসর

এখানে আমার ফলাফল:

mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714

সম্পাদনা করুন : পাইথন 2.6 এর সংখ্যা:

mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297

সুতরাং বাফার পঠন কৌশল উইন্ডোজ / পাইথন 2.6 এর জন্য দ্রুততম বলে মনে হচ্ছে

এখানে কোডটি রয়েছে:

from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict

def mapcount(filename):
    f = open(filename, "r+")
    buf = mmap.mmap(f.fileno(), 0)
    lines = 0
    readline = buf.readline
    while readline():
        lines += 1
    return lines

def simplecount(filename):
    lines = 0
    for line in open(filename):
        lines += 1
    return lines

def bufcount(filename):
    f = open(filename)                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    return lines

def opcount(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1


counts = defaultdict(list)

for i in range(5):
    for func in [mapcount, simplecount, bufcount, opcount]:
        start_time = time.time()
        assert func("big_file.txt") == 1209138
        counts[func].append(time.time() - start_time)

for key, vals in counts.items():
    print key.__name__, ":", sum(vals) / float(len(vals))

আমি এই সংস্করণটির সাথে একটি ছোট (4-8%) উন্নতি পেয়েছি যা একটি ধ্রুবক বাফারটি পুনরায় ব্যবহার করে যাতে এটি কোন মেমরি বা জিসি ওভারহেড এড়াতে পারে:

lines = 0
buffer = bytearray(2048)
with open(filename) as f:
  while f.readinto(buffer) > 0:
      lines += buffer.count('\n')

আপনি বাফার আকারের সাথে প্রায় খেলা করতে পারেন এবং একটু উন্নতি দেখতে পারেন।


উপরের পদ্ধতিগুলি সম্পূর্ণ করার জন্য আমি ফাইল ইনপুট মডিউলটির সাথে একটি বৈকল্পিক চেষ্টা করেছি:

import fileinput as fi   
def filecount(fname):
        for line in fi.input(fname):
            pass
        return fi.lineno()

এবং সমস্ত উপরে বর্ণিত পদ্ধতিতে একটি 60mil লাইন ফাইল পাস:

mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974

এটি আমার কাছে একটু অবাক করে যে ফাইলিনপটটি অন্যান্য সমস্ত পদ্ধতির চেয়ে খারাপ এবং স্কেলগুলিকে আরও খারাপ করে ...


কিভাবে এই এক-মাছ ধরার নৌকা সম্পর্কে:

file_length = len(open('myfile.txt','r').read().split('\n'))

এটি 3900 লাইন ফাইলের সময় এই পদ্ধতি ব্যবহার করে 0.003 সেকেন্ড নেয়

def c():
  import time
  s = time.time()
  file_length = len(open('myfile.txt','r').read().split('\n'))
  print time.time() - s

একটি ফাইল খোলার ফলাফল একটি ইন্টিটারেটর, যা একটি ক্রম রূপান্তর করা যেতে পারে, যা একটি দৈর্ঘ্য রয়েছে:

with open(filename) as f:
   return len(list(f))

এটি আপনার স্পষ্ট লুপের তুলনায় আরও সংক্ষিপ্ত, এবং সংখ্যাটি এড়িয়ে চলতে পারে।


এখানে আমি যা ব্যবহার করি তা বেশ পরিষ্কার বলে মনে হচ্ছে:

import subprocess

def count_file_lines(file_path):
    """
    Counts the number of lines in a file using wc utility.
    :param file_path: path to file
    :return: int, no of lines
    """
    num = subprocess.check_output(['wc', '-l', file_path])
    num = num.split(' ')
    return int(num[0])

আপডেট: এটি বিশুদ্ধ পাইথন ব্যবহার করার চেয়ে সামান্য দ্রুত কিন্তু মেমরি ব্যবহারের খরচ। সাবপ্রোসেসটি আপনার কমান্ডটি কার্যকর করার সময় একই মেমরি পাদচরণের সাথে একটি নতুন প্রক্রিয়া ফাঁকা করবে।


def line_count(path):
    count = 0
    with open(path) as lines:
        for count, l in enumerate(lines, start=1):
            pass
    return count

ফাইল মেমরি তারপর মাপসই করতে পারেন, তারপর

with open(fname) as f:
    count = len(f.read().split(b'\n')) - 1

এই বিশুদ্ধ Python ব্যবহার করে পাওয়া দ্রুততম জিনিস। আপনি বাফার সেট করে যে পরিমাণ মেমরি চান তা ব্যবহার করতে পারেন, যদিও 2 ** 16 আমার কম্পিউটারে মিষ্টি জায়গা বলে মনে হচ্ছে।

from functools import partial

buffer=2**16
with open(myfile) as f:
        print sum(x.count('\n') for x in iter(partial(f.read,buffer), ''))

আমি এখানে উত্তর খুঁজে পাই কেন পিডিথনের চেয়ে সি ++ তে স্টেডিন থেকে লাইন পড়তে হয়? এবং এটি একটি ক্ষুদ্র বিট tweaked। লাইন দ্রুত গণনা করা যায় তা বোঝার জন্য এটি একটি খুব ভাল পঠন, যদিও wc -l এখনও অন্য যেকোনো তুলনায় প্রায় 75% দ্রুত।


এই সম্পর্কে কি

def file_len(fname):
  counts = itertools.count()
  with open(fname) as f: 
    for _ in f: counts.next()
  return counts.next()

আমি এই মত বাফার ক্ষেত্রে সংশোধন করেছি:

def CountLines(filename):
    f = open(filename)
    try:
        lines = 1
        buf_size = 1024 * 1024
        read_f = f.read # loop optimization
        buf = read_f(buf_size)

        # Empty file
        if not buf:
            return 0

        while buf:
            lines += buf.count('\n')
            buf = read_f(buf_size)

        return lines
    finally:
        f.close()

এখন খালি ফাইল এবং শেষ লাইন (\ n) ছাড়া গণনা করা হয়।


আমার জন্য এই রূপটি দ্রুততম হবে:

#!/usr/bin/env python

def main():
    f = open('filename')                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    print lines

if __name__ == '__main__':
    main()

কারণ: লাইন এবং string.count দ্বারা লাইন পড়া চেয়ে দ্রুত বাফারিং খুব দ্রুত





line-count