Подтвердить что ты не робот

Могу ли я использовать список как хэш в 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, чем собственный список()?

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

Ваш код очень не похож на 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, и если бы это был цикл for, который был бы искушением. В R берут как можно больше из вашей петли. Использование применяемых семейных команд делает это более естественным, потому что команда предназначена для представления приложения одной функции в список определенного типа, а не в общий цикл (да, я знаю, что apply может использоваться более чем для одной команды).

Ответ 4

Я немного взломан R, но я эмпирик, поэтому я поделюсь некоторыми вещами, которые я наблюдал, и пусть те, у кого больше теоретического понимания R, проливают свет на whys.

  • R выглядит намного медленнее, используя стандартные потоки, чем Perl. Поскольку stdin и толстые гораздо чаще используются в Perl Я предполагаю, что он имеет оптимизацию вокруг, как это происходит. Итак, в R I найти МНОГО быстрее читать/писать текст, используя встроенный функции (например, write.table).

  • Как говорили другие, вектор операции в R быстрее, чем петли... и w.r.t. скорость, большинство применяемых() семейств синтаксис - просто симпатичная оболочка на цикл.

  • Индексированные вещи работают быстрее, чем неиндексированный. (Очевидно, я знаю.) Пакет data.table поддерживает индексирование объектов типа фрейма данных.

  • Я никогда не использовал хэш такие среды, как @Allen, проиллюстрированные (и я никогда не вдыхал хэш... насколько вам известно)

  • Некоторый синтаксис, который вы использовали, работает, но может быть затянут. Я не думаю, что это действительно важно для скорости, но код немного читабельнее. Я не пишу очень жесткий код, но я отредактировал несколько вещей, таких как изменение floor(1000*runif(1)) до sample(1:1000, n, replace=T). Я не хочу быть педантичным, я просто написал это так, как я бы сделал это с нуля.

Итак, имея в виду, я решил проверить хэш-подход, который использовал @allen (потому что он для меня роман) против моего "хэша бедного человека", который я создал с помощью индексированной таблицы данных в качестве таблицы поиска. Я не уверен на 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, который на моем ПК, кажется, запускает 100K выборок в течение секунды.

Я надеюсь, что приведенный выше пример поможет. И если у вас есть какие-либо вопросы относительно why, возможно, @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-код!

Ответ 5

Но среда не может содержать другую среду (цитируется с ответа Винса).

Возможно, так было некоторое время назад (я не знаю), но эта информация, похоже, уже не точна:

> d <- new.env()
> d$x <- new.env()
> d$x$y = 20
> d$x$y
[1] 20

Итак, среда делает довольно способную карту /dict сейчас. Возможно, вы пропустите оператор "[", используйте хэш-пакет в этом случае.

Эта заметка, взятая из документации хеш-пакета, также может представлять интерес:

R медленно движется к собственной реализации хешей, используя (см. Экстракт. Доступ к средам с использованием $и [[имеет были доступны в течение некоторого времени, а недавно объекты могут наследоваться от среды и т.д. Но многие функции, которые делают хеши/словари все еще не хватает, например, операция среза, [.

Ответ 6

Прежде всего, как сказал Винс и Дирк, вы не используете хэши в своем примере кода. Буквальный перевод примера 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 (например, о карте в perl), и его решение, вероятно, является лучшим. Если вы хотите использовать хэши, рассмотрите

keys <- sample(ls(envir = testHash), 10000, replace = TRUE)
vals <- mget(keys, envir = testHash)

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

cat(paste(keys, vals), sep="\n")

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

Аллана