Могу ли я использовать список в качестве хэша в R? Если так, то почему это так медленно?

Перед использованием 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, чем собственный список ()?

39
задан Arun 30 March 2013 в 15:36
поделиться

5 ответов

Основная причина в том, что списки R с именованными элементами не хэшируются. Хеш-поиск - это O(1), потому что при вставке ключ преобразуется в целое число с помощью хеш-функции, а затем значение помещается в пространство hash(key) % num_spots массива num_spots long (это большое упрощение и позволяет избежать сложностей, связанных с коллизиями). Поиск ключа требует только хэширования ключа для нахождения позиции значения (что составляет O(1), в отличие от O(n) поиска в массиве). Списки R используют поиск по имени, что составляет O(n).

Как говорит Дирк, используйте пакет hash. Огромное ограничение в нем состоит в том, что он использует окружения (которые хэшируются) и переопределение методов [ для имитации хэш-таблиц. Но окружение не может содержать другое окружение, поэтому вы не можете иметь вложенные хэши с помощью хэш-функции.

Некоторое время назад я работал над реализацией чистой структуры данных хэш-таблицы в C/R, которая могла бы быть вложенной, но она была отложена на потом, пока я работал над другими вещами. Хотя это было бы неплохо иметь :-)

.
34
ответ дан 27 November 2019 в 02:26
поделиться

Во-первых, как сказали Винс и Дирк, вы не используете хеши в своем примере кода. Дословным переводом примера 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")

Надеюсь, это немного поможет.

Аллан

4
ответ дан 27 November 2019 в 02:26
поделиться

Я немного разбираюсь в 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-код!

10
ответ дан 27 November 2019 в 02:26
поделиться

Ваш код очень не похож на 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 можно использовать более чем в одна команда).

11
ответ дан 27 November 2019 в 02:26
поделиться

Вы можете попробовать среды и / или пакет hash Кристофера Брауна (который, как правило, использует внутренние среды).

18
ответ дан 27 November 2019 в 02:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: