R: Recherche rapide dans les listes (environnement)

Je veux avoir une recherche très rapide et il semble que l’utilisation de hachages (via des environnements) est la meilleure solution. Maintenant, j’ai un exemple à utiliser avec les environnements, mais cela ne renvoie pas ce dont j’ai besoin.

Voici un exemple:

a <- data.table::data.table(a=c(1, 3, 5), b=c(2, 4, 6), time=c(10, 20, 30)) my_env <- list2env(a) x <- a[2, .(a, b)] # x=c(3,4) found <- get("x", envir = my_env) 

Je m’attendrais à found = c(3, 4, 20) mais recevez found = c(3, 4) (je veux que la ligne entière soit renvoyée au lieu du sous-ensemble de ligne inconnu)

Backround : j’ai une liste énorme contenant la source et la destination des routes calculées avec osrm, par exemple

 lattitude1, longitude1, lattitude2, longitude2, travel-time 46.12, 8.32, 47.87, 9.92, 1036 ... 

La liste contient dans un premier exemple environ 100 000 lignes. L’utilisation de la recherche binary dans un data.table a accéléré mon code d’un facteur 100, mais une recherche prend encore 1 ms. Comme je dois rechercher de nombreux itinéraires lors d’une simulation (à propos des recherches sur 2e5), je souhaiterais être encore plus rapide.
@ Gregor : Je suis débutant en R, mais je ne pense pas que ma question soit un doublon:

  1. Je connaissais le deuxième lien, qui est un résumé des possibilités d’expertise des experts. De plus, il a 4 ans.
  2. Je ne connaissais pas le premier lien, mais à partir de ces réponses, je ne peux pas voir si je devrais passer aux environnements et comment une implémentation pourrait fonctionner. Il n’y a pas non plus de discussion sur la recherche d’une partie d’une énorme liste.

Résumé (Merci à DigEmAll pour son exemple ci-dessous) :

  • En utilisant Rcpp sur des entiers, la recherche consum moins de mémoire sans perte de qualité. De plus, il est environ 3 fois plus rapide.
  • N’utilisez pas d’environnements hachés lorsque vous souhaitez rechercher des doubles (qui doivent être convertis en chaînes).
  • La mise en œuvre dans le code existant devrait être facile.

    Voici un exemple utilisant environnement et data.table, le code est assez explicite:

     library(data.table) # create a big random example (160k rows) set.seed(123) fromTo <- expand.grid(1:400,1:400) colnames(fromTo) <- c('a','b') DF <- as.data.frame(cbind(fromTo,time=as.integer(runif(nrow(fromTo), min = 1, max=500)))) # setup the environment to use it as hashtable: # we simply put the times inside an enviroment using # a|b (concatenation of a with b) as key timesList <- as.list(DF$time) names(timesList) <- paste(DF$a,DF$b,sep='|') timesEnv <- list2env(timesList) # setup the data.table to use it as hashtable DT <- setDT(DF,key=c('a','b')) # create search functions searchUsingEnv <- function(a,b){ time <- get(paste(a,b,sep='|'),envir=timesEnv,inherits=FALSE) return(time) } searchUsingDataTable <- function(from,to){ time <- DT[.(from,to),time] return(time) } 

    Référence :

     # benchmark functions # ie we try to search ~16K rows in ourtwo kind of hashtables benchEnv <- function(){ n <- nrow(fromTo) s <- as.integer(n * 0.9) for(i in s:n){ searchUsingEnv(fromTo[i,'a'],fromTo[i,'b']) } } benchDT <- function(){ n <- nrow(fromTo) s <- as.integer(n * 0.9) for(i in s:n){ searchUsingDataTable(fromTo[i,'a'],fromTo[i,'b']) } } # let's measure the performances > system.time(benchEnv(), gcFirst = TRUE) user system elapsed 2.26 0.00 2.30 > system.time(benchDT(), gcFirst = TRUE) user system elapsed 42.34 0.00 42.56 

    Conclusions:
    L'environnement semble beaucoup plus rapide que data.table pour un access répété à une seule touche, vous pouvez donc essayer de l'utiliser.


    MODIFIER :

    Les environnements ont un access rapide mais ils ne peuvent avoir que des clés de chaîne qui occupent plus de mémoire que les doubles. J'ai donc ajouté un exemple utilisant Rcpp et std::map<> avec une carte de valeurs multiples:
    ( note: si vous êtes sous Windows, vous devez installer RTools pour que Rcpp fonctionne )

     library(data.table) library(Rcpp) library(inline) nRows <- 1e7 ############# create data.table "DT" containing coordinates and times generate_routes_dt <- function(nmax) { set.seed(123) routes <- data.table(lat1 = numeric(nmax), lng1 = numeric(nmax), lat2 = numeric(nmax), lng2 = numeric(nmax), time = numeric(nmax)) tmp <- sample(seq(46, 49, length.out = nmax), nmax) routes$lat1 <- tmp tmp <- sample(seq(8, 10, length.out = nmax), nmax) routes$lng1 <- tmp tmp <- sample(seq(46, 49, length.out = nmax), nmax) routes$lat2 <- tmp tmp <- sample(seq(8, 10, length.out = nmax), nmax) routes$lng2 <- tmp tmp <- sample(seq(0, 1e7, length.out = nmax), nmax) routes$time <- as.integer(tmp) data.table::setkey(routes, lat1, lng1, lat2, lng2) return(routes) } DT <- generate_routes_dt(nRows) ############# create data.table search function searchUsingDataTable <- function(lat_1,lng_1,lat_2,lng_2){ time <- DT[.(lat_1,lng_1,lat_2,lng_2),time] return(time) } ############# ############# create Rcpp search function # the following code create 2 functions: createMap and getTime # usage: # map <- createMap(lat1Vec,lng1Vec,lat2Vec,lng2Vec,timesVec) # t <- getTime(map,lat1,lng1,lat2,lng2) sourceCpp(code= ' #include  class MultiKey { public: double lat1; double lng1; double lat2; double lng2; MultiKey(double la1, double ln1, double la2, double ln2) : lat1(la1), lng1(ln1), lat2(la2), lng2(ln2) {} bool operator<(const MultiKey &right) const { if ( lat1 == right.lat1 ) { if ( lng1 == right.lng1 ) { if ( lat2 == right.lat2 ) { return lng2 < right.lng2; } else { return lat2 < right.lat2; } } else { return lng1 < right.lng1; } } else { return lat1 < right.lat1; } } }; // [[Rcpp::export]] SEXP createMap(Rcpp::NumericVector lat1, Rcpp::NumericVector lng1, Rcpp::NumericVector lat2, Rcpp::NumericVector lng2, Rcpp::NumericVector times){ std::map* map = new std::map; int n1 = lat1.size(); int n2 = lng1.size(); int n3 = lat2.size(); int n4 = lng2.size(); int n5 = times.size(); if(!(n1 == n2 && n2 == n3 && n3 == n4 && n4 == n5)){ throw std::range_error("input vectors lengths are different"); } for(int i = 0; i < n1; i++){ MultiKey key(lat1[i],lng1[i],lat2[i],lng2[i]); map->insert(std::pair(key, times[i])); } Rcpp::XPtr< std::map > p(map, true); return( p ); } // [[Rcpp::export]] Rcpp::NumericVector getTime(SEXP mapPtr, double lat1, double lng1, double lat2, double lng2){ Rcpp::XPtr< std::map > ptr(mapPtr); MultiKey key(lat1,lng1,lat2,lng2); std::map::iterator it = ptr->find(key); if(it == ptr->end()) return R_NilValue; return Rcpp::wrap(it->second); } ') map <- createMap(DT$lat1,DT$lng1,DT$lat2,DT$lng2,DT$time) searchUsingRcpp <- function(lat_1,lng_1,lat_2,lng_2){ time <- getTime(map,lat_1,lng_1,lat_2,lng_2) return(time) } ############# ############# benchmark set.seed(1234) rowsToSearchOneByOne <- DT[sample.int(nrow(DT),size=nrow(DT),replace=FALSE),] bench <- function(searchFun2Use){ for(i in nrow(rowsToSearchOneByOne)){ key <- rowsToSearchOneByOne[i,] searchFun2Use(key$lat1,key$lng1,key$lat2,key$lng2) } } microbenchmark::microbenchmark( bench(searchUsingRcpp), bench(searchUsingDataTable), times=100) ############# 

    Résultat de référence:

     Unit: microseconds expr min lq mean median uq max neval bench(searchUsingRcpp) 360.959 381.7585 400.4466 391.999 403.9985 665.597 100 bench(searchUsingDataTable) 1103.034 1138.0740 1214.3008 1163.514 1224.9530 2035.828 100 

    Remarque:

    Je ne pense vraiment pas que l'utilisation du double comme clés soit une bonne idée ... les valeurs à virgule flottante doivent être utilisées pour rechercher une certaine tolérance ou à l'intérieur d'une plage, sans rechercher une correspondance parfaite dans une carte.