Перед использованием R я использовал немного Perl. В Perl я часто использовал бы хэши, и поиск хешей обычно считается быстрым в Perl.
Например, следующий код заполнит хэш до 10000 пар ключ / значение, где ключи - случайные буквы и значения являются случайными целыми числами. Затем он выполняет 10000 случайных поисков в этом хэше.
#!/usr/bin/perl -w
use strict;
my @letters = ('a'..'z');
print @letters . "\n";
my %testHash;
for(my $i = 0; $i < 10000; $i++) {
my $r1 = int(rand(26));
my $r2 = int(rand(26));
my $r3 = int(rand(26));
my $key = $letters[$r1] . $letters[$r2] . $letters[$r3];
my $value = int(rand(1000));
$testHash{$key} = $value;
}
my @keyArray = keys(%testHash);
my $keyLen = scalar @keyArray;
for(my $j = 0; $j < 10000; $j++) {
my $key = $keyArray[int(rand($keyLen))];
my $lookupValue = $testHash{$key};
print "key " . $key . " Lookup $lookupValue \n";
}
Теперь, когда я все чаще хочу иметь структуру данных, похожую на хеш, в R. Ниже приведен эквивалентный код R:
testHash <- list()
for(i in 1:10000) {
key.tmp = paste(letters[floor(26*runif(3))], sep="")
key <- capture.output(cat(key.tmp, sep=""))
value <- floor(1000*runif(1))
testHash[[key]] <- value
}
keyArray <- attributes(testHash)$names
keyLen = length(keyArray);
for(j in 1:10000) {
key <- keyArray[floor(keyLen*runif(1))]
lookupValue = testHash[[key]]
print(paste("key", key, "Lookup", lookupValue))
}
Код, кажется, делает эквивалентные вещи. Однако Perl намного быстрее:
>time ./perlHashTest.pl
real 0m4.346s
user **0m0.110s**
sys 0m0.100s
По сравнению с R:
time R CMD BATCH RHashTest.R
real 0m8.210s
user **0m7.630s**
sys 0m0.200s
Чем объясняется несоответствие? Разве поиск в списках R не очень хорош?
Увеличение длины списка до 100 000 и поиск 100 000 только преувеличивают расхождение? Есть ли лучшая альтернатива для структуры хэш-данных в R, чем собственный список ()?
Основная причина в том, что списки R с именованными элементами не хэшируются. Хеш-поиск - это O(1), потому что при вставке ключ преобразуется в целое число с помощью хеш-функции, а затем значение помещается в пространство hash(key) % num_spots
массива num_spots
long (это большое упрощение и позволяет избежать сложностей, связанных с коллизиями). Поиск ключа требует только хэширования ключа для нахождения позиции значения (что составляет O(1), в отличие от O(n) поиска в массиве). Списки R используют поиск по имени, что составляет O(n).
Как говорит Дирк, используйте пакет hash. Огромное ограничение в нем состоит в том, что он использует окружения (которые хэшируются) и переопределение методов [
для имитации хэш-таблиц. Но окружение не может содержать другое окружение, поэтому вы не можете иметь вложенные хэши с помощью хэш-функции.
Некоторое время назад я работал над реализацией чистой структуры данных хэш-таблицы в C/R, которая могла бы быть вложенной, но она была отложена на потом, пока я работал над другими вещами. Хотя это было бы неплохо иметь :-)
.Во-первых, как сказали Винс и Дирк, вы не используете хеши в своем примере кода. Дословным переводом примера perl будет
#!/usr/bin/Rscript
testHash <- new.env(hash = TRUE, size = 10000L)
for(i in 1:10000) {
key <- paste(sample(letters, 3, replace = TRUE), collapse = "")
assign(key, floor(1000*runif(1)), envir = testHash)
}
keyArray <- ls(envir = testHash)
keyLen <- length(keyArray)
for(j in 1:10000) {
key <- keyArray[sample(keyLen, 1)]
lookupValue <- get(key, envir = testHash)
cat(paste("key", key, "Lookup", lookupValue, "\n"))
}
, который на моей машине работает достаточно быстро, в основном это установка. (Попробуйте и опубликуйте время.)
Но настоящая проблема, как сказал Джон, в том, что вам нужно думать о векторах в R (например, map в perl), и его решение, вероятно, лучшее. Если вы действительно хотите использовать хэши, рассмотрите
keys <- sample(ls(envir = testHash), 10000, replace = TRUE)
vals <- mget(keys, envir = testHash)
после такой же настройки, как указано выше, что почти мгновенно на моей машине. Чтобы распечатать их все, попробуйте
cat(paste(keys, vals), sep="\n")
Надеюсь, это немного поможет.
Аллан
Я немного разбираюсь в R, но я эмпирик, поэтому поделюсь некоторыми наблюдениями и позволю тем, у кого более глубокое теоретическое понимание R, пролить свет на то, почему .
R кажется намного медленнее при использовании стандартного
потоков, чем Perl. Поскольку стандартный ввод и
стаут гораздо чаще используются в
Perl, я полагаю, он оптимизирован
вокруг того, как он делает эти вещи. Итак, в R I
найти НАМНОГО быстрее читать / писать текст, используя встроенный
функции (например, write.table
).
Как уже говорили другие, вектор операции в R быстрее, чем петли ... и w.r.t. скорость, наиболее применимая () семья синтаксис - это просто красивая обертка на петля.
Проиндексированные объекты работают быстрее, чем неиндексированные. (Очевидно, я знаю.) Пакет data.table поддерживает индексацию объектов типа фрейма данных.
Я никогда не использовал хеш окружения, подобные проиллюстрированному @Allen (и я никогда не вдыхал хэш ... насколько вы знаете)
Некоторые из используемых вами синтаксисов работают, но их можно улучшить. Я не думаю, что это действительно имеет значение для скорости, но код немного читабельнее. Я не пишу очень жесткий код, но я отредактировал несколько вещей, например, изменил floor (1000 * runif (1))
на sample (1: 1000, n, replace = T)
. Я не хочу быть педантичным, я просто написал так, как я бы сделал это с нуля.
Помня об этом, я решил протестировать хеш-подход, который использовал @allen (потому что он в новинку для меня), с моим «хешем для бедняков», который я создал с использованием индексированной таблицы data.table в качестве таблицы поиска. Я не уверен на 100%, что мы с @allen делаем именно то, что делали вы в Perl, потому что мой Perl довольно ржавый. Но я думаю , что два приведенных ниже метода делают то же самое. Мы оба отбираем второй набор ключей из ключей в «хэше», поскольку это предотвращает промахи хэша. Вы хотели бы проверить, как эти примеры обрабатывают хеш-дубли, поскольку я не задумывался об этом.
require(data.table)
dtTest <- function(n) {
makeDraw <- function(x) paste(sample(letters, 3, replace=T), collapse="")
key <- sapply(1:n, makeDraw)
value <- sample(1:1000, n, replace=T)
myDataTable <- data.table(key, value, key='key')
newKeys <- sample(as.character(myDataTable$key), n, replace = TRUE)
lookupValues <- myDataTable[newKeys]
strings <- paste("key", lookupValues$key, "Lookup", lookupValues$value )
write.table(strings, file="tmpout", quote=F, row.names=F, col.names=F )
}
#
hashTest <- function(n) {
testHash <- new.env(hash = TRUE, size = n)
for(i in 1:n) {
key <- paste(sample(letters, 3, replace = TRUE), collapse = "")
assign(key, floor(1000*runif(1)), envir = testHash)
}
keyArray <- ls(envir = testHash)
keyLen <- length(keyArray)
keys <- sample(ls(envir = testHash), n, replace = TRUE)
vals <- mget(keys, envir = testHash)
strings <- paste("key", keys, "Lookup", vals )
write.table(strings, file="tmpout", quote=F, row.names=F, col.names=F )
}
если я запускаю каждый метод, используя 100 000 отрисовок, я получаю что-то вроде этого:
> system.time( dtTest(1e5))
user system elapsed
2.750 0.030 2.881
> system.time(hashTest(1e5))
user system elapsed
3.670 0.030 3.861
Имейте в виду, что это все еще значительно медленнее, чем код Perl, который на моем ПК, кажется, запускает 100 000 образцов в ну меньше секунды.
Надеюсь, что приведенный выше пример поможет. И если у вас есть какие-либо вопросы относительно , почему
, возможно, @allen, @vince и @dirk смогут ответить;)
После того, как я напечатал вышеупомянутое, я понял, что не тестировал, что @john сделал. Итак, какого черта, давайте сделаем все 3. Я изменил код с @john на использование write.table (), а вот его код:
johnsCode <- function(n){
keys = sapply(character(n), function(x) paste(letters[ceiling(26*runif(3))],
collapse=''))
value <- floor(1000*runif(n))
testHash <- as.list(value)
names(testHash) <- keys
keys <- names(testHash)[ceiling(n*runif(n))]
lookupValue = testHash[keys]
strings <- paste("key", keys, "Lookup", lookupValue )
write.table(strings, file="tmpout", quote=F, row.names=F, col.names=F )
}
и время выполнения:
> system.time(johnsCode(1e5))
user system elapsed
2.440 0.040 2.544
И вот оно. @john пишет жесткий / быстрый R-код!
Ваш код очень не похож на R и является одной из причин, по которым он такой медленный. Я не оптимизировал приведенный ниже код для максимальной скорости, только R'ness.
n <- 10000
keys <- matrix( sample(letters, 3*n, replace = TRUE), nrow = 3 )
keys <- apply(keys, 2, paste0, collapse = '')
value <- floor(1000*runif(n))
testHash <- as.list(value)
names(testHash) <- keys
keys <- sample(names(testHash), n, replace = TRUE)
lookupValue = testHash[keys]
print(data.frame('key', keys, 'lookup', unlist(lookupValue)))
На моей машине это работает почти мгновенно, за исключением печати. Ваш код работал примерно с той же скоростью, о которой вы сообщили. Он делает то, что вы хотите? Вы можете установить n равным 10 и просто посмотреть на вывод и testHash и убедиться, что это так.
ПРИМЕЧАНИЕ по синтаксису:
Вышеупомянутая команда apply
представляет собой просто цикл, и они работают медленно в R. Смысл этих команд семейства apply - выразительность. Многие из следующих ниже команд можно было бы поместить внутрь цикла с помощью apply
, и если бы это был цикл for
, это было бы искушением. В R вытащите из цикла как можно больше. Использование команд apply family делает это более естественным, потому что команда предназначена для представления применения одной функции к списку определенного типа, а не к универсальному циклу (да, я знаю, что apply
можно использовать более чем в одна команда).
Вы можете попробовать среды и / или пакет hash Кристофера Брауна (который, как правило, использует внутренние среды).