Ruby: Есть ли что-то вроде Enumerable#drop, которое возвращает перечислитель вместо массива?

У меня есть несколько больших файлов с фиксированной шириной, и мне нужно удалить строку заголовка.

Отслеживание итератора не кажется очень идиоматичным.

# This is what I do now.
File.open(filename).each_line.with_index do |line, idx|
  if idx > 0
     ...
  end
end

# This is what I want to do but I don't need drop(1) to slurp
# the file into an array.
File.open(filename).drop(1).each_line do { |line| ... }

Какая идиома Ruby для этого?


person Samuel Danielson    schedule 04.02.2010    source источник


Ответы (6)


Если вам это нужно более одного раза, вы можете написать расширение для Enumerator.

class Enumerator
  def enum_drop(n)
    with_index do |val, idx|
      next if n == idx
      yield val
    end
  end
end

File.open(testfile).each_line.enum_drop(1) do |line|
  print line
end

# prints lines #1, #3, #4, …
person Debilski    schedule 04.02.2010
comment
Это очень красивое (и рубиновое) решение. Если вам не нравится язык, измените его. Я был уверен, что то, что я хотел сделать, было настолько распространенным, что для этого существовала идиома или функция. Прошло два дня с тех пор, как я спросил, так что я полагаю, что нет. enum_cons и enum_slice существуют, поэтому, возможно, имя enum_drop лучше подходит для stdlib. Спасибо. - person Samuel Danielson; 06.02.2010

Это немного аккуратнее:

File.open(fname).each_line.with_index do |line, lineno|
  next if lineno == 0
  # ...
end

or

io = File.open(fname)
# discard the first line
io.gets
# process the rest of the file
io.each_line {|line| ...}
io.close
person glenn jackman    schedule 04.02.2010
comment
Мне нравится второе решение Гленна здесь, даже если оно не использует более аккуратное замыкание File.open() do ... end. - person bta; 04.02.2010

Теперь, когда вы получили разумные ответы, есть совершенно другой способ справиться с этим.

class ProcStack
  def initialize(&default)
    @block = default
  end
  def push(&action)
    prev = @block
    @block = lambda do |*args|
      @block = prev
      action[*args]
    end
    self
  end
  def to_proc
    lambda { |*args| @block[*args] }
  end
end
#...
process_lines = ProcStack.new do |line, index|
  puts "processing line #{index} => #{line}"
end.push do |line, index|
  puts "skipping line #{index} => #{line}"
end
File.foreach(filename).each_with_index(&process_lines)

Это не идиоматично и не слишком интуитивно понятно с первого раза, но это весело!

person rampion    schedule 04.02.2010
comment
на самом деле, если вы используете очередь, это намного понятнее (менее обратная логика) - person rampion; 04.02.2010

Навскидку, но я уверен, что после дополнительных исследований можно найти более элегантный способ

File.open( filename ).each_line.to_a[1..-1].each{ |line|... }

Хорошо, зачеркни это... провел небольшое исследование, и это может быть лучше.

File.open( filename ).each_line.with_index.drop_while{ |line,index|  index == 0 }.each{ |line, index| ... }
person Farrel    schedule 04.02.2010
comment
Это будет жадно оценивать перечислитель до массива перед итерацией по строкам, в результате чего весь файл будет сразу загружен в память. - person Samuel Danielson; 04.02.2010
comment
Ага понял это. Я обновил его, используя только счетчики. - person Farrel; 04.02.2010
comment
Не уверен, что drop_while будет работать, так как согласно документам он также возвращает массив... - person Farrel; 04.02.2010
comment
Ага. Похоже, что drop_while возвращает массив. 10.times.class =› Перечислитель ... 10.times.drop_while{ |line,index| index == 0 }.class =› Массив - person Samuel Danielson; 04.02.2010

Я сомневаюсь, что это идиоматично, но это просто.

f = File.open(filename)
f.readline
f.each_line do |x|
   #...
end
person Shadowfirebird    schedule 04.02.2010
comment
Прошу прощения, я вижу, что пока я сочинял это, Гленн опередил меня. - person Shadowfirebird; 04.02.2010

Я думаю, что вы правильно поняли Enumerator и drop(1). По какой-то странной причине Enumerable определяет #drop, а Enumerator — нет. Вот рабочий Enumerator#drop:

  class Enumerator
    def drop(n_arg)
      n = n_arg.to_i # nil becomes zero
      raise ArgumentError, "n must be positive" unless n > 0
      Enumerator.new do |yielder|
        self.each do |val|
          if n > 0
            n -= 1
          else
            yielder << val
          end
        end
      end
    end
  end
person Bill Burcham    schedule 08.04.2013