Как эффективно прочитать первый символ из каждой строки текстового файла?
Я бы хотел прочитать только первый символ из каждой строки текстового файла, игнорируя остальные.
Вот пример файла:
x <- c(
"Afklgjsdf;bosfu09[45y94hn9igf",
"Basfgsdbsfgn",
"Cajvw58723895yubjsdw409t809t80",
"Djakfl09w50968509",
"E3434t"
)
writeLines(x, "test.txt")
Я могу решить проблему, прочитав все с помощью readLines
и используя substring
, чтобы получить первый символ:
lines <- readLines("test.txt")
substring(lines, 1, 1)
## [1] "A" "B" "C" "D" "E"
Это кажется неэффективным. Есть ли способ убедить R читать только первые символы, вместо того, чтобы отбрасывать их?
Я подозреваю, что должно быть какое-то заклинание, используя scan
, но я не могу его найти. Альтернативой может быть манипуляция с файлами низкого уровня (возможно, с seek
).
Так как производительность важна только для больших файлов, здесь большой тестовый файл для сравнения:
set.seed(2015)
nch <- sample(1:100, 1e4, replace = TRUE)
x2 <- vapply(
nch,
function(nch)
{
paste0(
sample(letters, nch, replace = TRUE),
collapse = ""
)
},
character(1)
)
writeLines(x2, "bigtest.txt")
Обновление. Кажется, вы не можете избежать сканирования всего файла. Похоже, что лучшие достижения скорости используют более быструю альтернативу readLines
(Ричард Скривен stringi::stri_read_lines
решение и Josh O ' Brien data.table::fread
) или обрабатывать файл как двоичный (решение Martin Morgan readBin
).
Ответы
Ответ 1
01/04/2015 Отредактировано, чтобы вывести лучшее решение в начало.
Обновление 2 Изменение метода scan()
для запуска в открытом соединении вместо открытия и закрытия на каждой итерации позволяет читать по очереди и исключает цикл. Время немного улучшилось.
## scan() on open connection
conn <- file("bigtest.txt", "rt")
substr(scan(conn, what = "", sep = "\n", quiet = TRUE), 1, 1)
close(conn)
Я также обнаружил функцию stri_read_lines()
в пакете stringi, ее файл справки говорит, что это экспериментальный момент, но это очень быстро.
## stringi::stri_read_lines()
library(stringi)
stri_sub(stri_read_lines("bigtest.txt"), 1, 1)
Ниже приведены тайминги для этих двух методов.
## timings
library(microbenchmark)
microbenchmark(
scan = {
conn <- file("bigtest.txt", "rt")
substr(scan(conn, what = "", sep = "\n", quiet = TRUE), 1, 1)
close(conn)
},
stringi = {
stri_sub(stri_read_lines("bigtest.txt"), 1, 1)
}
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# scan 50.00170 50.10403 50.55055 50.18245 50.56112 54.64646 100
# stringi 13.67069 13.74270 14.20861 13.77733 13.86348 18.31421 100
Оригинальный [медленный] ответ:
Вы можете попробовать read.fwf()
(файл с фиксированной шириной), установив ширину в одиночный 1, чтобы захватить первый символ в каждой строке.
read.fwf("test.txt", 1, stringsAsFactors = FALSE)[[1L]]
# [1] "A" "B" "C" "D" "E"
Конечно, не полностью протестирован, но работает для тестового файла и является хорошей функцией для получения подстрок без чтения всего файла.
Обновление 1: read.fwf()
не очень эффективно, вызывая scan()
и read.table()
внутренне. Мы можем пропустить средних людей и попробовать scan()
напрямую.
lines <- count.fields("test.txt") ## length is num of lines in file
skip <- seq_along(lines) - 1 ## set up the 'skip' arg for scan()
read <- function(n) {
ch <- scan("test.txt", what = "", nlines = 1L, skip = n, quiet=TRUE)
substr(ch, 1, 1)
}
vapply(skip, read, character(1L))
# [1] "A" "B" "C" "D" "E"
version$platform
# [1] "x86_64-pc-linux-gnu"
Ответ 2
Если вы разрешаете/имеете доступ к инструментам командной строки Unix, вы можете использовать
scan(pipe("cut -c 1 test.txt"), what="", quiet=TRUE)
Очевидно, что менее портативный, но возможно очень быстрый.
Используя код сравнения @RichieCotton с OP, предложили файл "bigtest.txt":
expr min lq mean median uq
RC readLines 14.797830 17.083849 19.261917 18.103020 20.007341
RS read.fwf 125.113935 133.259220 148.122596 138.024203 150.528754
BB scan pipe cut 6.277267 7.027964 7.686314 7.337207 8.004137
RC readChar 1163.126377 1219.982117 1324.576432 1278.417578 1368.321464
RS scan 13.927765 14.752597 16.634288 15.274470 16.992124
Ответ 3
data.table::fread()
, похоже, превзошел все предлагаемые решения и имеет большую эффективность в сравнении с Windows и * NIX:
library(data.table)
substring(fread("bigtest.txt", sep="\n", header=FALSE)[[1]], 1, 1)
Ниже приведены тайм-ауты microbenchmark на ящике Linux (на самом деле это ноутбук с двойной загрузкой, загруженный как Ubuntu):
Unit: milliseconds
expr min lq mean median uq max neval
RC readLines 15.830318 16.617075 18.294723 17.116666 18.959381 27.54451 100
JOB fread 5.532777 6.013432 7.225067 6.292191 7.727054 12.79815 100
RS read.fwf 111.099578 113.803053 118.844635 116.501270 123.987873 141.14975 100
BB scan pipe cut 6.583634 8.290366 9.925221 10.115399 11.013237 15.63060 100
RC readChar 1347.017408 1407.878731 1453.580001 1450.693865 1491.764668 1583.92091 100
И вот тайминг с одного ноутбука, загруженного как компьютер Windows (с помощью инструмента командной строки cut
, поставляемого Rtools):
Unit: milliseconds
expr min lq mean median uq max neval cld
RC readLines 26.653266 27.493167 33.13860 28.057552 33.208309 61.72567 100 b
JOB fread 4.964205 5.343063 6.71591 5.538246 6.027024 13.54647 100 a
RS read.fwf 213.951792 217.749833 229.31050 220.793649 237.400166 287.03953 100 c
BB scan pipe cut 180.963117 263.469528 278.04720 276.138088 280.227259 387.87889 100 d
RC readChar 1505.263964 1572.132785 1646.88564 1622.410703 1688.809031 2149.10773 100 e
Ответ 4
Объясните размер файла, прочитайте его как единый двоичный блок, найдите смещения интересующих символов (не считайте последние "\n", в конце файла!) и принуждаете к окончательная форма
f0 <- function() {
sz <- file.info("bigtest.txt")$size
what <- charToRaw("\n")
x = readBin("bigtest.txt", raw(), sz)
idx = which(x == what)
rawToChar(x[c(1L, idx[-length(idx)] + 1L)], multiple=TRUE)
}
Решение data.table(было, по моему мнению, самым быстрым - нужно включить первую строку как часть данных!)
library(data.table)
f1 <- function()
substring(fread("bigtest.txt", header=FALSE)[[1]], 1, 1)
и в сравнении
> identical(f0(), f1())
[1] TRUE
> library(microbenchmark)
> microbenchmark(f0(), f1())
Unit: milliseconds
expr min lq mean median uq max neval
f0() 5.144873 5.515219 5.571327 5.547899 5.623171 5.897335 100
f1() 9.153364 9.470571 9.994560 10.162012 10.350990 11.047261 100
Все еще расточительно, так как весь файл читается в памяти, прежде чем в основном отбрасываться.
Ответ 5
Контрольные показатели для каждого ответа под Windows.
library(microbenchmark)
microbenchmark(
"RC readLines" = {
lines <- readLines("test.txt")
substring(lines, 1, 1)
},
"RS read.fwf" = read.fwf("test.txt", 1, stringsAsFactors = FALSE)$V1,
"BB scan pipe cut" = scan(pipe("cut -c 1 test.txt"),what=character()),
"RC readChar" = {
con <- file("test.txt", "r")
x <- readChar(con, 1)
while(length(ch <- readChar(con, 1)) > 0)
{
if(ch == "\n")
{
x <- c(x, readChar(con, 1))
}
}
close(con)
}
)
## Unit: microseconds
## expr min lq mean median uq
## RC readLines 561.598 712.876 830.6969 753.929 884.8865
## RS read.fwf 5079.010 6429.225 6772.2883 6837.697 7153.3905
## BB scan pipe cut 308195.548 309941.510 313476.6015 310304.412 310772.0005
## RC readChar 1238.963 1549.320 1929.4165 1612.952 1740.8300
## max neval
## 2156.896 100
## 8421.090 100
## 510185.114 100
## 26437.370 100
И на большем наборе данных:
## Unit: milliseconds
## expr min lq mean median uq max neval
## RC readLines 52.212563 84.496008 96.48517 103.319789 104.124623 158.086020 20
## RS read.fwf 391.371514 660.029853 703.51134 766.867222 777.795180 799.670185 20
## BB scan pipe cut 283.442150 482.062337 516.70913 562.416766 564.680194 567.089973 20
## RC readChar 2819.343753 4338.041708 4500.98579 4743.174825 4921.148501 5089.594928 20
## RS scan 2.088749 3.643816 4.16159 4.651449 4.731706 5.375819 20
Ответ 6
Я не считаю его очень информативным для бенчмарковских операций в порядке микро- или миллисекунд. Но я понимаю, что в некоторых случаях этого избежать нельзя. В этих случаях, тем не менее, я считаю необходимым проверить данные разных (увеличение размеров), чтобы получить приблизительную оценку того, насколько хорошо метод масштабируется.
Здесь мой запуск на тестах @MartinMorgan с использованием f0()
и f1()
на строках 1e4, 1e5 и 1e6 и вот результаты:
1E4
# Unit: milliseconds
# expr min lq mean median uq max neval
# f0() 4.226333 7.738857 15.47984 8.398608 8.972871 89.87805 100
# f1() 8.854873 9.204724 10.48078 9.471424 10.143601 84.33003 100
1e5
# Unit: milliseconds
# expr min lq mean median uq max neval
# f0() 71.66205 176.57649 174.9545 184.0191 187.7107 307.0470 100
# f1() 95.60237 98.82307 104.3605 100.8267 107.9830 205.8728 100
1E6
# Unit: seconds
# expr min lq mean median uq max neval
# f0() 1.443471 1.537343 1.561025 1.553624 1.558947 1.729900 10
# f1() 1.089555 1.092633 1.101437 1.095997 1.102649 1.140505 10
identical(f0(), f1())
вернул TRUE для всех тестов.
Update:
1e7
Я также работал с строками 1e7.
f1()
(data.table) выполнялся через 9.7 секунд, где f0()
выполнялся в 7.8 секунд в первый раз, а 9.4 и 6.6 - второй раз.
Тем не менее, f1()
не привело к заметным изменениям в памяти при чтении всего файла 0.479GB, тогда как f0()
привел к шипу 2,4 ГБ.
Другое наблюдение:
set.seed(2015)
x2 <- vapply(
1:1e5,
function(i)
{
paste0(
sample(letters, 100L, replace = TRUE),
collapse = "_"
)
},
character(1)
)
# 10 million rows, with 200 characters each
writeLines(unlist(lapply(1:100, function(x) x2)), "bigtest.txt")
## readBin() results in a 2 billion row vector
system.time(f0()) ## explodes on memory
Поскольку шаг readBin()
приводит к вектору длины в 2 миллиарда (~ 1,9 ГБ для чтения файла), а шаг which(x == what)
занимает ~ 4.5 + GB (= ~ 6.5 ГБ), в котором я остановил процесс.
fread()
занимает ~ 23 секунды в этом случае.
НТН