Массив #delete_at или массив #slice!? и как искать реализации

#ruby #arrays

#ruby #массивы

Вопрос:

Я очищаю большие файлы данных ( 1 мм строк, разделенных запятыми). Пример строки может выглядеть так:

 @row = "123456789,11122,CustomerName,2014-01-31,2014-02-01,RemoveThisEntry,R,SKUInfo,05-MAR-14 05:50:24,SourceID,RemoveThisEntryToo,TransactionalID"
 

Из него должны быть удалены определенные столбцы, после чего строка должна выглядеть так:

 @row = "123456789,11122,CustomerName,2014-01-31,2014-02-01,R,SKUInfo,05-MAR-14 05:50:24,SourceID,TransactionalID"
 

ВОПРОС 1: Если я преобразую строку данных в an Array , какой метод предпочтительнее для удаления элементов: Array#delete_at или Array#slice !? Я хотел бы знать, какой вариант является более идиоматичным. Здесь важна производительность, и я нахожусь на компьютере с Windows.

 def remove_bad_columns
  ary = @row.split(",")
  ary.delete_at(10)
  ary.delete_at(5)
  @row = ary.join(",")
end
 

ВОПРОС 2: Мне было интересно, был ли реализован один из этих методов с использованием другого. Как я могу увидеть, как методы встроены в ruby? (Как for это реализовано с помощью each , например.)

Комментарии:

1. Вы должны использовать CSV библиотеку Ruby при обработке данных CSV. Если важна скорость, инструменты командной строки обычно быстрее: cut -d "," -f 1-5,7-10,12 largedatafile.csv

2. Производительность здесь имеет значение, так как файлы могут содержать 6 ММ строк при просмотре наблюдений за год (или более). (Я должен был упомянуть об этом в вопросе.) Я тоже пытался использовать CSV библиотеку на первом проходе (потому что вы определенно правы), но скрипт продолжал натыкаться на неприятные артефакты в данных — после стольких изменений с помощью манипуляций со строками я решил, что просто попытаюсь придерживаться строк повсюду. Я не рассматривал такую утилиту, как * nix cut , и сейчас скачиваю cygwin , чтобы попробовать. Спасибо!

3. Вы всегда можете покопаться в коде MRI Ruby. Он открыт, и это позволит вам увидеть, как все реализовано на более низком уровне. Будьте готовы прочитать C, хотя 🙂 github.com/ruby/ruby

4. В MRI Ruby Array#slice! это реализуется функцией rb_ary_slice_bang , которая в случае, если вы передаете только один индекс для удаления, просто вызывает rb_ary_delete_at . rb_ary_delete_at это функция, которая в конечном Array#delete_at итоге тоже реализуется, с помощью rb_delete_at_m которой просто преобразует параметр из объекта Ruby в C ‘long’.

5. Если ваши столбцы содержат встроенные запятые, вас быстро загонят в угол, если вы попытаетесь разделить, используя что-то такое простое, как split(',') . CSV будет обрабатывать условия, которые простой код не может.

Ответ №1:

Я предлагаю вам использовать Array#values_at, а не delete_at or slice! :

 def remove_vals(str, *indices)
  ary = str.split(",")
  v = (0...ary.size).to_a - indices
  ary.values_at(*v).join(",")
end

@row = "123456789,11122,CustomerName,2014-01-31,2014-02-01,RemoveThisEntry,"  
      "R,SKUInfo,05-MAR-14 05:50:24,SourceID,RemoveThisEntryToo,TransactionalID"

@row = remove_vals(@row, 5, 10)
  #=> "123456789,11122,CustomerName,2014-01-31,2014-02-01,R,SKUInfo,"  
  #   "05-MAR-14 05:50:24,SourceID,TransactionalID"
 

Array#values_at имеет преимущество перед двумя другими методами в том, что вам не нужно беспокоиться о порядке удаления элементов.

Эффективность этого метода существенно не отличается от двух других. Если @spickermann хотел бы добавить его в свои тесты, он мог бы использовать это:

 def values_at
  ary = array.split(",")
  v = (0...ary.size).to_a - [5,10]
  @row = ary.values_at(*v).join(",")
end
 

Комментарии:

1. Это потрясающая стратегия, и она сработала как gangbusters на небольшом тесте — спасибо! values_at В мой список ЗАДАЧ добавлено использование…

Ответ №2:

На самом деле разницы в производительности нет. Я бы предпочел delete_at , потому что это читается лучше.

 require 'benchmark'

def array
  "123456789,11122,CustomerName,2014-01-31,2014-02-01,RemoveThisEntry,R,SKUInfo,05-MAR-14 05:50:24,SourceID,RemoveThisEntryToo,TransactionalID"
end 

def delete_at
  ary = array.dup.split(",")
  ary.delete_at(10)
  ary.delete_at(5)
  @row = ary.join(",")
end

def slice!
  ary = array.dup.split(",")
  ary.slice!(10)
  ary.slice!(5)
  @row = ary.join(",")
end

require 'benchmark'

n = 1_000_000
Benchmark.bmbm(15) do |x|
  x.report("delete_at :")   { n.times do; delete_at; end }
  x.report("slice!    :")   { n.times do; slice!   ; end }
end

# Rehearsal ---------------------------------------------------
# delete_at :       4.560000   0.000000   4.560000 (  4.566496)
# slice!    :       4.580000   0.010000   4.590000 (  4.576767)
# ------------------------------------------ total: 9.150000sec
# 
#                       user     system      total        real
# delete_at :       4.500000   0.000000   4.500000 (  4.505638)
# slice!    :       4.600000   0.000000   4.600000 (  4.613447)
 

Комментарии:

1. Я тоже выбрал delete_at для удобства чтения, ха-ха. Спасибо вам за то, что вы также выполнили тестовую проверку — как вы думаете, означает ли их сходство, что одно реализовано в терминах другого?

2. Метода нет deleted_at .