Самый быстрый способ пропустить строки при разборе файлов в Ruby?

Я пытался найти это, но не нашел многого. Похоже, что-то, что, вероятно, спрашивали раньше (много раз?), поэтому я прошу прощения, если это так.

Мне было интересно, каким будет самый быстрый способ проанализировать определенные части файла в Ruby. Например, предположим, что я знаю, что информация, которая мне нужна для конкретной функции, находится между строками 500 и 600, скажем, файла из 1000 строк. (очевидно, что такой вопрос ориентирован на очень большие файлы, я просто использую эти меньшие числа для примера), поскольку я знаю, что это не будет в первой половине, есть ли быстрый способ игнорировать эту информацию ?

В настоящее время я использую что-то вроде:

while  buffer = file_in.gets and file_in.lineno <600
  next unless file_in.lineno > 500
  if buffer.chomp!.include? some_string
    do_func_whatever
  end
end

Это работает, но я просто не могу не думать, что это могло бы работать лучше.

Я новичок в Ruby и мне интересно изучать новые способы работы с ним.


person DRobinson    schedule 19.02.2011    source источник


Ответы (4)


file.lines.drop(500).take(100) # will get you lines 501-600

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

PS. Комментарий Железного Дровосека заставил меня поэкспериментировать. Хотя я не нашел причин, по которым drop загружал бы файл целиком, проблема действительно существует: drop возвращает остальную часть файла в массиве. Вот как этого можно избежать:

file.lines.select.with_index{|l,i| (501..600) === i}

PS2: Дох, приведенный выше код, хотя и не создает огромного массива, перебирает весь файл, даже строки ниже 600. :( Вот третья версия:

enum = file.lines
500.times{enum.next} # skip 500
enum.take(100) # take the next 100

или, если вы предпочитаете FP:

file.lines.tap{|enum| 500.times{enum.next}}.take(100)

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

person Mladen Jablanović    schedule 19.02.2011
comment
Это выглядит более рубиновым! Я действительно подумал об этом только после того, как разместил вопрос - тот факт, что строки на самом деле не устанавливаются ничем, кроме пробела между символами «новой строки» (или, скорее, до и после). Это означало бы, что все они в любом случае должны быть проанализированы для этого персонажа. Я думаю, если бы у меня было общее представление о пространстве, предшествующем требуемым строкам, в битах/байтах/независимо от того, я мог бы перейти к этой области, а затем начать синтаксический анализ строка за строкой, но пока я соглашусь, что это работает довольно хорошо, как является. Или, как будет с более красивой линией, как ваша собственная! Спасибо. - person DRobinson; 19.02.2011
comment
На самом деле вы могли использовать seek, если строки содержали некоторую информацию, связанную с их положением в файле (например, номера строк или отсортированные временные метки). Затем вы можете использовать какой-то вариант бинарного поиска. Вы можете открыть еще один вопрос, если это поможет в вашем конкретном случае. - person Mladen Jablanović; 19.02.2011
comment
Однако это приводит к некоторым проблемам с масштабируемостью. Если в файле несколько миллионов строк, он будет полностью прочитан в память, прежде чем вы сможете drop. Это может быть медленным и привести к тому, что машина не будет отвечать на запросы при загрузке данных или заполнит всю доступную память, если строки длинные, что вызовет подкачку. Для более безопасного подхода к текстовому файлу вам лучше читать их построчно, пропуская их, пока не дойдете до нужных, а затем захватывайте только нужные строки. - person the Tin Man; 20.02.2011
comment
@The Tin Man: Что заставляет вас думать, что нужно загрузить весь файл, чтобы drop? - person Mladen Jablanović; 20.02.2011
comment
drop находится в Array, а это значит, что сначала должен быть беззвучный to_a. Я только что посмотрел, и Array получает его из Enumerable, а исходный код показывает, что он перебирает свой блок (n) раз, отбрасывая результат. Так что не нужно загружать все в память; Он должен загружать строки последовательно и отбрасывать их. И, как вы говорите, есть разные способы написать это, но конечный результат один и тот же, строки читаются только для того, чтобы считаться. И это было моей точкой зрения, что чтение строк по отдельности связано с проблемой масштабируемости, а не с slurping файлом, который может убить хост. - person the Tin Man; 20.02.2011
comment
для ruby ​​2.0.0p247 вы должны использовать each_line: предупреждение: IO#lines устарел; вместо этого используйте #each_line - person Lucas Renan; 09.10.2013

Я не знаю, есть ли эквивалентный способ сделать это для строк, но вы можете использовать аргумент seek или offset для объекта ввода-вывода, чтобы «пропустить» байты.

См. IO#seek или см. IO#open для получения информации об аргументе смещения.

person coreyward    schedule 19.02.2011
comment
Чтобы узнать, где заканчивается строка (с символом EOL), нет выхода, вы должны прочитать файл побайтно, а затем удалить прочитанную информацию. Если вы ищете 1000-й байт, вы не сможете сказать, сколько строк вы пропустили. Это может быть 400 или 1 или даже ноль. - person karatedog; 01.09.2020

Похоже, здесь может помочь rio. Он предоставляет вам метод lines().

person s.m.    schedule 19.02.2011
comment
Это просто перебирает строки. Это не очень помогает в данной ситуации. - person coreyward; 19.02.2011
comment
@coreyward: почему бы и нет? Вы можете передать ему диапазон и перебрать эти строки. Есть что-то, что мне не хватает? - person s.m.; 19.02.2011
comment
Встроенная библиотека ввода-вывода делает то же самое. - person coreyward; 19.02.2011
comment
@coreyward: я до сих пор не понимаю, извини. ОП запрашивает другие способы чтения только определенных строк файла. Мой ответ не соответствует этому? Вы предложили что-то вроде seek, что не сработает, если вы не знаете, сколько байтов вам придется пропустить (например, вы не знаете, какова длина каждой записи). - person s.m.; 19.02.2011

Вы можете использовать IO#readlines, который возвращает массив со всеми строками

IO.readlines(file_in)[500..600].each do |line| 
  #line is each line in the file (including the last \n)
  #stuff
end

or

f = File.new(file_in)
f.readlines[500..600].each do |line| 
  #line is each line in the file (including the last \n)
  #stuff
end
person pablorc    schedule 19.02.2011
comment
Это не очень удобно для больших файлов. Строить массив из 500 000 записей просто для доступа к 230 000–230 100 неразумно. Во всяком случае, перебирать каждую строку в потоке и отбрасывать их по мере необходимости разумнее, потому что файл не загружается в память сразу. - person coreyward; 19.02.2011
comment
Это может быть моя реализация (и, конечно, я продолжу тестирование, когда у меня будет время), но этот метод кажется немного медленнее, даже на небольших файлах около 2000 строк. Тем не менее, на этих уровнях разница довольно мала (когда я сделал f = Files.new ... readlines[x..y] ... в среднем это заняло ~ 0,85 секунды; первоначальный метод, который я опубликовал, дал мне около 0,75 секунды. среднее) Конечно, я могу делать это неправильно или не очень хорошо. Я сделаю еще несколько тестов. - person DRobinson; 19.02.2011