programing

전체 파일을 메모리로 읽지 않고 파일의 줄 수를 계산합니까?

copyandpastes 2021. 1. 16. 10:59
반응형

전체 파일을 메모리로 읽지 않고 파일의 줄 수를 계산합니까?


거대한 데이터 파일 (각각 수백만 줄)을 처리하고 있습니다.

처리를 시작하기 전에 파일의 줄 수를 확인하여 처리가 얼마나 진행되고 있는지 표시 할 수 있습니다.

파일의 크기 때문에 전체 파일을 메모리로 읽는 것은 실용적이지 않습니다. 단지 몇 줄이 있는지 계산하는 것뿐입니다. 누구든지 이것을 수행하는 방법에 대한 좋은 제안이 있습니까?


Unix 환경에 있다면 wc -l작업을 맡길 수 있습니다.

전체 파일을 메모리로로드하지 않습니다. 스트리밍 파일 및 단어 / 줄 수에 최적화되어 있기 때문에 Ruby에서 직접 파일을 스트리밍하는 것보다 성능이 충분합니다.

SSCCE :

filename = 'a_file/somewhere.txt'
line_count = `wc -l "#{filename}"`.strip.split(' ')[0].to_i
p line_count

또는 명령 줄에 전달 된 파일 모음을 원하는 경우 :

wc_output = `wc -l "#{ARGV.join('" "')}"`
line_count = wc_output.match(/^ *([0-9]+) +total$/).captures[0].to_i
p line_count

한 번에 한 줄씩 파일 읽기 :

count = File.foreach(filename).inject(0) {|c, line| c+1}

또는 Perl-ish

File.foreach(filename) {}
count = $.

또는

count = 0
File.open(filename) {|f| count = f.read.count("\n")}

보다 느릴 것입니다

count = %x{wc -l #{filename}}.split.first.to_i

어떤 언어를 사용하든 상관 없습니다. 줄 길이가 가변적이라면 전체 파일을 읽어야합니다. 그 이유는 줄 바꿈이 어디에나있을 수 있고 파일을 읽지 않고는 알 수있는 방법이 없기 때문입니다 (캐시되지 않는다고 가정하면 일반적으로 그렇지 않습니다).

진행 상황을 표시하려면 두 가지 현실적인 옵션이 있습니다. 가정 된 줄 길이를 기반으로 진행 상황을 추정 할 수 있습니다.

assumed lines in file = size of file / assumed line size
progress = lines processed / assumed lines in file * 100%

파일의 크기를 알고 있기 때문입니다. 또는 다음과 같이 진행 상황을 측정 할 수 있습니다.

progress = bytes processed / size of file * 100%

이것으로 충분합니다.


루비 사용 :

file=File.open("path-to-file","r")
file.readlines.size

325.477 라인 파일에서 wc -l보다 39 밀리 초 더 빠름


게시 된 솔루션 요약

require 'benchmark'
require 'csv'

filename = "name.csv"

Benchmark.bm do |x|
  x.report { `wc -l < #{filename}`.to_i }
  x.report { File.open(filename).inject(0) { |c, line| c + 1 } }
  x.report { File.foreach(filename).inject(0) {|c, line| c+1} }
  x.report { File.read(filename).scan(/\n/).count }
  x.report { CSV.open(filename, "r").readlines.count }
end

807802 줄이있는 파일 :

       user     system      total        real
   0.000000   0.000000   0.010000 (  0.030606)
   0.370000   0.050000   0.420000 (  0.412472)
   0.360000   0.010000   0.370000 (  0.374642)
   0.290000   0.020000   0.310000 (  0.315488)
   3.190000   0.060000   3.250000 (  3.245171)

DJ의 답변과 동일하지만 실제 Ruby 코드를 제공합니다.

count = %x{wc -l file_path}.split[0].to_i

첫 번째 부분

wc -l file_path

당신에게 준다

num_lines file_path

splitto_i숫자에 그것을 넣어.


내가 완전히 이해하지 못하는 이유로 파일을 사용하여 줄 바꿈을 스캔하는 File것이 CSV#readlines.count.

다음 벤치 마크에서는 1,045,574 줄의 데이터와 4 개의 열이있는 CSV 파일을 사용했습니다.

       user     system      total        real
   0.639000   0.047000   0.686000 (  0.682000)
  17.067000   0.171000  17.238000 ( 17.221173)

벤치 마크 코드는 다음과 같습니다.

require 'benchmark'
require 'csv'

file = "1-25-2013 DATA.csv"

Benchmark.bm do |x|
    x.report { File.read(file).scan(/\n/).count }
    x.report { CSV.open(file, "r").readlines.count }
end

보시다시피 파일에서 줄 바꿈을 검색하는 것이 훨씬 더 빠릅니다.


이 라이너가 하나 있습니다.

puts File.foreach('myfile.txt').count

파일이 CSV 파일 인 경우 파일 내용이 숫자 인 경우 레코드 길이는 매우 균일해야합니다. 파일의 크기를 레코드 길이나 처음 100 개 레코드의 평균으로 나누는 것이 합리적이지 않을까요?


135,000 개 이상의 라인에 대한 테스트 결과가 아래에 나와 있습니다. 이것은 내 벤치 마크 코드입니다.

 file_name = '100m.csv'
 Benchmark.bm do |x|
   x.report { File.new(file_name).readlines.size }
   x.report { `wc -l "#{file_name}"`.strip.split(' ')[0].to_i }
   x.report { File.read(file_name).scan(/\n/).count }
 end

결과는

   user     system      total        real
 0.100000   0.040000   0.140000 (  0.143636)
 0.000000   0.000000   0.090000 (  0.093293)
 0.380000   0.060000   0.440000 (  0.464925)

The wc -l code has one problem. If there is only one line in the file and the last character does not end with \n, then count is zero.

So, I recommend calling wc when you count more then one line.


With UNIX style text files, it's very simple

f = File.new("/path/to/whatever")
num_newlines = 0
while (c = f.getc) != nil
  num_newlines += 1 if c == "\n"
end

That's it. For MS Windows text files, you'll have to check for a sequence of "\r\n" instead of just "\n", but that's not much more difficult. For Mac OS Classic text files (as opposed to Mac OS X), you would check for "\r" instead of "\n".

So, yeah, this looks like C. So what? C's awesome and Ruby is awesome because when a C answer is easiest that's what you can expect your Ruby code to look like. Hopefully your dain hasn't already been bramaged by Java.

By the way, please don't even consider any of the answers above that use the IO#read or IO#readlines method in turn calling a String method on what's been read. You said you didn't want to read the whole file into memory and that's exactly what these do. This is why Donald Knuth recommends people understand how to program closer to the hardware because if they don't they'll end up writing "weird code". Obviously you don't want to code close to the hardware whenever you don't have to, but that should be common sense. However you should learn to recognize the instances which you do have to get closer to the nuts and bolts such as this one.

And don't try to get more "object oriented" than the situation calls for. That's an embarrassing trap for newbies who want to look more sophisticated than they really are. You should always be glad for the times when the answer really is simple, and not be disappointed when there's no complexity to give you the opportunity to write "impressive" code. However if you want to look somewhat "object oriented" and don't mind reading an entire line into memory at a time (i.e., you know the lines are short enough), you can do this

f = File.new("/path/to/whatever")
num_newlines = 0
f.each_line do
  num_newlines += 1
end

This would be a good compromise but only if the lines aren't too long in which case it might even run more quickly than my first solution.


wc -l in Ruby with less memory, the lazy way:

(ARGV.length == 0 ?
 [["", STDIN]] :
    ARGV.lazy.map { |file_name|
        [file_name, File.open(file_name)]
})
.map { |file_name, file|
    "%8d %s\n" % [*file
                    .each_line
                    .lazy
                    .map { |line| 1 }
                    .reduce(:+), file_name]
}
.each(&:display)

as originally shown by Shugo Maeda.

Example:

$ curl -s -o wc.rb -L https://git.io/vVrQi
$ chmod u+x wc.rb
$ ./wc.rb huge_data_file.csv
  43217291 huge_data_file.csv

Using foreach without inject is about 3% faster than with inject. Both are very much faster (more than 100x in my experience) than using getc.

Using foreach without inject can also be slightly simplified (relative to the snippet given elsewhere in this thread) as follows:

count = 0;  File.foreach(path) { count+=1}
puts "count: #{count}"

You can read the last line only and see its number:

f = File.new('huge-file')
f.readlines[-1]
count = f.lineno

ReferenceURL : https://stackoverflow.com/questions/2650517/count-the-number-of-lines-in-a-file-without-reading-entire-file-into-memory

반응형