filehash/0000755000175100001440000000000013071747306012063 5ustar hornikusersfilehash/inst/0000755000175100001440000000000013071742312013030 5ustar hornikusersfilehash/inst/COPYING0000644000175100001440000000127113042123362014060 0ustar hornikusersLicense ======= `filehash' is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA filehash/inst/CITATION0000644000175100001440000000101513071741770014171 0ustar hornikuserscitHeader("The reference for the 'filehash' package is:") citEntry(entry = "article", title = "Interacting with data using the filehash package", author = personList(person("Roger", "Peng", "D.")), journal = "R News", year = "2006", volume = "6", number = "4", pages = "19--24", url = "https://cran.r-project.org/doc/Rnews/", textVersion = paste("Peng RD (2006).", dQuote("Interacting with data using the filehash package,"), "R News, 6 (4), 19--24.") ) filehash/inst/NEWS0000644000175100001440000000466513042123362013536 0ustar hornikusersCheck the 'filehash' git repository for the latest updates on the package at http://repo.or.cz/w/filehash.git Version 1.0 ----------- * The 'DB' format has been removed; users should use 'DB1' instead * Internals of 'DB1' format have changed so that it should be a bit more reliable but perhaps a little slower * The 'dbDisconnect' generic has been removed since it is no longer necessary for the 'DB1' format (as it was before). It was never needed for the 'RDS' format and one never existed for that format. Version 0.9 ----------- * For 'filehashRDS' class, the 'dbDir' slot has been renamed to 'dir'. * An attempt has been made to normalize the error handling to make it consistent. * The various 'dump' functions have been given a 'type' argument Version 0.8 ----------- * Added function dbLazyLoad for lazy loading filehash databases. * dbCreate and dbInit are now generics with a method for character vectors. The behavior should be the same as before, by default. * dbLoad is generic. * The second argument to dbMultiFetch is 'key', not 'keys'. * dbInitialize is deprecated * 'DB1' and 'RDS' formats use normalizePath() for resolving paths to directories * There is a vignette now [via vignette("filehash")] Version 0.6-3 ------------- * Added methods for "[[", "$", "[[<-", and "$<-" for filehash objects. Only character indices are allowed * filehash-DB functions use the new serialize() from R 2.4.0 so that numeric data will not suffer from rounding error due to previous use of serialize(ascii = TRUE). * New format filehash-DB1 which stores the key index/map and data in a single file. * New "filehash" method for lapply so that functions can be applied to database entries. Version 0.4-1 ------------- * Patch release, changed some internals for the "DB" type databases * Added test database for regression testing in future releases Version 0.4 ----------- * Added name mangling scheme to prevent clobbering on case-insensitive OSes like Windows (thanks to Bill Venables and David Brahm) * Added dumpImage, dumpObjects, dumpDF functions for dumping various things to filehash databases * Added filehashOption() function for setting global options; right now only the default database type can be set * dbLoad and db2env are regular functions now rather than generics/methods. dbLoad's default 'env' is the parent frame now * Added a "filehash" method for 'with' * Added new generic dbUnlink which deletes a database from the disk filehash/inst/doc/0000755000175100001440000000000013071742312013575 5ustar hornikusersfilehash/inst/doc/filehash.Rnw0000644000175100001440000004462313071742312016061 0ustar hornikusers\documentclass{article} %%\VignetteIndexEntry{The filehash Package} %%\VignetteDepends{filehash} \usepackage{charter} \usepackage{courier} \usepackage[noae]{Sweave} \usepackage[margin=1in]{geometry} \usepackage{natbib} \title{Interacting with Data using the \textbf{filehash} Package for R} \author{Roger D. Peng $<$rpeng@jhsph.edu$>$\\\textit{Department of Biostatistics}\\\textit{Johns Hopkins Bloomberg School of Public Health}} \date{} \newcommand{\pkg}{\textbf} \newcommand{\code}{\texttt} \begin{document} \maketitle \begin{abstract} The \pkg{filehash} package for R implements a simple key-value style database where character string keys are associated with data values that are stored on the disk. A simple interface is provided for inserting, retrieving, and deleting data from the database. Utilities are provided that allow \pkg{filehash} databases to be treated much like environments and lists are already used in R. These utilities are provided to encourage interactive and exploratory analysis on large datasets. Three different file formats for representing the database are currently available and new formats can easily be incorporated by third parties for use in the \pkg{filehash} framework. \end{abstract} <>= options(width=60) @ \section{Overview and Motivation} Working with large datasets in R can be cumbersome because of the need to keep objects in physical memory. While many might generally see that as a feature of the system, the need to keep whole objects in memory creates challenges to those who might want to work interactively with large datasets. Here we take a simple definition of ``large dataset'' to be any dataset that cannot be loaded into R as a single R object because of memory limitations. For example, a very large data frame might be too large for all of the columns and rows to be loaded at once. In such a situation, one might load only a subset of the rows or columns, if that is possible. In a key-value database, an arbitrary data object (a ``value'') has a ``key'' associated with it, usually a character string. When one requests the value associated with a particular key, it is the database's job to match up the key with the correct value and return the value to the requester. The most straightforward example of a key-value database in R is the global environment. Every object in R has a name and a value associated with it. When you execute at the R prompt <>= x <- 1 print(x) @ the first line assigns the value 1 to the name/key ``x''. The second line requests the value of ``x'' and prints out 1 to the console. R handles the task of finding the appropriate value for ``x'' by searching through a series of environments, including the namespaces of the packages on the search list. In most cases, R stores the values associated with keys in memory, so that the value of \code{x} in the example above was stored in and retrieved from physical memory. However, the idea of a key-value database can be generalized beyond this particular configuration. For example, as of R 2.0.0, much of the R code for R packages is stored in a lazy-loaded database, where the values are initially stored on disk and loaded into memory on first access~\citep{Rnews:Ripley:2004}. Hence, when R starts up, it uses relatively little memory, while the memory usage increases as more objects are requested. Data could also be stored on other computers (e.g. websites) and retrieved over the network. The general S language concept of a database is described in Chapter 5 of the Green Book~\citep{cham:1998} and earlier in~\cite{cham:1991}. Although the S and R languages have different semantics with respect to how variable names are looked up and bound to values, the general concept of using a key-value database applies to both languages. Duncan Temple Lang has implemented this general database framework for R in the \pkg{RObjectTables} package of Omegahat~\citep{TempleLang:2002}. The \pkg{RObjectTables} package provides an interface for connecting R with arbitrary backend systems, allowing data values to be stored in potentially any format or location. While the package itself does not include a specific implementation, some examples are provided on the package's website. The \pkg{filehash} package provides a full read-write implementation of a key-value database for R. The package does not depend on any external packages (beyond those provided in a standard R installation) or software systems and is written entirely in R, making it readily usable on most platforms. The \pkg{filehash} package can be thought of as a specific implementation of the database concept described in~\cite{cham:1991}, taking a slightly different approach to the problem. Both~\cite{TempleLang:2002} and~\cite{cham:1991} focus on generalizing the notion of ``attach()-ing'' a database in an R/S session so that variable names can be looked up automatically via the search list. The \pkg{filehash} package represents a database as an instance of an S4 class and operates directly on the S4 object via various methods. Key-value databases are sometimes called hash tables and indeed, the name of the package comes from the idea of having a ``file-based hash table''. With \pkg{filehash} the values are stored in a file on the disk rather than in memory. When a user requests the values associated with a key, \pkg{filehash} finds the object on the disk, loads the value into R and returns it to the user. The package offers two formats for storing data on the disk: The values can be stored (1) concatenated together in a single file or (2) separately as a directory of files. \section{Related R packages} There are other packages on CRAN designed specifically to help users work with large datasets. Two packages that come immediately to mind are the \pkg{g.data} package by David Brahm~\citep{brahm:2002} and the \pkg{biglm} package by Thomas Lumley. The \pkg{g.data} package takes advantage of the lazy evaluation mechanism in R via the \code{delayedAssign} function. Briefly, objects are loaded into R as promises to load the actual data associated with an object name. The first time an object is requested, the promise is evaluated and the data are loaded. From then on, the data reside in memory. The mechanism used in \pkg{g.data} is similar to the one used by the lazy-loaded databases described in~\cite{Rnews:Ripley:2004}. The \pkg{biglm} package allows users to fit linear models on datasets that are too large to fit in memory. However, the \pkg{biglm} package does not provide methods for dealing with large datasets in general. The \pkg{filehash} package also draws inspiration from Luke Tierney's experimental \pkg{gdbm} package which implements a key-value database via the GNU dbm (GDBM) library. The use of GDBM creates an external dependence since the GDBM C library has to be compiled on each system. In addition, I encountered a problem where databases created on 32-bit machines could not be transferred to and read on 64-bit machines (and vice versa). However, with the increasing use of 64-bit machines in the future, it seems this problem will eventually go away. The R Special Interest Group on Databases has developed a number of packages that provide an R interface to commonly used relational database management systems (RDBMS) such as MySQL (\pkg{RMySQL}), PostgreSQL (\pkg{RPgSQL}), and Oracle (\pkg{ROracle}). These packages use the S4 classes and generics defined in the \pkg{DBI} package and have the advantage that they offer much better database functionality, inherited via the use of a true database management system. However, this benefit comes with the cost of having to install and use third-party software. While installing an RDBMS may not be an issue---many systems have them pre-installed and the \pkg{RSQLite} package comes bundled with the source for the RDBMS---the need for the RDBMS and knowledge of structured query language (SQL) nevertheless adds some overhead. This overhead may serve as an impediment for users in need of a database for simpler applications. \section{Creating a filehash database} Databases can be created with \pkg{filehash} using the \code{dbCreate} function. The one required argument is the name of the database, which we call here ``mydb''. <>= library(filehash) dbCreate("mydb") db <- dbInit("mydb") @ You can also specify the \code{type} argument which controls how the database is represented on the backend. We will discuss the different backends in further detail later. For now, we use the default backend which is called ``DB1''. Once the database is created, it must be initialized in order to be accessed. The \code{dbInit} function returns an S4 object inheriting from class ``filehash''. Since this is a newly created database, there are no objects in it. \section{Accessing a filehash database} <>= set.seed(100) @ The primary interface to filehash databases consists of the functions \code{dbFetch}, \code{dbInsert}, \code{dbExists}, \code{dbList}, and \code{dbDelete}. These functions are all generic---specific methods exists for each type of database backend. They all take as their first argument an object of class ``filehash''. To insert some data into the database we can simply call \code{dbInsert} <>= dbInsert(db, "a", rnorm(100)) @ Here we have associated with the key ``a'' 100 standard normal random variates. We can retrieve those values with \code{dbFetch}. <>= value <- dbFetch(db, "a") mean(value) @ The function \code{dbList} lists all of the keys that are available in the database, \code{dbExists} tests to see if a given key is in the database, and \code{dbDelete} deletes a key-value pair from the database <>= dbInsert(db, "b", 123) dbDelete(db, "a") dbList(db) dbExists(db, "a") @ While using functions like \code{dbInsert} and \code{dbFetch} is straightforward it can often be easier on the fingers to use standard R subset and accessor functions like \code{\$}, \code{[[}, and \code{[}. Filehash databases have methods for these functions so that objects can be accessed in a more compact manner. Similarly, replacement methods for these functions are also available. The \verb+[+ function can be used to access multiple objects from the database, in which case a list is returned. <>= db$a <- rnorm(100, 1) mean(db$a) mean(db[["a"]]) db$b <- rnorm(100, 2) dbList(db) @ For all of the accessor functions, only character indices are allowed. Numeric indices are caught and an error is given. <>= e <- local({ err <- function(e) e tryCatch(db[[1]], error = err) }) conditionMessage(e) @ Finally, there is method for the \code{with} generic function which operates much like using \code{with} on lists or environments. The following three statements all return the same value. <>= with(db, c(a = mean(a), b = mean(b))) @ When using \code{with}, the values of ``a'' and ``b'' are looked up in the database. <>= sapply(db[c("a", "b")], mean) @ Here, using \code{[} on \code{db} returns a list with the values associated with ``a'' and ``b''. Then \code{sapply} is applied in the usual way on the returned list. <>= unlist(lapply(db, mean)) @ In the last statement we call \code{lapply} directly on the ``filehash'' object. The \pkg{filehash} package defines a method for \code{lapply} that allows the user to apply a function on all the elements of a database directly. The method essentially loops through all the keys in the database, loads each object separately and applies the supplied function to each object. \code{lapply} returns a named list with each element being the result of applying the supplied function to an object in the database. There is an argument \code{keep.names} to the \code{lapply} method which, if set to \code{FALSE}, will drop all the names from the list. <>= dbUnlink(db) rm(list = ls(all = TRUE)) @ \section{Loading filehash databases} <>= set.seed(200) @ An alternative way of working with a filehash database is to load it into an environment and access the element names directly, without having to use any of the accessor functions. The \pkg{filehash} function \code{dbLoad} works much like the standard R \code{load} function except that \code{dbLoad} loads active bindings into a given environment rather than the actual data. The active bindings are created via the \code{makeActiveBinding} function in the \pkg{base} package. \code{dbLoad} takes a filehash database and creates symbols in an environment corresponding to the keys in the database. It then calls \code{makeActiveBinding} to associate with each key a function which loads the data associated with a given key. Conceptually, active bindings are like pointers to the database. After calling \code{dbLoad}, anytime an object with an active binding is accessed the associated function (installed by \code{makeActiveBinding}) loads the data from the database. We can create a simple database to demonstrate the active binding mechanism. <>= dbCreate("testDB") db <- dbInit("testDB") db$x <- rnorm(100) db$y <- runif(100) db$a <- letters dbLoad(db) ls() @ Notice that we appear to have some additional objects in our workspace. However, the values of these objects are not stored in memory---they are stored in the database. When one of the objects is accessed, the value is automatically loaded from the database. <>= mean(y) sort(a) @ If I assign a different value to one of these objects, its associated value is updated in the database via the active binding mechanism. <>= y <- rnorm(100, 2) mean(y) @ If I subsequently remove the database and reload it later, the updated value for ``y'' persists. <>= rm(list = ls()) db <- dbInit("testDB") dbLoad(db) ls() mean(y) @ Perhaps one disadvantage of the active binding approach taken here is that whenever an object is accessed, the data must be reloaded into R. This behavior is distinctly different from the the delayed assignment approach taken in \pkg{g.data} where an object must only be loaded once and then is subsequently in memory. However, when using delayed assignments, if one cycles through all of the objects in the database, one could eventually exhaust the available memory. <>= dbUnlink(db) rm(list = ls(all = TRUE)) @ \section{Other filehash utilities} There are a few other utilities included with the \pkg{filehash} package. Two of the utilities, \code{dumpObjects} and \code{dumpImage}, are analogues of \code{save} and \code{save.image}. Rather than save objects to an R workspace, \code{dumpObjects} saves the given objects to a ``filehash'' database so that in the future, individual objects can be reloaded if desired. Similarly, \code{dumpImage} saves the entire workspace to a ``filehash'' database. The function \code{dumpList} takes a list and creates a ``filehash'' database with values from the list. The list must have a non-empty name for every element in order for \code{dumpList} to succeed. \code{dumpDF} creates a ``filehash'' database from a data frame where each column of the data frame is an element in the database. Essentially, \code{dumpDF} converts the data frame to a list and calls \code{dumpList}. \section{Filehash database backends} Currently, the \pkg{filehash} package can represent databases in two different formats. The default format is called ``DB1'' and it stores the keys and values in a single file. From experience, this format works well overall but can be a little slow to initialize when there are many thousands of keys. Briefly, the ``filehash'' object in R stores a map which associates keys with a byte location in the database file where the corresponding value is stored. Given the byte location, we can \code{seek} to that location in the file and read the data directly. Before reading in the data, a check is made to make sure that the map is up to date. This format depends critically on having a working \code{ftell} at the system level and a crude check is made when trying to initialize a database of this format. The second format is called ``RDS'' and it stores objects as separate files on the disk in a directory with the same name as the database. This format is the most straightforward and simple of the available formats. When a request is made for a specific key, \pkg{filehash} finds the appropriate file in the directory and reads the file into R. The only catch is that on operating systems that use case-insensitive file names, objects whose names differ only in case will collide on the filesystem. To workaround this, object names with capital letters are stored with mangled names on the disk. An advantage of this format is that most of the organizational work is delegated to the filesystem. \section{Extending filehash} The \pkg{filehash} package has a mechanism for developing new backend formats, should the need arise. The function \code{registerFormatDB} can be used to make \pkg{filehash} aware of a new database format that may be implemented in a separate R package or a file. \code{registerFormatDB} takes two arguments: a \code{name} for the new format (like ``DB1'' or ``RDS'') and a list of functions. The list should contain two functions: one function named ``create'' for creating a database, given the database name, and another function named ``initialize'' for initializing the database. In addition, one needs to define methods for \code{dbInsert}, \code{dbFetch}, etc. A list of available backend formats can be obtained via the \code{filehashFormats} function. Upon registering a new backend format, the new format will be listed when \code{filehashFormats} is called. The interface for registering new backend formats is still experimental and could change in the future. \section{Discussion} The \pkg{filehash} package has been designed be useful in both a programming setting and an interactive setting. Its main purpose is to allow for simpler interaction with large datasets where simultaneous access to the full dataset is not needed. While the package may not be optimal for all settings, one goal was to write a simple package in pure R that users to could install with minimal overhead. In the future I hope to add functionality for interacting with databases stored on remote computers and perhaps incorporate a ``real'' database backend. Some work has already begun on developing a backend based on the \pkg{RSQLite} package. \bibliographystyle{alpha} \bibliography{combined} \end{document} filehash/inst/doc/filehash.R0000644000175100001440000000674413071742312015516 0ustar hornikusers### R code from vignette source 'filehash.Rnw' ################################################### ### code chunk number 1: options ################################################### options(width=60) ################################################### ### code chunk number 2: exampleGlobalEnv ################################################### x <- 1 print(x) ################################################### ### code chunk number 3: create ################################################### library(filehash) dbCreate("mydb") db <- dbInit("mydb") ################################################### ### code chunk number 4: setseed1 ################################################### set.seed(100) ################################################### ### code chunk number 5: insert ################################################### dbInsert(db, "a", rnorm(100)) ################################################### ### code chunk number 6: fetch ################################################### value <- dbFetch(db, "a") mean(value) ################################################### ### code chunk number 7: delete ################################################### dbInsert(db, "b", 123) dbDelete(db, "a") dbList(db) dbExists(db, "a") ################################################### ### code chunk number 8: accessors ################################################### db$a <- rnorm(100, 1) mean(db$a) mean(db[["a"]]) db$b <- rnorm(100, 2) dbList(db) ################################################### ### code chunk number 9: characteronly ################################################### e <- local({ err <- function(e) e tryCatch(db[[1]], error = err) }) conditionMessage(e) ################################################### ### code chunk number 10: with ################################################### with(db, c(a = mean(a), b = mean(b))) ################################################### ### code chunk number 11: sapply ################################################### sapply(db[c("a", "b")], mean) ################################################### ### code chunk number 12: lapply ################################################### unlist(lapply(db, mean)) ################################################### ### code chunk number 13: cleanupMyDB ################################################### dbUnlink(db) rm(list = ls(all = TRUE)) ################################################### ### code chunk number 14: setseed2 ################################################### set.seed(200) ################################################### ### code chunk number 15: testDB ################################################### dbCreate("testDB") db <- dbInit("testDB") db$x <- rnorm(100) db$y <- runif(100) db$a <- letters dbLoad(db) ls() ################################################### ### code chunk number 16: accessbinding ################################################### mean(y) sort(a) ################################################### ### code chunk number 17: assignvalue ################################################### y <- rnorm(100, 2) mean(y) ################################################### ### code chunk number 18: removeandload ################################################### rm(list = ls()) db <- dbInit("testDB") dbLoad(db) ls() mean(y) ################################################### ### code chunk number 19: cleanupTestDB ################################################### dbUnlink(db) rm(list = ls(all = TRUE)) filehash/inst/doc/filehash.pdf0000644000175100001440000030411513071742312016057 0ustar hornikusers%PDF-1.5 % 3 0 obj << /Length 3055 /Filter /FlateDecode >> stream xڍˎ[4'%Fo֎c$1 j7>K5B|sk*zN?0ݢX%ag'ȓ0* a<0M72ƺQ!]^R?Sxi]kyo/vӾmjYkt;faN&l*۽vf-wqQwu O+Y. LLzo3iͤY@ܷxΠsݽvɃ^;A\Nfn}x\3%j(h\B|8gFcvMFL9'=JOvmNj) ph ؽJ[ er#}m&ѝ3ƅ:X=`л4)! 5NҸqg1DI`+ƦmƆX>2\1HI&mk/7 *M ЕkSY:gH8-o7\/M$B28O5*4y1 3k3Λ#XVjwl1 ǐ[%'+֔Vr0ة4Pk+ba;SA]=FWF#4El[=SfIDtftpCsrCROR4&LdΞe7&‒YrhNgPܾo~mcuˣ9iQ<8*4rtGm+gd/i'-7G" 4<[FsH' H-|F]N387Z>P:*ǔ׶PX0rfI&':F ` Y!:f1&90hHA\8_$M,4c AC[<b8:hv䘄H$m| ;ǘQb(x_- qRPaW8@IeܼV8Bޚm;fim:؋uB`^|q//_]eE^fpa܌xkƉUxBS$o^-l<2};FeX77[T |WYA++⪊6u-@}B|Uþ5gqo66kGF7!|"Ez4`@Z0O ^44I%ѡBksNVշ*b7N!C ?PFSS䄎 $%)V04 Lp2k32y`V\~eS|s-qPx +ȹѧ{j5b'2q=qS:4z 8EsSNj%'bcQ)JR%EH$yski{;a)Fz׿5PCL2X\+;ڪC}"j'OFOb| *s/k`}Sk0JrаSP}.S{yjn>)H _r#8^Ѝey<E59Q RbipTv-NږnM\'f6#5-r҂TYbdĪHc<喪T(^ǩ/"1v3u:(ohЕD[gPV4;=2Eq9h}[}3v:lgp۾(ƣ߆"xrKBMQ-q諂_Fy_Sqj.;o ,c8c ø&oWU.O7 pi'x&d5RcYPmZ+kQRT䎉E^'yxyQ6!2V8Ehg>`4:ګt_nX$υK5s uߕcA:䐾0D'{$^_ԿiK%.$aF2?,A>]oukFYc|_ Կ_wMB+;dͿS?ARqʖ( |Q(qv{Z 1cGǐR0P?0"ɒcj,jeˬfEzUmƱ5ߺ3x$uR,GO(pC]Ķ4ȄyAcv|mu'wEa7&[2 ^=DxA T ݄ϲco.},@?RPo!Vuġފ13 ~aXX!Ϡ~Wjp8W͞_;" Uד>|Kom0F濳{`iU~A X Bjs$RBKTdxD}~Y'\b"R_eQ Ӈjm.jWb2#J 2aH @8672LNoF~?e _kuJ=ymoH{So5c|硫l(x> stream xڍْ_1sV4T8z YP<8h}o4LwYx_Ey {zwҰCo-ekzvfSfc p#cCej zm~+m-oʺs\t-Mɭ93wJ)Hކ%,fw4Ƨ*8ˀppT0i 2{e\QYN 19۱.W1b9u5 Rz{m;F?ŽQr" 8 Xc%9d͉t%{~ `mN<^Ȝ[O?]ɤ`  +.h@n#)O^SJ0^2 ? yۙ$C۵~͎qv` {j+Km?p{8w7.okSoQ[ annA3~0l)i"tofcB.k"y{"ؾCϺjtM[i-}U>x<qhk8 \,hh{;<(H=Σ)? 腝E29tm蹭3 "]g_2)B~t?M,e~0.flP1ɼ*pi]!w:m \ kG_+[׿U᝽BDӾ{n`k3dUT7n۔ƔoQHtXT&HzKnډkk|/bxJ)M82k}}')X#fW7)? 3GemwUK ^ȈgKVQzߏue&VV"?̲'MB q|򔅄䈨'FcOm66l_s79CZnq2O;T뾔J dʯ{TmߎA s#)8KOaCBL8BuDGhkKsp*Cl81 #&dL@xد\4g#J6s^`v{+Ι/Ll4ZC 0rDX#5A(jdT '5ꏒ\z/CX8d sghB|VU^aXS:AIBT[LAͶ6ҙj2,TO!\DxVB<,MWӃ=gҏGٟ.Ъjˆs'i.AKF80R8[2Ì|r1ƐEɒ7 5ZTē""bG,] 2טy/EGʟLטӗ¿ RaKjʀ* ߕʜlz[bfEš! 0g +r!o*L'R؛yy6p(obǛθzA 5"ˬF Ci]!#k!@z2"L?FsFI tz;]k Gnڱ]TN6n9|y]zX*%78V^֘a!8X ~sZV:Wun9RXJz.DaᥱЍ.Bj~ P%:ר-(8%  80 HVLk!+5o skqTړh:zp`8䲱]+QKGo(8NZЫ@F`]u-9|ߟQ j.׌sX=<ĉx#;3N"[:2Hu8p=:5xF]"\&ˆu!yppK!pLng,֬T]GNR ?'`'af n1>8ÉOS?K,G@ߝP64iAf,.QA^H̲D9O\fhʿk~cQė~!g9xNhAe"N p%{r _qLJYo>|߿\Cc%ڗnR(/܋/C&3 endstream endobj 17 0 obj << /Length 1830 /Filter /FlateDecode >> stream xڵ]o6(5+R0 hP`<2s%e~wVwO6'V͚d%O2V4%^I9L]#GkU#2sF[R{G!`'tLM bK)Ety̖VP8zh=x>Boԡ@+@TJ5JEMIᒕRNTp$]+DDՠ-bydzZ:F'DBkh3wU;=Jw/.GWHɲ|Wi& RmCS2XlC[&e8?˔8HfQKW=L^[ZvG+U\(-Eے2L{ pdO)BP?ۛ%.tgXIF:yv%IInj2l]r _~s4^z!۴vkP7_`o/8J8[u!0t[_X~K+T8C#PBA2D5a( ZpW906gh_ v7{U9g#L|jR.'?-U :m=N8GNT¶'h !7#,Q08h1:HKQy{`C!k*aBzEuãZuT*m5RtHV tKFXBLk_)\ `|x@$aK,-vjiѽMk͖#|AZűz€4|6mNaXҡg8nF˓5IOIwf^ә)uk0~dT;SyⳆ0aX4>g2,C /a[en=#CCB]m֪3u~84w's{QɌbtO$T7߻ }BˡTx?TaL=@!Gi4w\?!O1Jpi' Dih@8uz7ҒGr%lFL2=, M^jY1"Ąݵo^Vwi@d5;/9_^[~8EvAAd ΔO4FJewɑB[CC/zLG^+νJ,1 kwӇ:C+iC%yzj24-w#krFM\z3#L\셭c㒟-kh77/l/Z=k!?6WN"M{CN?!nv}տ9 endstream endobj 20 0 obj << /Length 2248 /Filter /FlateDecode >> stream xYK ϯhڒ3d6 af*[.خ4"%?=,QEHJOUzU"U&8ʮn7i4 5;hy,0xK[u]6O4<ޔeƺj2zm%nJ%_/' 44ȓ7=W>E$db[?zf 䇞d(YyOߦ+M)Jn'C~9͛gZwf,8:|=_IK8߳>l*7Oޜ{5pp!R ыS"w I1;[J!#NT!tJ}" Zi8!5j\38Fċ;@Eck2"ܽUX^)-WޓZDiteeI扑F+YʃmE~Dj3  8uxDMQyыŪm9:Dwke?H6h#d<a)(Shl\QlellhHp5ἡ /qGvmDI(/Bg&'/ƪ;KgGՉfN9P{r3rсm|v|wG\>18K/KMA"Mg6?Zi1pVsA6ؑSG<7VXW*k_W}w^!jt!cIS骺"VuMM&p#1]!x]{~x!IQ }( <7[3 ;8 #@dC H;u11EV*=ģ )\S¼G̚SJ bG|TgXg-Dy Aa‰J-\F[ ߱ƹ"L_Q*Dӗdv< .?_7EAPhJ;.qǔG>M8{ B`{~A<9`qOhK$TPCObv6N83K[W`KmZ[QW; 0:ƭdo]"O#A^3 n2!GQu]ؼvE*vɟMJah賈dj:<]sJo_.7.8wmЉkatI6nS:,?eDŻ_0ΜxhQ$ x/[xX!n˹AԆ 0/}8"#9>IfuepԚ_O#33&5wݻ(sY>&ܹhG ;g4n'von }:_m?y2ȧW-LpPoBkV$QX*@#!F'` @ 1`sq'3_`gH8"Wu )ii'gk S УۋuvPrU~E(dpqi}:7+B%=A0af#-!G_;{6ʱ(  ]~W endstream endobj 23 0 obj << /Length 1896 /Filter /FlateDecode >> stream xڵXK6ϯh4rpSZK~LbRA3sP٪.e=~IQ.cd("?R~_?MƲX7M"n3?Hnvƾni3ȇ͖,GŞ޺]~hβh/smݴj8^.>/>Yhyz%1CTDJ &6a"㳰10_>8OȞyrvMk@>SIJ$}. 1fAG;?#*G&nβ#gZO#:GmzZtw0M;ȮSAihI|6UfѰ(!84ݧɵ1(ZN4Qʆ]42 ]ӠRQֺ^[b0&`If/7*>ݰ_ܡ9 h$"|{ (p50_* v5 J@.a]V+W7?ZW_@enSQ0w"Bҝ,d{e4C Hd̏4XHqZ+o(9PnM@ Bo ]Tzncg&A0s%&akww4ݬVItdjO)Cq vqh6P5Yg3!x zɧ} m ,urqi(f=3@0c'hK|t?sH55VyfsEGV!fAۘmÃcyxl1DPX:᥋Kwv> x4?iOCvmYZуU16f6 F7CY4T_ԥܗʕùL<@~a,,^!#H.~}>(!#^PziQЄr$sPD4 V3)98z( , - }L(AB'6IOkaqnAlLbNN L"bMqƒ޻ Zk\JLRK^lͽy`NwW*@_ۈȝГFj~f8]2 \C{IavB}1lwi5u{OvY҂x Y,.8Cͫ7Y|c!8}Y"&hAkb*,; "zg 89YKrY`C̎W=km7 ixo+$uɺ5lJ9'y&„Oe\x0j@,MSpͻݛ endstream endobj 26 0 obj << /Length 3213 /Filter /FlateDecode >> stream xڕYKsWPU;\ܛey]벝D+2Ӎn1b\ƫя1w_7" T7<ɣ汾5ow"(Ӎ2..EP+{Rճks)RnvqB}sGV$i&y`ͫ*)¨,`[($E楟zNيR=6`ԧQ8yCݖg~i~ʚvFZչ[ MTYٕeN2+;՘Z;Kh[wn\Qs oNSrqqDֿx'%(iyI,h_u^|lZNw `SOtL7\e}k[յh}i4oNqT+ͳp6 {@,I;TLvddn=lpt9nz%D"NԿ4mM3T~*]Z?OVS*eۡadž;fD2I&A5?`#DrJP ф8}; f'򼾣M_ibQa*3F _eGƙhTޏ^!H$߁ƨU}%[] Rbmh\HfBͥܩp. 36=MYH<pH'MnB96b{ݲt8 Bƙ)`7]@.po1lD8tY} S̫HN莚t3xvz&+)>^ȭ-8fW%%*-6Y0+A)B45x4]p% et54׃m\\CgU@P~e˫m dQwR|89z<<z0R1RRR(tVG׊[RI^!i6]1$ݬVcdCcaE5G5d7N= l8/DJ;P_Hx Nx쵚nNjQ!'pz D!â #[L(R iNcKF EH9,f/ZvN< a;N+wOQ/(@H]SdsiD@p &>4z d M Dq`ҥ޲L%GR3h@>C:Z%,N(Tm q8;>F4Y9$/$A̜ZN[G%`  ;PXMw?0C@4=Tz[KZd @TKyҏշ8h-Pu}u 5GGrM]x\RpByJ\I}ՌpS]NURQ}i(7ER"1yj(uՀ̑QcN[\M1: T,݆[Et%^›]Lzr-b{F}hFƏ ME*69&Bblb8VefiwQ]ȉgp.T+?3a{^')QXb9؎PʒSrQiEgJMQ&y׮BPЂ *qx8#Fea18 Y9*{/Ԕ҄b>tQݧE]8kxƝc2X!hG.] &=Q׆$NTV~MPk1Ŀ^_rQ8+V{H` PHȌ9>M8QxOf XyU7t܌cs?b20e|ꉬuk^qˬA GmVES>yXLVy6~2x!}$¼f_8se\!aN;Co8OhLUDց$('O؈#qIHo9!ϧiG'2 &+Q/XȻ+{NڌpzȓP_@20T#U'Y6(7M@1{Uz]l˟)Ĭ!Ĉ\1EG3l8L ]iwQoB\}k1xkKpCeZ = w ڴriD>h-|F]EiQ|%pKGU78,"+'pԣ-:wN(IN$3sФ4 *30v\1@i;_,isgU3҆۞ ˣkF?kHHDwAi;ߓ5QQ1'%?n^:HΚe<}]_s7_0gg~H Lȗǔ0 Y쐴9tJ"bhcU?pY4䷴'"5P/ϔsB9oGlST?)TB\pR%lIA!d5dq.XYfuol8>G' q8x1c xp^k!`>/#cr [H Iha1t ʿލ*H^Zې0ܸu^-x&e^DS@+~җM֢0m1V )bו>>iN1dqA.*tIoU q? ~ + M8$;_23=>X[e`o4 ~[n[( WF 3WȂ1ZUQ{56|cw2MnPh,<Ya>P_V#ɬ6 2LɤN"CAq&wgzzLќ1 oq[YO@Ix&UA] zSwcX{۞uS:=U kptyY1v8Sys>%| (v>KLOvRW*B$H> stream xڝueT۲-=ݵ%8wwwww@p ξyGUUsZMFL+hlkqec'#vmmDN .2 g`01q13q1199?BNNw;@hasw075s;tm6vΆVf c%1 ̍MA4YA$A++H#dL71hm ss09NPS˫*ɊʩDU$UDEJ*>*fG['WQ AĠ:h>̍n%`lhdeQ nr(/XGgC GE:99Gd#4uF1A%+;-#'҈ @|́\@6Z}#P_U:o^s~ 1;#_%+ @ `edTO/vkN@#'[zk>ZW`d;8:? hDo~4 12c5CC9ᇻ <_:1pcibck>(ƶ6Vc <n¿?Ŝ %lt dke?V:t~hICL :2w3w+;LV\`enRu4{_h8aXL#K#_p#9-BWrR8}c1w`. X@76?tA*jcdklnc!: ٺ몟Z,&HT+&|(){Styكf&H+ Κ7'nbmCFݫk4l0fg >xLєr D;NVZ c}W꯹ { m/(35Eiq~]PE+1#3&B|g:zFf1yM7F2,|iݢpgoFb"Œ㺊]N#rVwŒ*3~UҙO7Uth8‚=J7ob¤TgIhּ@-i4A>,fXrdkUHׅ%R=[/|Rw|Kܯ1}nN+tz·`6 sAм]RӃK$Ye,FS<#^E=Wo 5iE撝qK̙5TޏJAH8.Ko,K~X ᬥbl!"6"`^3H Q4&ev6$[f?3s{qm(v6 {̪/*@PA엳;^EI_ZTh׍Q;>S*Hoɩ1!ۭCH%Ķq)H+^=.4,p+fMK¿U!FнٴhW+핊P#a`Id[}&q̈́m+s&5m !$&Iҍ%;29ru?iR> 7.0b |܆Tz$ўER̓W;"Yd  Hkj{5N:5 JG>-#U)ROdq?C@ v/й 3`-JzZ3a־:YjT1~='ʕOedq⃪s3Y_5ՆD\j}EWKǞOm`5W~c!8Eq<-itY АǕ3M '-s,XrrkQ0vrZJf}<ύ_ƄJPpk~2wu(" 2ͧV])69U R䯴rZm00p>H[ %mM#4mT[@:)=FTK]/Kj+9M3cw&;}SKڭi’+TȯL)f"Md=.! I NO;tGKSՓ#´ǷxQ<''\%N$!ZDQ` -j)JoНҠ%|/\/ߺJ'xn'F㉧͎U< ;>LVPcਸiBpf4Ќ3[KiX*I?8^ L'w_>.62R ·}b>߉^LS^kJ%55ma!o?ksZC5V(zײh5߳Y%d*lea KC!&Pby"JӍ V'₂=2_/ i YDi[NK4Ϯgi{D9S9Op5p>{7׭-jU^֎t$&ȍw%Q@30QDF>QKxjzujz7` !3Bư ghqRGt v7p ̅^ IFw*S5e巁wԤIf\6sM刁%xS=S;<W%M7a*|6߿+:yG^ѣ EoU3|ٌ򓱓wVW^OMF0<#x ɚDV!ĽP^ΣP6ohQ.'')jM.^\%KX1VJܯ(E/ 0,|:Y rxYi3 dvA&%|o܀$KJiVq_T2ä%#yWrעo8aB{Pa'3!{7xM7i.Z33g%x1is{ !kqyÞ,QiŠ&_4pAzBsΘʬN 3+ qN:Pi/1_?'Դw$Ęrd%tdֻ[Ҷ81_~&bpW$X-oF&o]JԾf>}Е9I+ ݷl GZ%[E"f7[z RyrSOAOՓ_*Vu{ dњ;(j>5m7wo4"7#uksݢj k2nectࠋ0E<>NMy|5Z~ElIty# -cmwpBbL68NfGq<{=~N 6עg4բXYQD$/6 &ulr`,  dBC8oUb_1)zO^ hccV4cwe)~KF@͓KᄉmDe͟Ro80Qb=*U-ߓ21b/\{,Z `gEث7lRGȝ] O!C_խQDD_s1;GuU3bp,Fӱ;~,7{L5Q/8ä`F#qkԴμ=\ WM!K/Ig䋷 hć@ƥ8<޶p#d,+hHF: YVF<uA_`&4Y ^)QeQMGQne_Ч)w*qKo x H($J2uh,_AV'١}hIP$6&qr,ʫ:1+C@2g$ЕmR/m42=`[y`ժNq|x: +dnx9 .'AH̒LHl"whb"ρH"*.W( 1|$tl Ũ':9n Ԓl~le-|ʊb-t.FE~$'{ Ӽp~} 1G?Py8',#cyY1H0uu4vըO7(ӻV~gi~!bB 5R-6dqVv8CFJP*RS'D,_]V ,H tZ*#8B]Y٘ ʘ/<"JnE*awzm|gn5fC7-`6uh>1؅kʓ&IRڏX!սTH*LSr!JH!x5tQ7d%DޢX@@QZY!h}"~;Ϗ :Vi>5uwTع䇂/E?rWZo,{n:(byK9Kq렞#|! ;ҔO_ΪKIInagWJ6͜+[QV>2'+N牰>z4V8F©ő4Z#<-S7IyH24Cąat<~B6rE4Һ,LuKXFYEQnn L5ҥ c^6607߀xu4>*f2kc2|"3#LY,VэАtj<>0˗`HNa;4SZN."؏VS sJF蹒C5 vG2 RA2Z6| Ngͷ1IJvBZ{w8R|>r?NCʬlIKkj/Q{(SC}Pǣ0ڦ(`zCp*_*\`ocNoԬ+~ҼN)6t6 !'@1NHgHgv<HeN䙮w$߇=>~47׹!4/, 9q~vΤnNPAN̊i7 +@^N?_OCƇO$aߣ/a}Ȕ,|jVr~1gohgMe0]*`u0X{- 4== 2Z= {/TxN5=#Q ?3ʭWP|,W7LE^}ڳj/?}[bˮժ-=^}u^u̒/z?(d"]H'u2 T|@w<]yK9gƴbFz2ЯW^W.J訏E֎ʯA q݆Nl#A4tmjܶW# B#as7Su~u-* a9iyNj 3jCXrɾB;aKc*HQ=lV0{X*L(Cfx A٬|&&+"ZfV/rPlxi@wm'$mLΛҕ+qiH& x-uB˔ʭ(z5ՐA'8]!x,.pCwA+#ACi9.( 9'dl Pg;o58O}> xc!?-օ Y$$yFԴ6QCemƞ_ ߄D#vA ߰b+k}09c~AhnqXЭA,&?❊׻_}8om)esnWy_Dtr\ŕmgV-$tDj8F1! 儂`3~ o~^MLdBՈGG""$< /IL1T` r߉rԾ%dU*Wq<1iQvNe!oM6J$ytDjHA\N6 Fs8xZ@mAFˎ9YS:kQlΤL1L:λX'oWFix_%H-w]BU_Ik&y-(욐6|3@mUPRƍ^0ɤAdՠ-t2t7r΂w6-gI>*qoǛ6s4S{eiJ28tU]= l3b9vR>'m_]PEלf!ѩ*whU3u`C1C.9/&,XX|%Ds~ra甯չ3.7Raq#T"ف>)VggjC#oG9Iϸ@1M߈fd"$ncc8=0Kˬ(4@맠i5q1[x m|I9|}UM . B)PڄS;[߬n珤!ThΓZm]so&$6O|k5=* /O#Mdѹ y0'Һ#Y2'XIu~H7#d Z $O"/3~WuO.K֪Y~D7<cT<\^M^>~Tr[>˻hf^ccPD =\S| iQ?m .)xXDܢb ް<ǐ^SPj|>1Xm PDĹ=8O[4(V7wkb sLŠa`82yDPlKLv^6V%MݨIw`|o'NRa8oY[,Nq 5d`Q(xwQ!t<{M.\7xj5t~ū!Z6 U4Gm*m(\W8^K;D-bSK=!dYME9>(8#WgvezEɗne@j-=ymx4 fE!%۩0Gdٜq؋/}I:2t(,lnjXi.(Ύ}$}=D>tSzQx5*s*9{DJq h*MD"ax٩A)Pj n~LR皑Hoa> stream xڜuPݷ- \kN.!܃\w}η֭ZϚs1eUO5%*1PȌ@I)4Yۉ 'K+|9>}mj@  ]3bblcl4;Y=LDl,Ḿ_"H"66N@g+Д_;3KSdd 9y@y@Q ! /PWUSUHKJ~YX:@nFN@333#?Ц.& sI%`;~"8X[J3g i tLXgc+'gI,M9 Y9g `fgiOd3`d O>"*VFVV. -+o\f9g?lWD7((/˧׿HS)Z)'+ gOl%D~4ddwb5sltNT?O, K'g@̬,쬌C8F6t>If 5Tg}Kۙxkml8~RxLfL h?Is.66 FJ3&ill* 2kYhd 2|}u;SPaa`aTX,j?oL2*+oj *//%#K;п9,o9~fFffOUSxhd77ڷo/X8,,H-Oǹ\o#cfï|ᏻra| A8MU?6 ;?|zһhB^6܏9cHf63*0 rԕB$L0 ry !s r#MDh~H6Ю]k)u4%k!RYeKK>Tr}X˒ )BCikSoKuL%R&a3eCUQnXIu& LjjZ鎻 IWil4snoY9O vuиk"./.j"{N,Y=Lj9^kʌ̖4 -V}Vi cnpFr'\qR{>O)1׳KodPYG42Lf'1QNC TSы}iV~mPŭ ѱb\EڪGfX6Mw=0Q6 Cꤳ#&(prX{.{SK+/(_Tj@P|Xz^ξvWqonhӝe9-hhlftSa?H*isejA0ļ6 'UǎQ^ElP8IW/caבz E2#{~."]ʥd"#MfQ>PM5W\)- OXptiDQ=&6i1/C cXE H:p# |#<$>Xo<4NsYB!?4]fC>dh{\'͂wWNoa*tou5ɮY(GA[j'$u!{D.䡓MsPw*VW[1aZZi|u=)*8õJ >$鵚:] 1$"W}\ tNg.mޛWuYV2ҍeJP8MVU)_]qt4& |`/t&+M*`xJ_gK u5@2:b`r%@O4cZ~t`Ulxz/+JtlEqvdPu=2Uc0\75R ܫ`d}1/۲UV1"pzI/p]Ҝ;žƉt1z*aѣ)l*my[!O|{^ }c^UT0ŽSg-G1׶}{6$e뾊hi6 S&"H)] p-hTeI_]Yw^+n)/Jj(t%?"KqXzEcDN#rNt&b%rT^O5g岬)Yrvr#a)zjkTL}=, AU$dIGLj^-ĭ|,_m콳jq^ H5EGۡ5+Ioum/cSv>J*xQOc͚[jvY)G2cZ-t \\SF+i28fr?ɔNzMY#n̾d?4k@jRqL^PBYw"paϊpꔿ7su(0'\LO#]p(y{Nsk@ӏW h g ?uk"i)ý0z]W)~AOM ӣL:Wwمd|o϶K2Ä~&ӒW R2Ӊz,tzg~GRO^xo/0q_H|rfhCs!^|Pp?\#M ~7&Z+Gy;yRϐ ܟ]?=WN`ԝgҭ]hvzgZUq&mQ~!3[{!#rgpD@~PoIP0ѹć=|9G. lrgiT:ѓFBF켤%izB$NlЇ̊-SRrYB~%ꜷs&K] lTAB]픽d1ϩ4j0N6#S^0ߛ'ȅ)լ3u'F!+0^bU H´g9Zz)Sݼ^meM9d7 >挺/.;]}5z?8* ܧj]QKѹ{D9R~nڝp7bL _}oM)30XLf ) 1rw?}j՚k'+LT)4* Jͬ)Q_5\qG?~RoUfA$€FT=V^ F|Ƞؗ3wۅ/_^#FĤ% `㻚t:T*b{dhץتG6}~=%qKhw|璋\Ryab;mHL_og(d%y_\T{"w!tg2fft Ӎ%L ރA0NgkʩIRmމ|!ԖxU7|tA2ڲUlnGo&Jx5~{Sx}AuQGކݷ# VYeWβ' AWIJ[4RmPqK"~fŧ5pz.p+OX#ԝ|o&3P?}y4*s\ T߇"A5CO !&1h >޲KcHK:Bғet֕&]E{2h%$kEfn nv>و E֞Dq'W[-or{%jq1^"mƱSHr{I.~5l] _aZa!z8չfƝSlNR*z8]L1:ʇ&K3#Kq]%ܷAlqr0T'KDZCUjZP%wi܀7R{Kŗ->6W"B}(w7Ļ_g^` `n4]mH/, N3l4MgDh P 7g#V>8[0r'0qBKHDJD݂R,X#XrOMJBP ҃L= JU,!6BFj7Wwִ`;c{eih֒ qu-W'9 E]=dM:j!R84fnr =A}0W!S8Dzɩ}xKHi9o(}YWf\ 9g>~a|| AVҙK2 )k[sK? "^bGX^]r;_pCz*MpE*XKJbs*|G D\XVN͵AVuѶ1"T]8^΍W'@cb_["ޕA[M̑xrc|i'dL_0_K]֣5kSJ8Ko> ѕ m?'ts;+ֹ߰ڌ([B^ǐuwi)rU qcr-3nX/7#;n.qK`^7g̚[QөGC65jC"5m\R3=2o$Y[.L5pI6Xr $Ճo|[` !G6C#etE 7'K~X=V qC]Yέ,E8UK`|DQ!fQ^a7X(HOwtk uCp])߂#:Gܷ,u!K)R.;KFLuWAYELk%_DS{ճkC#YΒQ5;ڐxJDc wŐ83 1׀z BR~-.L JT VJa|S$2yx|Zg|EK[wgG-_P0 %m 2Ո2Ũjy6ikM+Z%m#=ږD[OM<ۋ|0>*wI=P"Ь܃^{ nq[7"~`,jj'DD4]qs +}2?X$4ܽU'dS$4c[Y0G#'a,T¸CܷY'E>+Tp,[EO5 y!<y+x@;LV_nH4T(A YlČMP%p^KQ+82xNƚ'(4 &]<|QD?r"Luuk8mZxrZq6S(>^R}r1f203*iY+~sXdQ/?5<%9 &4SL彵$g0/ڬed+1fv9V4>5Y2X%|X 24\)&!Mi1!]d]Q}CFyM< '~ (>%% =p69w7S5fY0slW +@w(7"L! (XV(R#-Z'>/~ڋ %^XGyFJҡ@-?X,d~/JzD}ްX JU}}X/:)l0ql~ f6-dI vgYf  YmFXrXb\A@OIʝTMI Q̄M7'|?-џHd0+ŗ#(f,$e}#Gߧz%4R cKD]ux߽l@4SOmHͺg3-֗[>>i{Eܳ_G =1" S)EYʮ۱_%]fyeP.NDOft:Ua iͥ?2uoQzVzUz1 @Ȃq8cGf{K7OCi*!xeBNY,F6 BڨHb>F\ <|GJgym_ILN!Z'>&SuU}{>uy/%G 6ܦ`>*l߮[.yo+2tHbcEMٙ`7/%cLMѦٽYB r L NKE0q2P$n7^f-(OG >t #j"⦸Y= kW+Ф%[D7xwHmqh*y-0Έ"X -]4v&䛿 M=a"yu4wM}¯T~& 3ZGK]zONKC`ˠ78KNf}?C o2wǷQDsA!x")WjXΔ%ת]H+7Rek W$g 'w^rO. S6t]E+K@ކc}z7j L#Sey+Rs`yBnʫ$.RG3QEv! rWdrYY&ǍYP5L퀰ESDQO+iZ^8T9Vpw͋>j{9n ZGtF4#Zqq*qdsdŎ+tB Qֆf'Ѩ`@>xs_5{Yx~ʶ2Νon-XdY4 z-;]&梌/5Q0us;w,=o?uv^(ZƋXJل@1H=ov*r n\%,)Ä.\y g߻fܐ3vD˸;1+ØPoxMRs٥)U~<_ťPJJ3yh.!З/ $ɒ#zg9%G(ҏ<22\0䀃*0OVNu N݈Zg:ft+tNHYG]$sljR1?p*Wkg˩[Ylqy+ew 9E?%(SᐧKϤBDnժ6ҖF'-U3U5X*7Z$T(zF+9Kv-|qҬ]ֻ(? u@h uD 5X_p22SyPM2FtPIe MGf>U洀7$#ߐs3H%2k9mAbMf, ]10ͧ:eߒ?Z7G!ֳ3>WS#*͍y!q)w6M7)6864D{ݺe?i~]] v]$%[e?q|8O}8Z߁TФI.NhΛnJe C5D ,0r`&NӮ"!?V- {cyf?}9nE],7N;`_I7n slTC=ZH!ETucį+'=Ma5w8v_,@ J PX1QE W,knQ _:36%EՊiVM68 R}G;>pgeᑥ{Wz.`~aMqY&eA#) U~]faXH;Ƭ];# 9ɜ_T @=t)q?f~|AR"0F3O3SN:GIj1tSq ۺ[ϯ:ǧ^bC,5k?VLw>FzHXZW] )5A$roq?s) (wC=Zpw1%vɌw3I_׉oA&+ftd [ntjBBMqDޒHO[>gAK7g3;xMS踝>n蠳1p 'dz>R3޽^mCN\فzsn/|% ubQ3?pi"pökxn)xWE·qLsijaw}JP}5+ q^:j/]ڱl_₿ csF{ȱ %kCSo~/HzchBԬEUx@sKddЛ;FY6~NfnDLox$$R;6wO5#5!y%>]3Ь@*mD8J)c#-7zҼU2tW3׋瓁@|H CyP fH.65V u)"Yjm㽊w3$Hh(iu"t 7 J2S?QDnq[,ᛦ| ~_.sm<6jmF3T>\$/Y`{'wfCBf%E4"ߐqz,@ ZE&nD,zI`$m$ᅭak$}qv1Kx\4 蹡' <]O|m|JUl\VTǫnɚک:Wtn$5g[K~A}} |eX ^>߳zP%_6F [Qv0,pݢBl9}ly3!dBڴlPv|͡G<zeo p}F @0ӄ-O[:Ό|SH^_waF ۫D]R2{pcsN$޻WO۬U:sT Q}IṦa{N23W&p)=};P 'Un2V{{VƲL ICEEl?L߈ɇE} ::Ѧ S72М8Xѱn|6҉xiast;{P96nI7 Fv[w,8; Fz&%R%ӺPng=TH+ /RϓUJ4v9Zt/S@7W6}q)utngFFwaRZ^ Dqe"u v{bVVrwq2xwm䜦K,UŌ{>m# "9T&fNvFieA/K32lۭ_)_"iF^l6{ZWeAP)!ib8_{m3| E]a z&%\fjl~'qJ󓛇Bf M-S/;!-ssN?u_CyEN3L֔tjQKf? `~1lխ$rɏӔ13<' q:L̰; 96v:G%T4v3whI* ¯zCģk/gp^}mzlQpJ0%i/)Y۬8_g<>gN9jțو_ƴ+!\+(фDLfQĔqQLw Rj~t9iT—!j60RY~X̄}Hn\4Ռi5 h*< !c!(C+( E]w0#TbK7OI&NX="||w'[Dg٭)-ot{v)R(I|>02"^xy|$A9jW}@CbrөȻN"|Dk?PɈlkإ tvnW@ic;L 毀Ĥ6*UUsdv4h` Z0v*zƞܬ0-yNM(,u@x1J*NcR7s c׿@T*5"d^b/V g%֘{!sMbT5,3a+e:Q'P7,s*I+v8*D3 uXTΣ\ 5 ZF~# /KĶxHHFYld[P,BI'ޢE)NA_sd[tdMl.ݿ$l$~K%Š\MDlC|s=|wYe ,*AbAh[ͻ?[MR(YKHG=q&)spgJOz $rL qºӰu 2@eZAD"Zf㭃~:,m:9h%,PM)3a/\+{C R?7XD >eds29F &W,6$!#5~sk*nj?X܊=#(51 s߉@.Lh=)oMMq)A$f,sNw|NvJCI0gJiX8-*Yh6MnKލ<;[g֒s@@k_A?8-.mR Wu@; *bOsCU,8.,lz=\yLu-i eۦh!~|]*@:ٚG%28]1n9{7)_C ߯uԝ /y?b) Vuڏ"OhU7-4 z`{YY8tKs%'QQl\e5Ua@}5&ÀWv1JT*9 4o]g}+S\Ss7SND/.:{VgMcoavl%+Ӽ6+u͟bfC CB=kbIJ\z-LÚ`FYRg 4[<%6Jy9"6\uN/s>5+gFl|0^AYW!#RLfk"r KO%[>2"}_셜q]Edڔ!Ήڣ;QhGpPZQȣl!Wr[b!C_&kd d wm@hM'\ Ǩ"a|O&"զCVtbm=!1}죙?COƛxM\]΂@w}d'Ksq/^/ ivs],}7=ĉ1"gq {~F#&e:.DK4؃q>߿KV*sx쓟K~`Fo`+cU'1їy`2c3a-aVwzm<~$w2 ٌ]V]Kt2nHz#1«jgNQU6;:ꣳ^$ac`ExѕT]*5!{'RHtf~ÿW[xU/⤤k 0eg˄!zd$ HPH:p>^5s:b\顀˰C̈́)X cUUoA~eGvo>#IVC/KX6FK~B (Aₒ"Ж~dpQy_::;!G̚q&jwMi$!A!r+Rs TY<ϐotuGRڞhQ\mRaJI) )7%=iφ7N ;.# *v@f晔.N+$>kUjۓߎ=SJ8c0uXZ?"ZCLV:m?˯QUh[i;aiiD#iL{ݞXwH #B~xyԌ@k $SNP}k s-W"l'ª̃w g Q㍂u8ṃ.R,*JJ+uBwWmj g堾cP#cEz f!2#sX\ 'fOH f]A'9V/Fv4A4#V8:Tq .)쐥5,̀Jgr]s )r^ Q-9-:Ê-߁K=HEZ/AoMgё\E[.:E8oPi-|?Y#iH=,k"=杚ry\&Xl<9yfkΣp'2,0;\vk*.Țk(ZZ\o0(RBitWSsTܗ`\syXH˃ea߄`Q$+ز2Bro*@q anp`% 0*jU=`;% kW0:BꌰoN$`@H+FZdJ?FdfH%d$QrZeOKPXmFm1ai1EF?t͔ce1Hf ?Ѝhֳy{ HFE qha'-T@nٍL(:N;{\dZ)WFz9fH@EnS߸L -i3%%Lv r v੐)KجzHW7Ҳ:ضRO R c_' [zj-J}rM>PXs!fф-ؖnT#`Ld UGL 8 _. gP n:{mG hW#\t-_@UE,u<{>!wF%F J6t@sR%p;4,9+\D'e$JNz#K<.h_3\v-H)8s¨K$v3Ʈ8\*Ѿ}Yvro(9Fx=͏ƴ /lN,jjN*n┥+^ݤQNu4G."`[gcjJӤD>xsq۪;kyַ3(ۯ[I])J{u <G"bGh֕yԑSYHw endstream endobj 38 0 obj << /Length1 1468 /Length2 12434 /Length3 0 /Length 13443 /Filter /FlateDecode >> stream xڝeT\k-\!ݵƽqwM.!;>>ݣX뫪9jV12!HƑTt4:b9[g dv|ف0 @w 89Z@[KHƈ 6t076dޑ# 0dL71hm ss09}PS˫*ɊʩDU$UDEJ*>*f`8Wf4mdx7:Ӿoh1`;߭:FV`w@q[g{ -@ `nW$s`#wd :@CՀw@aD `zhu`+53 hw~V}~/>J_lM6ȋvz?*(𝕑"o/vwG@#G[zkӻsS`d;fD?fhSs#]w2c5}"02.ỻ _s.icb ]4 O]Lt ; ` 2Au|@?ߌbNVVr@w6:W t"Vn;Bho?6ގ?s+X `zUmAV6 [ߛC/Yڀ`&w^ZDQEQ?R'6;?f, |_FW6{# MHAebf{o ; B998+|&\AFsFT*( WV7 C!ć*ʹhV!PY!{q_6p8B|QZp Ţj(f\{ٷ?a$(}s!-RI逭I6>1z8=~^lXVN3_{[~/o z귥z+!dϳ]_/--9JZ GO,ךOIU=Q^z.y["g|.A-q)WV펈5C:;1׫pˆj*d|EDwIԿDCxW+Sb(>(yG$s1њ,z D`OYğW x,a`_uK]}M4;~Q+8[ ^ ?O_i LL UV&~U &Î|7F[ДeYr㇦grMNB樷xR). .%L/iWyfUk0 WL[f5jeRu3Cj(RhM䎑0_M4FXc'iQA9!#sr!ާ…ݕQn[l*Q˴jQ2̱=uא"̂zVSs꓆ w"$X+~,KF+'^!,_wGXa_Sr yi>YŒ6zB5'!/v 3=9 p&>.p5(M ~=b2,7 $1d$xRY K,-/n1p,-7B!>gʒ/\[6Tw)l!FmlV ԵNCrUR =3J ~xS9nD?ξYbi1m_ٯ+>"4Emlr[w)KRۯo3M [̵Qʣ-;ZeRRwX݃L$FNRj~N&y2[*.b-7ּwגUuD@}ܰ2:&ד=ԁ#G9~e\x\fk<Kirmo{Z 7[4a6iVI\Z1mb2}FsTiZ̅`; 2SN |B.Q$Ϻ Zެ/rdڗX@[Hmat7GNu+֓?%,Q;5얥@ JÍ+`ɿAԖRd(`Wh\a6wW7'b[ԭm^egaN8(d'=Z;}9_Or])s8>̱v.0yrkHM:*>> [Z1h1mdb9"2t붾j&Ts4Z2 JϽnMIU"tj$RLɭq4w_bj!Yҽll jMGw4Ҙ*iY%#P$?c@E;93__KׇsײYߖ?2,KhA"r*F2J,xTR\K>2M#ń îl],̂1Hnw}l9ʊ;Щ9LσH8IER%p'g.BH@h+I^aTgWio>9$[z2%RoLt,fJLrc9) Bdy,MdZd嬌g{d}}DgH1n)7π-Cv5@<2.֯ y\ Qy,"qII r<\fs;~ޅj/˗Ό M%?$ͷI(Sl?‰J9FGJۚrN -B_(C<>zW]W'h%!M&&֊nݪEm<d"hrVr_V:}n z6Z>X^, LSG8l?"vgbawH36iQ|A:P[o7E0ۚjCe¢5x#zMx=pumʋ뵳*B)bW+0WJ4@X+ s-:f{q7W6!& u=iи!j8!tm$Jo|JYYR{rnz Ϳp>u9fN*?o7>oM} ynoXahNy;oA* .?"m >hۺuq:;P $ON45u3?=Lſ5Ҵ3,ܬH5&.a)f:a@UDi =V:&:&6(V9o#BG\Wxq|f4g c@0O؄{\'Ӕ,˱+F}-,uK2/.҈] mAeYȈ+LG8TȡZu;}] {GJ?lC·R~'~ Eth"h}Q.zУnțC 7Od*iFQ5 A DɀqL|Xȍ\sDhI@|&}ܠA_Q'GkWu Q(3ixt[lF+JWkYӛ04[a̞[O15KPw y᳤lrs<|A])(HP챀)+܆/hWօ,Y&83nsm;G+ċ/:uߊD! `^c$h^v~3E͎Ę9 ÐM<,J&]fhg 2MC*Bw/@0v?\lZv093 IR;,PzM#F nj5C\E$MK2 >QڜXn _6lԭ#(r0UXpJN;N$LԥE/ H9H88m&[GmH[$gA_'[< `-䖗r]`:f׮Dxɹԡx nlKy<Ȥ&Kkvٵ$Sc m/fv{DS.ٿNzhleRlsuD_!֙'2N/Aedj+D^܊#_)qW}TտSEj=c йN™C\Hb7HDL-;C(Un^A‡n{SfhěN}xz? BvK@@G!$ұ,Tt6^NMrhwYnT[|.$ iGQTRzyWwxŹ)OQ=`"Z졏)8bٳ%c%0B(_ f\&3TW6 x7y F[65B*i7P!=q$_ߟ`?Mgnx9^2Fs!!\<#.}0KQox)NwxsK#G z$-V}9l F;#\%~؎KDjYپ4le ܆Np{dj/GTj|F`)L`$l:=)vA%CEY,̝O1Ti jJYCJCel~QNpy' 4t%]'lnd~=}@Kl7|(o8:kdsHhĆ6Fh(V[c۰_>vWHsoT&;SquF5* V(&=]8>gUG:M撚D/kתgfFUOOĖ\Ij3bDX$ruw։x['tu$7 vĖ\V9"1jDEKDܳwM .ldC-|i?0qča@I$]et:W*P%eq\+4m7zsvkF҇lS 6<(yODp;$xb=^{wMDc5"=bYyuPjg.BݏI]*}|\cIc>V㽏 ôҶHu^7cQE )@:H?M* 0HWo 75+wKT<TpwijZ51aE[P3 i#\RtZxBwܯ<0ލ)8) QNn&}4;˻qRqȩzBQ!:t&JV'3AKQr5K~sNoTtEZ18 N0& Wv9x%%ɖ\g?_2Ŵǃu gSAwNe}饋~ruLEBZōM0y׵iܓԒ ڻ ꄮXrqֳWo _ "qAu^G 1υ٣\/JݙPZA4ǖ|( O\¢/3kxMREkÊb:ךx iH\,Uy^@T\I2f*4-UN8t!vV$c7Z ߢWAdj%2ޟ]@,2#egt߾%^}CV|1ؿy};=+FR^%]Ct& mlˌ>j vXoF4XB#una_査!x}O['dUB֬mōzuj-}Z̞EHGc75f~0iZb KQЌ T[JGxP*,I_ NJON:=,Lm|kzgf-V8g?km? d\wvڠ@{ Zo0ѿ:B$K"ɪS`Us2pbZ43'*{6.6ҠRZڙwA*X'JbX<5H6畗6w-)Rxݐ缜|(ôI3Eݺ؆%)g1rYvq[!E*a.EWe@& ʇхu޻rc[Eï~!=@yF:x>JAQ9,${FQ׮%܊z*:G{ Hް{EQ,IrJ]~:(/ IQR{HPNfJ#W:fn'n;kU.q> n4+cU+l׾rA]fcK]ZXe8Oʗ..Nf-@_UeWZTᅔH^)_iF2ϧԿji?S{ӕ;V2!'ՉU_% Q(CNø HiEq ʝWs=sߨ Kޤ;8r›0[lBzvk̈́SQGv%ǝq}IcÞrsϹ]*]8jIy z]13ctû#k GvXYp.Anw jPTIk =jWx;}=:dKΛ=#Ob1?4m| }^ćV23܅UozODRJf:4~Ϭ3<^ɞlDjK\3_I:j/0W[TN "-}wr{q63u+U}F)%inh`x|z}tjjgy!^~1<΍^}%1rrX-;A?Hv :S]62+Tw2:9._~`~|[BK+$ɌT!~kCs|%$*;Tf9q K}^9!LO=PO!WeW9p^- cϼSqF(a"窍3J$i((}iO&2xR ӿt'SXF< ]dނuDD*%/c<#'0 d&ƨ:~uҚ<SNP&u#F7T#~bׁ S4bNX-d,H MoPʤNoghͣv&0E%X.L~ƞ 㓇|L=詽{'̡sVf1|d@_&I OׅrdטLC2SKNUs VPUqzLZvPGIfTpsޕA\Vi$Oҵ@KAJm܅Ũz&}VŘ:+FF^lF~[]EyqY!i)誢utkw72kfiպ%`c4S:C2[NΉg^|1C؁jRU!XC,6gVƯYaEO%8`Ƙ(L鰙_$H32Gpkr 9*r'im8kn'YxT` r[.r#~"e8fnMF1s*#;[c^Uł\Q@ptpJs=6.f "lcMjOb&]/w2c*3@}uO$d5zM!M5臊/@ե6NV p{vI/:Bo,]$uII7'-)1t`\6n".uDŽdz(Z/+lSܲF~Qz2jqß]M11W΀pQ95 |%'œLK^SdP 8 I^{dI'3W?,`إDV>GF1жнE-]e5ge(GKd!3܊!1v#XO"& cNo#VS^M!jmߺX}Nj4=(2sO^m86~ Hkt~Cx@`J\:p^dh- }n&Y;'+9U>10؍'Y%d.+7iZK&8oPל`x:y.QWO74CG+W6{UEIxw:g̠&L'(}㎤o'v`im pw]G-Y*ۤ#W{~/~I r,hCg(byg,g౟䊞Jqt8AbbcȽ;EĢȢx#E(>Lg?^:Ž}إC˞NУ>7㽵Uq[Vzh0mۼ~-9͗As tk,"\2ۍv ?Ď>%u=ICD{cTn}Huu򫴃Gb*cP ΕLs*:v هna1Wl@~ӡ߹B!y_Z M{2~o^-]ZM5Ii+|6|aCS& _- p+¹|7B7b԰VpMS`>W vPc'ͲEhڃ@R]:AfsAXc?3A4>&z"1fwkd3zQ`3#9;į4*(<:R]SeBl+E6A6{~ka\ BZ,*F 35yTx)4XMe)1W 煤вrڮ% G` b~IK}ҽ- %E݁ Q^C.9RU[Taǫƃpj.%A#+he)άFAqCk9|=cLf!S Us >@YQa>y cCM) {+EZžT=9ylU}n~f_3B$Z0sLj+i7ZH ƭ Y7M(ST5zRbj$fqz!#\'Z쟨W ?[~Ȧ6m^׀D:>S`% TѨTWz?"شC؎1u ]~xYr.Rsc%<_8SMEmϷI=,-I>W{j]CF?PX2_Sg6qIQ:9Ez`jg0"( HO^g= 64ѪWA\AK`‘qTC7$Qc/i_0>ÅO1;sdy5HyOP eZUF"b9.lEugOɮ~ %7w,ݷqZ>Gt%әrx@1`y3i 3:2o0V |U/5<4bG/l5A^J_HEC*Qwx#̭4HvHe*oU?4B]]3Lip_X HC(y>"ȞNsqSfN 5D;Soɰf$E&!+[Bm%@=} . )B7`lF8 qkTj/I\綤L 䟌eMoءZSI_z{6Ϸ> S_ %Vֺ=1(6l:&b1>[`,Fw)o⤂@5m6T$5n*ĢkA*kȭ\J tyVP ns'/> stream xڍWT[(HQz Mz^wH@:HGPC]&Hޤ#Rw{kV̞93}=C>;-DCJ@!01 O@B0PB@@(O03( IťB)2jha$1upDa %%y\ (hP̊`3BP()BJ xzz\p,/r@p52@3?10ۣubZT ̄CPW uQ fU`vJp $O1-p0߿,{(j ;wWcqsbvZ+rcfu1c@/E `]1珀z,1>y0st?AEEOA!w:/f 8( b:p u!οn)(*||w+? ( 1 5֮6: Ci>o? AQ`ߴ73у#WW &KP_10!#9ԯ!Qnw>!Q1ycc|A~ 0){8ńWwf0>AUWvG 0 _‡@ `18X:ԩ6G' NS!kBTïqSoN:n?|_z|cV]s`}1vs1F\$E&"D73"9;nC~wk&=*Vki.Тb|`j3Z=22Y'US8γlo-gA˻(r>e3yEijJTڹ,4}Jy M;4lNě47[V '¢!ٟY8Q^ʵAWa|>L~m« iTUv#& |Y.QּKLC&vJKU w%DK>^1GPjŊI\[RO V`,L6~aGc2Cٌ[!~z]1c)G7.:tBeI,-w9k]RpEDЄܶѸv(57gs'BB&l]yq$28CSo U2ɣ;eL6fB I65P >P^pj ⩈jKR#󒃸ŵE쳶Ė;SpY2{:R]vߊ IЙM*4@䩶He8qWe[ gF-kHp~xb ,^ZWRf~C'ReZz)NYL${GɖG{v)*xpdȨ33~$G;n1馇p sJo7!]6xF9qd0.N89'x,a¤GGuDY,   =/ylw:e Xh8TQnW3`ݹۥBxxJjoE#IwD]¥E Yh wFei_G0+2nҁ="UZ;ͨ>y-;|sSm,vn˜z-0k!7\xZ{Jd&/$\$}YLXܳsq[g͛,B[>1t/&RNOiH z&вMk|F c ?\oV}Xk4 =q>x츭aV2N9[ #k3 0w+[ԒB5Ud6E=KPyS+$A^;/jd|ς&}sjJsz#xnʎX oX6sU N:Oy E_ !?z|ŧm׉8]}Aơhdژ`r27'Bn\l/Dm#ʱՂlX]Ka VyeCw>χ{Xn["vkZ"j,mVm_2aYїmX 1Q)ʪQR1Z/E0YE??g~/T_oû5gziH\@4{}$% \,F+{s-Nڔ rzvjܯ$fT ]|͔@_-&!q{|G^"'<Pr fHsZ$?a3N ՠ="c"wZ@BZnuի2ܭ@Ip$hЋV=SDT@6QBWqN=OYB$: W+i*AWM~50_ap>hIjovT|\IAuot{\U^ϳjs9kk?pj}!K%%7H {=D1k*wkC¢< otT7~|WkDپBAQäLXm4\ޏOE +N.x>NmZvG K}Nܺhl =ox^3>*I%vv"BI! a ոo7*3SҼ6*G^8 u42Zf;>,sr#G/`F&i["ǭ U/pqkr vF m#tsngm RFXʨi 3 VV/p $dVjOM,I`³cDM{utp\q^N}>ͭ`l?9״jyxv4p5 @<')=t=xVHoj2T`ΠnFMlX!ao/U;M.[P/ʘO=2Eg^ TQ0b CD֑9eEѢ'J>V.Tֹٙqtj+iV'nYAzX'/:8WZob*4?d@ x.ks(4?Yϙ4b>{ Ĥ֨^*n 2q_Jfݮ<)t7+Mk B djw|i\XXK,BX5)1vTrБW4|p6LL9(2n*!XKo"߲ؖ w^͎Z[/D2Fd]}ыbczjt3T}^h)f%cO6EowK!/,So9\s:7Tl Vd`zC g cHsRNwt9ސB`[IWepB..&+2ŘorgR3]xVDX:3DPF+GYQUL 4+@m3BBPYBlx {I GY!YTY, ÆxʩQӛp oTI2V\UW-)DX dbUzúbw]=hxC  =ycdg7F,]G[Y ͺ>ᝅRou%2xO6'MƟlqTk+hʈw=v }}`~%ז{Erjܶ'}t<:kgO4jbPolayX WHy6xpBh}#ON;6aSb=$2j*ϟw]oy(ڣ̎cn|6f÷rRz=T[]'VrnǬ =[,~_KŒ[Ϭ3h/LLſf'pvhA$ze=a[ uƗr.fk3/;֊{:?&Qle*b/5&6ޓODF֦Śa|m_uG8JCttePX?,F8ٖ|[vVBp'6{cX_|JW/>N,gXDrS% ~Ɋguf/oНb>lW'7@6=eOl#^ m 7>|ߋ|]BKekBS(L5moDڕ$#19qZAҽY%bӐC%Vg3.B0ۮnt(œ|Ħ$?ިD8[f|kkQ6Sm J0):=&6ycZ9l5k'POܽB"r8=&|"RTf2q> ܅USj/<>{+SҦZܐjxi@xʩZI$^CSDs6J?W6s\ѯkg$,M3nXq|yȜ>^;ϲ>:[PhAU>Ϋvw8gS%Ks*~ؐ9 }FX]͒+ZܸP-qٖ@˾)LZ_@sE؀=Ρ;|V׬j}]Q% WqFo!?wr6w#RiY WeՆYr2ͽ:a L{hDzuYŋMV8 endstream endobj 42 0 obj << /Length1 1612 /Length2 14318 /Length3 0 /Length 15151 /Filter /FlateDecode >> stream xڭct&bsNŶmTTmN*۶m۶yNw=a[||Zc+ mmֆΎr6@Sg_#9F DFff9@ @AMKK_B7@hekg q ̀s+ @DAQSJ^@%! ΆVFYs##`b`dkclOk G;4`m`0u0q;'[?MlEo__0E[G'G#s;'ߪdfOmGnHc[#Z/_# O-C o`vhnc_ @Sc+_LoY+_Q#ʄoM#Mm(R6&&ۍQ3IX&pmPߩ'-OB;[YX]?0/-@ce 76r?$g$?p C 6;͝&V'/1W @>U3s#KFo?_Կ T7_QwRuK"gk` ۺ<@zf߂LLja@oˌLj0b6F슊_FU׍dYzJc3´[X9NTWRj5J'+Y-ZAE$*cD-r `΋y_B+Wc˸1;g?{ ES.ry߀*OǑ6ke fx>40 JƬzRˎ)%޿#h3m'C /yQ6֕GJ hg*l)I$[T+ T/!7H(YEm@:hڇ7UeayI/amRtTFL :h^ӊP2A'EEjqn.8ě %;g(JR\uĤ&`c';sUP|jʖo60ͶDVڎLI v]VYvuqT-*dT,9_Uv rsZnpC/ Qd el@6`br2 1*&z`F򾓯ڳfXeE<.#IZFG1c(.ǗKdnݖ;7+? |%2뗹YA%/e=ZC7q$>ۛ`,#f*Νu;j{A ɡ gEC5tK0qjͷ%*0yM+qޢ d4O xN%]+OIp0uR24̷UrA5R f66BKzXrju?4>%[ZАFg+X$zϗ W@LV oɦa(^C+>Z?6=;` nѮO;[wB,i pSn5e5ku:? x?`2gDC ܝ2[RSu|TJ?Y΢A29%^'^eDrQr)µmOL|:VCìUG۽]R)#| O fYh)ͿNUTnqXXf#p3͇.l΋0KR7-r11c&Kt=> QaScg}8DN-.yB' >GP}RLJYYv{1BJ+ySfUH':d-z$Cx|;Mw}S1k]4(*\t*gW/WfiQ'~B ~L qhz<wPBgzĨY"4" n#TpO'kdMk*qS0x|ʷu}_qHYU|t^e*8uʗ1 PVXdSHc]8$q NtL;; 3O;I'3DNژh!14n#JeLj5|(mrQb"$mnV/w fLK'~kd= $.ž|Cx B[#4gXl/;tMnK3_d}ėwɋWɝVJ8qID#;: gA!T1{΃ V$]mTC2REwғhůE#k3 ňE|a١c 0@BRXY3m5f2[Tt?QC,ejKL@q{T@'9L7E=S"@`KА%dzn6*j8 uQ{Z X{Ig+M.X #V._αh늰#4"?Q۶y:~|"KNg# aAe> j]{l$F/v3 t]9`ۉ:ME_˶ `23s+Zc5}aLHʆ Zpz=WoL"PҠ,I9~ T|tp(..S27W$zH"( 6b28Ja̞dPX8i3E޵:^QP=Oe#B] %jgMdywxs:A-b\{(CXF?jl~x 5=Xޝ`3hRc_GN snD\ܨJrw[CʩT( ǐ-)C*)nd~}Bc 4M[CĈrv5EF< m8 l ;ĔmUGr:G.q ,ƭV?EjΆx,彽*nȼ>Rnƒ:]*FF5c^UP`*KA֑GFK)Bk_M4ts3o9[=y<.l_KA8-wFؕv1e&}\?&ؤNδY{P8Ww*/Q-8}̪N^f|n<-%yl.Mֺ,hC}mA)ӆwHI1)aq}6mFrxD7oi\B=>xH >s@vrL?Gɬy;x.;rȪbo变IO=qŮZ=Zo!\3;zoyHjÓ *oUbY/[틲J& IlsCp^`0-bAGYvnAdo(~t-^fILT_k;^R#k;+_7EN*r$Џ8(Ҋ$̷WM#~.w_5.0!7=P$ٰ&f`NPwm`CArBMGL)B3B{xz)|gB&ga̠5mLCm["AD4T\c> vBnrk~yjfyXwBc]K' , ͼ1 E6fxuA-M-:wNҪt<Ö5ᰟteQ'I]vYB7\Uӻ], D":x71y4`?~0,EH}h/(Zyv"J_.e^)қ1_)_,/e[1hUIj#{w>[F۟+{h`md9;_Z|(0e99tQ9ϜziG{UwEȚ!)Hg:6f>y3?Ѧs3dFTوk&e\koɟǚU0^fMEig?AwOcrk/+9,MƬx~qQBLT S_*`~~e<\#g0@=VTr9 b LNVh̶LdTUA1414ԉ^ԀZ:{u-@~$w,c/*a6>Uvzx7ɾjeBy倂Ռ4xwrwn}*$ q&?;sNq*4sڒdU-%z'}tpY6 R@'j!}O2kHj S,Aa s14w9VW<'DzŒd&I?@p\8X%*u)P4{jŰxRyԜrmpϪ}H4¹>;BHJ:d7羫ɖ+^u,4N[\]{:YX)ZLRY t)Җk, Wu@X~ndQZE'^؋ [Ycl^ڡSPކ/m~K= R@)1t x\:-??( >G RhhnA+dB`^%! )&eڙ"bejy/H3h;Ս?lE?feeWs"2Zi?ASF E!9R'hC0[]@$ I{X UFcE/ݻYa-F,>Uqbt~ł{s9/~sI?@Pvf 0vͣ1:O4];3B/`# J>8(@xCI~p/i7KꖂP =dP$񁉡|kŘ״wY}RjK#(wz t݅ }"8k*лl? ͑CwuDCtK8AFU9:cz%ϮlĢLzT̞N&6DXqƶL^mS57<׈*hQ?/)P_(+s&oYJ /[qCQxpUpܯ]3gy +ET+E Dn֧[XR qOGgZ,c蹻4 qw5"eXNY}v ().Rd*$F.| I;c#lcI1 BHñv,EO >;K) 6>t4 o,eG KqAKN7Vrf3S5g/?GJSUPjkYBXOf6?۷vAIBȧsKLj1VJJ[}'HBBt?&הDc}|j 8J%f{3'{Rfwξ" M`MU`!`N;ؘ8霔A2x\yĭO'[pBOʹrEh"Q\?%"Oogң!fi u-H!\R]}kT~U }:e-\o,[Bm4>^Rx. ݕ jydOaf[KʱՅ1˅_U2k*^v6us"2ba|S9z[Ydt@#rdjZkiEbR>^oQڏ<:o)CLLtzhu< {rba/.#PYن|D3$IJD1iW }~z\ĭ@zI |F#pj8nS SI \j;߅H$/!cfY%L6kɹٶɠkA^ to@>$0$TBoB.yR`DߺVdZV^wJ/cueEZH+p❹pZ=( Q hCxP@_KM]71t}0b> QiPchF|^{)rwt7V 7@G0#~$zGdq.PYAn6qs~)5d /^?ϳ}y= .n]`{G/A:R|LpOoӽhe$1N}2_HXs F8e/aΤsTd'P m߼(3ߢ]-c*:ObH; Q%rKy! 4Pcsp8F ʤW,:ZE/ze\t+w${p5"q~6 5;鑧9.>[ Ңg_/F kQf2fw 2P9e'e+߷G&\H9 ~ٽTOGegH$Pl56N)`Qqvc73kqh9_T|7 qʭ աttx1?l΄1h_y1]&/?S$ ̧u N]ERSw2aN*1Nɓr[K ź¨1'Ǝgeqv,䄋tRhyZI3#Ya+;vm*%R5EvvOdmmyspzNY ܖUGe;ȇS*|UbS#KUm$_՛r$5N?Wԉƀx7wY`{tҳ >$f0l\Ƿ? w:\d02I,E v^i#a1 1{L*B$ j MG9}Vnz>WbļL=$`CHfUH{҆FE[cs\ 8Mj3|尃inlk.1 P6#8Mzo9 P#1qIC֓WmeAe f*%'bW0jh!N#s|F=fWZ% 4v1ySD-jFD95eu^e=sj1,_1)jD݆wk- ɓ3 ]8[C`Dee᷋ȸC7 1?j0hm?kK6OJߜ~-PN\07%0~ocioһ-@d QԬZfjRmփg$^; DmMG< Q[ pi<ŃT S4Ȧ{HZ=ywm| v3հ=nd(II[4FH֯hpOspfV$<w_u,2~ܭ^gn!ᐎ1o(ᯓ|:7 H5)X[Kz9DPA)!VLڿOt9]]w\d%`]ZdRP;њ nWmςQts2.nTCJpi9i~qonUҸ>&guN}z 1C;##gdk'DdS?餳0w!K6g0"dueˁ n.d/ S$9*-- 7Eƙk˝JmަbCkꩰݳPOAk&l3Kc۪7?Ri?#a&49R}Z?<9&=nCBڳ9QFN~4pDTyaRX0v}NL#7TnL:1#|wt8N>p[0Y>݂YGuB.;{#T(t/HXHu+z6Pj2wa]_E qyz _h^A*vĬ9oMl?6&gq h>c2 Qw.xjwkڙM`L'춳! jϬ,/v0E"1M[ϰ{KsEdoVXC<D8If-aqzd[SMJ"NHaKo~xF5[f!GybGv'T= +t=)z˧G9DF;Ez:PʒQJAnOHL+1TJH8;xm;R9*e)V'PۀVE[t[u'|iQ H.egb4Q E|]E3Z{J{#2ê"2SyF< 3E Sb8 s4AM_m*ܢ3ϡ)󅉟u~w׭!)*vBpr3>cL׾E~A9b7ϾvDBܳK9{Q7QcmIC|ՔT.s0.lU̷4k7Qƻw8MʖN>r.bq(́Ѽ-I?C%\nB({_fbpzblOJ\rDcs}U2 u>t\ ěz..? ǽ*'=um<P6LX 9Gs6 fTwM+DAf12§×ܠ/JjnNfSvX}]bpY6A)],̺[~\WeG Ҝ3}qkoyM^M1}LwCYl- E Qۆ:؇uqP.|ƸF9uhU6Dk!sY"7sٚAhX{I9-"j ϟ G0O0(w2DPPv$^z| Y廒[%|ޕ.,V#`eđű X[DONߏCrPŗ`*bn]+QL;""ystHoSEZLQb!X&ԅ\nͻ%w;о7).mxTF/\Kqkv\> stream xڭteXے5i<@%C@-hpw`][pwww8Ν;~]k*Y\+1HƑ6vrcT9)["5=lk#fhLb ÃH s;>h10|+c"`33dN?v AS % (ـ썬JNr` D0Xژ*́K`pn W /do vpxfF6=pmVN&%n7;!;{;lx$&<͍~7MlN7N:m Wǿb&`;+#dvprۘ+O{Nooۿog`G)"+{L{l3 "_"mcj `e3}kfޓ02rLlC>Tf@O߉G}N-de`d>X2-c goNl8E ?ޛ"bc. #+?` +D 4Yo lzﶾ;jZ%? ɿ.3J*3wJfG y[<勭+ feD_gy#G{+@߿NzF#n5kr:٘Nߋ\A@ąY[ J1NV_5y9><%LcMn3v/2;Vӟ)r0VhZvi{M˭hsl(<ÒßP:`SڡzpZk!0+shniv\|&bȌB3J8$wt3~xtrSG/f:o >Hv+R#ǕSoO2۱k.`OY:ZQ{.so dDa 2%8E[{L5hw6Kc^m bK<hB*{xQ>=Wtsn \֒]'6𦕝Y((~cweG:,]T$?ͤe~joUO߲CA?nt L*A(r[NO7yi4tF @ZLU'T&|Жfh" (ZDIlx[Lj^xL$PP o8 J6S'6H[@ IysBn07pųd<ā2t&Ƙ|n%KDƮw\J MtϾVF9Nw2ZȎ%gq (HT2Fl̽g{dK64̫|dج];` F}$aTMj-7/ ,$G*6,vwU:U7wV_*g@.6`LBǵUDV8"]ڎM`A@|lPYW Wzr%u|m[OuZUMi9&_ol Ry#Plmk*cz2v7tkxq$֗eģR:LuTLJtk2S 餜$V Ҳ|!~\>e!ijEO GvHO{1Ai/">0Ӿ~ܶ$ nW<a]АKT%j83CoĂXd>4E;5_)pTx Vn`^|EH1O"$>3\cz1r%A`ʱaE"l+~eJ0}8}8ۊJxܭM2d]ZKn K .Y"{!(D&b#bU 5!#A(ny&xM&z^24nuګT$av>&Lk` Ĵގe3 lN-j0}~Vs֭AO BKo)̏Nvβ-#b4:x֔u -E6%/OI㼝eBhgJnX (ru+bvפY<am w7O*F^k0({8]?tqZEiMmj]ǐq0EY˰hk$@f d3O>,K&m΅C-x;,kL!7zoE)꒞\${lnpTRљ+[SB _K@~^Wh7rp3{H׽2)־h0iQo*b+^#O9eӊ o7}mS+kβf_*_.*SμXW,~7nNI.'JSu Юݲ=j"1eK S:dB!'\KʻH)_B̼iQda=?DG^Ə cs;E=DJ@s&>Tx$݌cg{9 .}Z EuQ)* veKnQv}jh B Xֳ[ƏZ IEjna=rcR5R_.grU`^ůp@A'qDvqF <{'w}*tWˮԏY3](+ TЪP~tv*j-R,9E,]м\0 @L:BUh°+j8=ls8@JmU]fV啱-Mcy-3ן8oΖ} V9DSN F8VK&Aq6T0.{C}SD?]?]="])ՖTr xw<=omx:vVeIƽI%^@ ge9mH-!RV^?djcA7fك]ݙ? 䫨!&+tȧx>4rG xGh +Uh`XYw}Q9lr]h_G.OhT-TVL 29rǘC>T[yF)o<(< I*K/pqu&'NoO݇|5kSb4"i}pG[MZIc;2TWq2L9oa`bx[dl %/7'1ƜBʹǻ Ky:N~ %"MTR!'̂SejOw3Т}a1`O^: Lt)0K.=&KiV*?,q!MB;]~qlF @l~YSEɡ5l|''̆Mlń."w3&25e{l3:FͰ48/49#K&$t_ة$5v씱6ML A<.i. Pj9D?R 7&Fi׎YDQW Or&[4Cӡ%tX[J%aƷOI4݄!{-4qG;PV81*yD9p(!e|b4qdhhLH)m[էz"tgJ1Zl>RnspmM'{&MC=*G)sQ~j'K٨;E|WQSA輻?W$gkn \a Vjb0oX :md]_+R@_}->!T"𧷌)}X(͗ fG(0#?pHeq0oRbHJ_*`°]|9"z^xޒzs+I5!̌}̰Lla@Z3W3|c(njVͿB&xO ՞yMwꓻ_ _Fr4#}Qn6US-ƾcg68䛪p;,DfB%xQ{z`)DnqlcLePmҒpdž &.LC5iܽ7:YGt[t 4cwiIh/x6ta~ɄĶD-9 u ^q:ePW$Ā.f1%BV|UP.{?~t=E a52}y͓4W`/n窍   e7'sͶXA `}BeNj/QCu>=\'h%6v?թ4Z. eY|2m1O<5fl!22Zq|2`Ry2c*bvUEP M/jx?AN3V @BD$|9Gb"q{(9k-i)Kun̍-QER5s$bBp Fn(jLR 5! q\?pz i]%ajXa>os1²iq"AXpw{ ?"1 錥N>% 1.kb4h&7k(#﨎Á30W#!jbȴFaR9(_]'eb֢V1fgYnњ-)nvKq;oNZ.V{S߼58_PCף황ULV5P }U&j njۧ^2r: =c՝ $U .߾B[{dT$c|^n :SdU;%_Nyfmuw1) gGį2#Cѝ%)qd4.N7ixsߌO⾧)D $O3XFiE!#lxOKYrXQm-4@s_.nH? %FRHv=qޝ! 0@<)Q6>Z+I믛Р? i5$W =^ǀ]iu-4Zx}fWJܝ[sCoLb&7&9߲;F|rZHǸ}A|'yW}o3-m][() iI6!a`6ZKߥ߱"UZ!ӈN͂IǀŁ`8XT'XjPq$ʆ~mncK5.2o}+{m"C( 7~;8SHrHQ6s{?$XyyPdmǡ;E{ֺ -vzTLR&^1ubID55lC&A"@'|Q*'U?S&.)j"*7~ &nlr<> 1/)kg*&cPxb҈V׌ [bO!<=z)$' n,ϺnU?`^c+`/ ţXNu6S˷x&I:9+وfi};s3߲@$zZ3cF\ S;C\.Ehݕ y#IZ蕫qNo_0=oze.z@+ș=1bk=EB~x[+70^2o5)24@x ᡛNgLҼe jCōX:9U@lkb&_x6rd}G{}-w@6foتa %hzbJ|l.#{}9`4,햭Ls&>GBҍ/g1U tWYymT=Dr _C?y}=a^YQfWRܰF]P q㤲% yE3x~ƦY̾YN|ueʰ(RfW6fSP̲U1>PcX(1CG͸?n% :] m]i!xi:dL|=2>.ddB҂p&^w !QxXt~<-f.d(Op9[U^Jm80tw[ncUP\fvD#`G];` tw2Hhgmr"H7 mO#冿<*l }[}Q)堃V˵xU);\JH y ,AR:NȟKuqe8<$ވޝ3' mCN{BPkKqDr ΰ{0mNk̵ m%.+<;m88[e`Uߝ ) fq ZdoE!BjuuL u_0fOo St)'  b&d}ys!ӷXϦW<>og WWVukYUFЯhq TLsot?ƂcLC..cУ.H<)[z^ԧ[Jܯ=AX3"k3fw@P ŏ#;O29sE0I6lK7Aq؎.6gtFd(މ D}>@zS^4ݿJ~6ZAvΐ5@b4mM=%&)F>nsȑdRs.Wܔ6Lޞ>Ɂ0*uzns+/1H ]GIK;a@pMemu}vs9LK}"DrkwKgyk5}VxND}4}OcxӨfr_5wJ'`kO4<5c̲!h] qުD*~C08vzutaBm,%Ա&>n5˧<%ѮX^*o/d7p$3)HM|o*MuXeA89kT3v\54Ā~՗ cRV!M_#>ɀÈ:6.D m̲IҶr)7Us @RiL^IX~؊%VbqEmN84p^w@`.D#TیYˑ Ay a 3sηGN$\w71Ka^-RY[ӕnl]RXT<=\7$*wa\?"X|Md~H-Mi~gޮseuBh.|#HwG4)=;F>g(N di3?&3ewwxY>A:SۼZHͭy u2FBVP9ғŝZ<4I,Dž֔ J<%0DLT'&+\rD,.ԯ"&N-!ܰ*S{h?gd"GRf Ǧ=œK{{_ I\ϫP2G,\dWEf40Pjp[QdcN $A^A+=|︑;v=AG(@_%ZL!W 0ܩܓo ~F}gch f&LJ[8<2Eiٿ4;"/-~ bgooTP/ #n[崱7<|]|cNITtWF҉[۟DfĒ 6sMsvJ<-L7EUaEw#tIx=\s*eݏGGku^#bm3f(yuMq 0rO :2nsAt@M8SX6 ǩ+ QTc+TgVrN߾&5RC7s⢜If\1/E[ gW = S⣉ЩѪv-=5_}Y7Y^fC1@p̒@&-8Nܽ/EK5u-[6ӿKkpd  T)[y-DRQ+.A(}ILJx]*h*ĵ QvR 헒lNRdtoEol& Dcv f|쨗{A(vGPwtz%);$P^cڌūkQAVZO,ғ[λGr.%zN Q&BDȼ;On 9~\#qjBW헪^̀da!hW1u+=d?4.s .|<_)7;$c幠i+J:њEiui|A.9xQA.+ȭ*!R9@BZ7p˦,џ \V{朮H@lWTP3ۏwCv-ekDұ#탆36vi9Ќ/C^EnN.8Kj-kjy3A5}击a3=a NSZW#A'1=jt^BDd,|d-[l5tjo7ّCV+He-r HTX i 8t}hq壘A _ǵ'RrjT.K%7[$?v `<ld> v)j#zI6uߴ#qha?>6 {}C={b-ה0wܗ >E~C?\z:⡃&-:}vѱ ɤ+nSo1wLZjsGJЊ9P´`s9a:x]qQ4r[k)#hVeE<_UIOQA= !"COڂUIl endstream endobj 47 0 obj << /Producer (pdfTeX-1.40.17) /Creator (TeX) /CreationDate (D:20170407125642-04'00') /ModDate (D:20170407125642-04'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016) kpathsea version 6.2.2) >> endobj 10 0 obj << /Type /ObjStm /N 33 /First 242 /Length 1987 /Filter /FlateDecode >> stream xYYs}WJy>ުJMUIN&ʃ 2 ,|`b'}rD voCQ"RBAH%sB{'a.5BJ 1LLGYL(tgbt`ZW`Z-ÂڢˣUsa2L6Rm>?ɭ }&X 'N8stpXg;t:&M9ijAiN˺M{e ^?,/}\}`x}oq.V jt[߂вTL|mcoCC 0ԮVZh=/eD!,#k0dߐ1KB9khgxTdOcD%e6~8gl`Հi96z! !y\jhpqyEZk%Z9{(5h<7;p`DCwڴo{Qj\L+JJW zng_ԗTNbRkt[ƻ\ŏqV5e|,1[jCz86MZE'g'-ѓ8UWjT=YKu%WUWTZrFA$BRI>b(6-xn+D/O<88>~*f4mؿʞn'ޞ4 mѧe,pBu>r?{p|>ɋr0[Ku ] /Length 146 /Filter /FlateDecode >> stream x;^ˮ7҉E"tKgғ|!P'/FbH~h[NdtYyb,"1xEH,]X$FA&Zdrrpy~VX endstream endobj startxref 100032 %%EOF filehash/tests/0000755000175100001440000000000013042123362013211 5ustar hornikusersfilehash/tests/SHA1SUM0000644000175100001440000000015413042123362014215 0ustar hornikusers6b1babdfa60a17a2e79cd9187ea06b3df3c46624 testdb-v1.1 6b1babdfa60a17a2e79cd9187ea06b3df3c46624 testdb-v2.0 filehash/tests/versions.R0000644000175100001440000000110413042123362015200 0ustar hornikusers## Test databases suppressMessages(library(filehash)) testdblist <- dir(pattern = glob2rx("testdb-v*")) for(testname in testdblist) { msg <- sprintf("DATABASE: %s\n", testname) cat(paste(rep("=", nchar(msg)), collapse = ""), "\n") cat(msg) cat(paste(rep("=", nchar(msg)), collapse = ""), "\n") db <- dbInit(testname, "DB1") keys <- dbList(db) print(keys) for(k in keys) { cat("key:", k, "\n") val <- dbFetch(db, k) print(val) cat("\n") } } filehash/tests/misc/0000755000175100001440000000000013042123362014144 5ustar hornikusersfilehash/tests/misc/create-testdb.R0000644000175100001440000000053713042123362017022 0ustar hornikuserslibrary(filehash) name <- sprintf("testdb-v%s", packageDescription("filehash", fields = "Version")) dbCreate(name, "DB1") db <- dbInit(name, "DB1") set.seed(1) dbInsert(db, "a", rnorm(10)) dbInsert(db, "b", runif(7)) dbInsert(db, "list", list(1, 2, 3, 4, 5, 6, "a")) dbInsert(db, "c", 1L) dbInsert(db, "entry", "string") dbDelete(db, "b") filehash/tests/testdb-v1.10000644000175100001440000000132613042123362015106 0ustar hornikusersX  aX  fX  ܲ?ǁx7 suppressMessages(library(filehash)) > > ###################################################################### > ## Test 'filehashRDS' class > > dbCreate("mydbRDS", "RDS") [1] TRUE > db <- dbInit("mydbRDS", "RDS") > show(db) 'filehashRDS' database 'mydbRDS' > > ## Put some data into it > set.seed(1000) > dbInsert(db, "a", 1:10) > dbInsert(db, "b", rnorm(100)) > dbInsert(db, "c", 100:1) > dbInsert(db, "d", runif(1000)) > dbInsert(db, "other", "hello") > > dbList(db) [1] "a" "b" "c" "d" "other" > > dbExists(db, "e") [1] FALSE > dbExists(db, "a") [1] TRUE > > env <- db2env(db) > ls(env) [1] "a" "b" "c" "d" "other" > > env$a [1] 1 2 3 4 5 6 7 8 9 10 > env$b [1] -0.44577826 -1.20585657 0.04112631 0.63938841 -0.78655436 -0.38548930 [7] -0.47586788 0.71975069 -0.01850562 -1.37311776 -0.98242783 -0.55448870 [13] 0.12138119 -0.12087232 -1.33604105 0.17005748 0.15507872 0.02493187 [19] -2.04658541 0.21315411 2.67007166 -1.22701601 0.83424733 0.53257175 [25] -0.64682496 0.60316126 -1.78384414 0.33494217 0.56097572 1.22093565 [31] -0.21145359 0.69942953 -0.70643668 -0.46515095 -1.76619861 0.18928860 [37] -0.36618068 1.05760118 -0.74162146 -1.34835905 -0.51730643 1.41173570 [43] 0.18546503 -0.04369144 -0.21591338 1.46377535 0.22966664 0.10762363 [49] -1.37810256 -0.96818288 0.25171138 -1.09469370 0.39764284 -0.99630200 [55] 0.10057801 0.95368028 -1.79032293 0.31170122 2.55398801 -0.86083776 [61] 0.54392844 -0.39233804 1.23544190 1.19608644 -0.49574690 -0.29434122 [67] -0.57349748 1.61920873 -0.95692767 0.04123712 -1.49831044 0.66095916 [73] 0.28545762 1.38886629 -0.15934361 -0.46091890 0.16843807 1.39549302 [79] 0.72842626 0.33508995 1.16927649 0.24796682 -0.35814947 1.38349332 [85] 0.41206917 -0.12300786 -0.06622931 -2.32249088 -1.04565650 2.05787502 [91] 1.97153237 -1.92099520 0.46212607 -0.16072406 -0.10421153 0.46783940 [97] 0.44392082 0.82855281 -0.38705012 2.01893816 > env$c [1] 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 [19] 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 [37] 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 [55] 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 [73] 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 [91] 10 9 8 7 6 5 4 3 2 1 > str(env$d) num [1:1000] 0.0854 0.3317 0.5647 0.4989 0.4549 ... > env$other [1] "hello" > > env$b <- rnorm(100) > mean(env$b) [1] -0.02208835 > > env$a[1:5] <- 5:1 > print(env$a) [1] 5 4 3 2 1 6 7 8 9 10 > > dbDelete(db, "c") > > tryCatch(print(env$c), error = function(e) cat(as.character(e))) Error in dbFetch(db, key): unable to obtain value for key 'c' > tryCatch(dbFetch(db, "c"), error = function(e) cat(as.character(e))) Error in dbFetch(db, "c"): unable to obtain value for key 'c' > > ## Check trailing '/' problem > dbCreate("testRDSdb", "RDS") [1] TRUE > db <- dbInit("testRDSdb/", "RDS") > print(db) 'filehashRDS' database 'testRDSdb' > > ###################################################################### > ## test filehashDB1 class > > dbCreate("mydb", "DB1") [1] TRUE > db <- dbInit("mydb", "DB1") > > ## Put some data into it > set.seed(1000) > dbInsert(db, "a", 1:10) > dbInsert(db, "b", rnorm(100)) > dbInsert(db, "c", 100:1) > dbInsert(db, "d", runif(1000)) > dbInsert(db, "other", "hello") > > dbList(db) [1] "a" "b" "other" "c" "d" > > env <- db2env(db) > ls(env) [1] "a" "b" "c" "d" "other" > > env$a [1] 1 2 3 4 5 6 7 8 9 10 > env$b [1] -0.44577826 -1.20585657 0.04112631 0.63938841 -0.78655436 -0.38548930 [7] -0.47586788 0.71975069 -0.01850562 -1.37311776 -0.98242783 -0.55448870 [13] 0.12138119 -0.12087232 -1.33604105 0.17005748 0.15507872 0.02493187 [19] -2.04658541 0.21315411 2.67007166 -1.22701601 0.83424733 0.53257175 [25] -0.64682496 0.60316126 -1.78384414 0.33494217 0.56097572 1.22093565 [31] -0.21145359 0.69942953 -0.70643668 -0.46515095 -1.76619861 0.18928860 [37] -0.36618068 1.05760118 -0.74162146 -1.34835905 -0.51730643 1.41173570 [43] 0.18546503 -0.04369144 -0.21591338 1.46377535 0.22966664 0.10762363 [49] -1.37810256 -0.96818288 0.25171138 -1.09469370 0.39764284 -0.99630200 [55] 0.10057801 0.95368028 -1.79032293 0.31170122 2.55398801 -0.86083776 [61] 0.54392844 -0.39233804 1.23544190 1.19608644 -0.49574690 -0.29434122 [67] -0.57349748 1.61920873 -0.95692767 0.04123712 -1.49831044 0.66095916 [73] 0.28545762 1.38886629 -0.15934361 -0.46091890 0.16843807 1.39549302 [79] 0.72842626 0.33508995 1.16927649 0.24796682 -0.35814947 1.38349332 [85] 0.41206917 -0.12300786 -0.06622931 -2.32249088 -1.04565650 2.05787502 [91] 1.97153237 -1.92099520 0.46212607 -0.16072406 -0.10421153 0.46783940 [97] 0.44392082 0.82855281 -0.38705012 2.01893816 > env$c [1] 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 [19] 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 [37] 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 [55] 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 [73] 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 [91] 10 9 8 7 6 5 4 3 2 1 > str(env$d) num [1:1000] 0.0854 0.3317 0.5647 0.4989 0.4549 ... > env$other [1] "hello" > > env$b <- rnorm(100) > mean(env$b) [1] -0.02208835 > > env$a[1:5] <- 5:1 > print(env$a) [1] 5 4 3 2 1 6 7 8 9 10 > > dbDelete(db, "c") > > tryCatch(print(env$c), error = function(e) cat(as.character(e))) Error in readSingleKey(con, map, key): unable to obtain value for key 'c' > tryCatch(dbFetch(db, "c"), error = function(e) cat(as.character(e))) Error in readSingleKey(con, map, key): unable to obtain value for key 'c' > > numbers <- rnorm(100) > dbInsert(db, "numbers", numbers) > b <- dbFetch(db, "numbers") > stopifnot(all.equal(numbers, b)) > stopifnot(identical(numbers, b)) > > ################################################################################ > ## Other tests > > rm(list = ls()) > > > dbCreate("testLoadingDB", "DB1") [1] TRUE > db <- dbInit("testLoadingDB", "DB1") > > set.seed(234) > > db$a <- rnorm(100) > db$b <- runif(1000) > > dbLoad(db) ## 'a', 'b' > summary(a) Min. 1st Qu. Median Mean 3rd Qu. Max. -3.036000 -0.642100 0.172000 0.004131 0.614100 2.107000 > summary(b) Min. 1st Qu. Median Mean 3rd Qu. Max. 0.004583 0.229900 0.478600 0.482200 0.729200 0.999800 > > rm(list = ls()) > db <- dbInit("testLoadingDB", "DB1") > > dbLazyLoad(db) > > summary(a) Min. 1st Qu. Median Mean 3rd Qu. Max. -3.036000 -0.642100 0.172000 0.004131 0.614100 2.107000 > summary(b) Min. 1st Qu. Median Mean 3rd Qu. Max. 0.004583 0.229900 0.478600 0.482200 0.729200 0.999800 > > > > ################################################################################ > ## Check dbReorganize > > dbCreate("test_reorg", "DB1") [1] TRUE > db <- dbInit("test_reorg", "DB1") > > set.seed(1000) > dbInsert(db, "a", 1) > dbInsert(db, "a", 1) > dbInsert(db, "a", 1) > dbInsert(db, "a", 1) > dbInsert(db, "b", rnorm(1000)) > dbInsert(db, "b", rnorm(1000)) > dbInsert(db, "b", rnorm(1000)) > dbInsert(db, "b", rnorm(1000)) > dbInsert(db, "c", runif(1000)) > dbInsert(db, "c", runif(1000)) > dbInsert(db, "c", runif(1000)) > dbInsert(db, "c", runif(1000)) > > summary(db$b) Min. 1st Qu. Median Mean 3rd Qu. Max. -2.76800 -0.65520 -0.06100 -0.01269 0.65240 3.73900 > summary(db$c) Min. 1st Qu. Median Mean 3rd Qu. Max. 0.0002346 0.2416000 0.4813000 0.4938000 0.7492000 0.9992000 > > print(file.info(db@datafile)$size) [1] 64980 > > dbReorganize(db) Reorganizing database: 33% (1/3)67% (2/3)100% (3/3) Finished; reload database with 'dbInit' [1] TRUE > > db <- dbInit("test_reorg", "DB1") > > print(file.info(db@datafile)$size) [1] 16245 > > summary(db$b) Min. 1st Qu. Median Mean 3rd Qu. Max. -2.76800 -0.65520 -0.06100 -0.01269 0.65240 3.73900 > summary(db$c) Min. 1st Qu. Median Mean 3rd Qu. Max. 0.0002346 0.2416000 0.4813000 0.4938000 0.7492000 0.9992000 > > > ################################################################################ > ## Taken from the vignette > > file.remove("mydb") [1] TRUE > > dbCreate("mydb") [1] TRUE > db <- dbInit("mydb") > > set.seed(100) > > dbInsert(db, "a", rnorm(100)) > value <- dbFetch(db, "a") > mean(value) [1] 0.002912563 > > dbInsert(db, "b", 123) > dbDelete(db, "a") > dbList(db) [1] "b" > dbExists(db, "a") [1] FALSE > > file.remove("mydb") [1] TRUE > > ################################################################################ > ## Check queue > > db <- createQ("testq") > push(db, 1) > push(db, 2) > top(db) [1] 1 > > pop(db) [1] 1 > top(db) [1] 2 > filehash/tests/testdb-v2.00000644000175100001440000000132613042123362015106 0ustar hornikusersX  aX  fX  ܲ?ǁx7 ## Test databases > > suppressMessages(library(filehash)) > > testdblist <- dir(pattern = glob2rx("testdb-v*")) > > for(testname in testdblist) { + msg <- sprintf("DATABASE: %s\n", testname) + cat(paste(rep("=", nchar(msg)), collapse = ""), "\n") + cat(msg) + cat(paste(rep("=", nchar(msg)), collapse = ""), "\n") + db <- dbInit(testname, "DB1") + keys <- dbList(db) + print(keys) + + for(k in keys) { + cat("key:", k, "\n") + val <- dbFetch(db, k) + print(val) + cat("\n") + } + } ====================== DATABASE: testdb-v1.1 ====================== [1] "a" "c" "list" "entry" key: a [1] -0.6264538 0.1836433 -0.8356286 1.5952808 0.3295078 -0.8204684 [7] 0.4874291 0.7383247 0.5757814 -0.3053884 key: c [1] 1 key: list [[1]] [1] 1 [[2]] [1] 2 [[3]] [1] 3 [[4]] [1] 4 [[5]] [1] 5 [[6]] [1] 6 [[7]] [1] "a" key: entry [1] "string" ====================== DATABASE: testdb-v2.0 ====================== [1] "a" "c" "list" "entry" key: a [1] -0.6264538 0.1836433 -0.8356286 1.5952808 0.3295078 -0.8204684 [7] 0.4874291 0.7383247 0.5757814 -0.3053884 key: c [1] 1 key: list [[1]] [1] 1 [[2]] [1] 2 [[3]] [1] 3 [[4]] [1] 4 [[5]] [1] 5 [[6]] [1] 6 [[7]] [1] "a" key: entry [1] "string" > filehash/src/0000755000175100001440000000000013071742312012642 5ustar hornikusersfilehash/src/sha1.h0000644000175100001440000000064713071742312013656 0ustar hornikusers#ifndef _SHA1_H #define _SHA1_H #ifndef uint8 #define uint8 unsigned char #endif #ifndef uint32 #define uint32 unsigned long int #endif typedef struct { uint32 total[2]; uint32 state[5]; uint8 buffer[64]; } sha1_context; void sha1_starts( sha1_context *ctx ); void sha1_update( sha1_context *ctx, uint8 *input, uint32 length ); void sha1_finish( sha1_context *ctx, uint8 digest[20] ); #endif /* sha1.h */ filehash/src/hash.c0000644000175100001440000000353413071742312013736 0ustar hornikusers#include #include #include "sha1.h" /* * This code is adapted from the 'digest.c' code in the 'digest' * package by Dirk Eddelbuettel with contributions by * Antoine Lucas, Jarek Tuszynski, Henrik Bengtsson and Simon Urbanek */ SEXP sha1_object(SEXP object, SEXP skip_bytes) { char output[41]; /* SHA-1 is 40 bytes + '\0' */ int i, skip; SEXP result; sha1_context ctx; unsigned char buffer[20]; Rbyte *data; int nChar = length(object); PROTECT(object = coerceVector(object, RAWSXP)); data = RAW(object); PROTECT(skip_bytes = coerceVector(skip_bytes, INTSXP)); skip = INTEGER(skip_bytes)[0]; if(skip > 0) { if(skip >= nChar) nChar = 0; else { nChar -= skip; data += skip; } } sha1_starts(&ctx); sha1_update(&ctx, (uint8 *) data, nChar); sha1_finish(&ctx, buffer); for(i=0; i < 20; i++) sprintf(output + i * 2, "%02x", buffer[i]); PROTECT(result = allocVector(STRSXP, 1)); SET_STRING_ELT(result, 0, mkChar(output)); UNPROTECT(3); return result; } SEXP sha1_file(SEXP filename, SEXP skip_bytes) { char output[41]; /* SHA-1 is 40 bytes + '\0' */ int nChar, i, skip; FILE *fp; SEXP result; sha1_context ctx; unsigned char buf[1024]; unsigned char sha1sum[20]; PROTECT(skip_bytes = coerceVector(skip_bytes, INTSXP)); PROTECT(filename = coerceVector(filename, STRSXP)); skip = INTEGER(skip_bytes)[0]; if(!(fp = fopen(CHAR(STRING_ELT(filename, 0)), "rb"))) error("unable to open input file"); if (skip > 0) fseek(fp, skip, SEEK_SET); sha1_starts(&ctx); while((nChar = fread(buf, 1, sizeof(buf), fp)) > 0) sha1_update(&ctx, buf, nChar); fclose(fp); sha1_finish(&ctx, sha1sum); for(i=0; i < 20; i++) sprintf(output + i * 2, "%02x", sha1sum[i]); PROTECT(result = allocVector(STRSXP, 1)); SET_STRING_ELT(result, 0, mkChar(output)); UNPROTECT(3); return result; } filehash/src/sha1.c0000644000175100001440000002147013071742312013646 0ustar hornikusers/* * FIPS-180-1 compliant SHA-1 implementation, * by Christophe Devine ; * this program is licensed under the GPL. */ #include #include "sha1.h" #define GET_UINT32(n,b,i) \ { \ (n) = ( (uint32) (b)[(i) ] << 24 ) \ | ( (uint32) (b)[(i) + 1] << 16 ) \ | ( (uint32) (b)[(i) + 2] << 8 ) \ | ( (uint32) (b)[(i) + 3] ); \ } #define PUT_UINT32(n,b,i) \ { \ (b)[(i) ] = (uint8) ( (n) >> 24 ); \ (b)[(i) + 1] = (uint8) ( (n) >> 16 ); \ (b)[(i) + 2] = (uint8) ( (n) >> 8 ); \ (b)[(i) + 3] = (uint8) ( (n) ); \ } void sha1_starts( sha1_context *ctx ) { ctx->total[0] = 0; ctx->total[1] = 0; ctx->state[0] = 0x67452301; ctx->state[1] = 0xEFCDAB89; ctx->state[2] = 0x98BADCFE; ctx->state[3] = 0x10325476; ctx->state[4] = 0xC3D2E1F0; } void sha1_process( sha1_context *ctx, uint8 data[64] ) { uint32 temp, W[16], A, B, C, D, E; GET_UINT32( W[0], data, 0 ); GET_UINT32( W[1], data, 4 ); GET_UINT32( W[2], data, 8 ); GET_UINT32( W[3], data, 12 ); GET_UINT32( W[4], data, 16 ); GET_UINT32( W[5], data, 20 ); GET_UINT32( W[6], data, 24 ); GET_UINT32( W[7], data, 28 ); GET_UINT32( W[8], data, 32 ); GET_UINT32( W[9], data, 36 ); GET_UINT32( W[10], data, 40 ); GET_UINT32( W[11], data, 44 ); GET_UINT32( W[12], data, 48 ); GET_UINT32( W[13], data, 52 ); GET_UINT32( W[14], data, 56 ); GET_UINT32( W[15], data, 60 ); #define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) #define R(t) \ ( \ temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ \ W[(t - 14) & 0x0F] ^ W[ t & 0x0F], \ ( W[t & 0x0F] = S(temp,1) ) \ ) #define P(a,b,c,d,e,x) \ { \ e += S(a,5) + F(b,c,d) + K + x; b = S(b,30); \ } A = ctx->state[0]; B = ctx->state[1]; C = ctx->state[2]; D = ctx->state[3]; E = ctx->state[4]; #define F(x,y,z) (z ^ (x & (y ^ z))) #define K 0x5A827999 P( A, B, C, D, E, W[0] ); P( E, A, B, C, D, W[1] ); P( D, E, A, B, C, W[2] ); P( C, D, E, A, B, W[3] ); P( B, C, D, E, A, W[4] ); P( A, B, C, D, E, W[5] ); P( E, A, B, C, D, W[6] ); P( D, E, A, B, C, W[7] ); P( C, D, E, A, B, W[8] ); P( B, C, D, E, A, W[9] ); P( A, B, C, D, E, W[10] ); P( E, A, B, C, D, W[11] ); P( D, E, A, B, C, W[12] ); P( C, D, E, A, B, W[13] ); P( B, C, D, E, A, W[14] ); P( A, B, C, D, E, W[15] ); P( E, A, B, C, D, R(16) ); P( D, E, A, B, C, R(17) ); P( C, D, E, A, B, R(18) ); P( B, C, D, E, A, R(19) ); #undef K #undef F #define F(x,y,z) (x ^ y ^ z) #define K 0x6ED9EBA1 P( A, B, C, D, E, R(20) ); P( E, A, B, C, D, R(21) ); P( D, E, A, B, C, R(22) ); P( C, D, E, A, B, R(23) ); P( B, C, D, E, A, R(24) ); P( A, B, C, D, E, R(25) ); P( E, A, B, C, D, R(26) ); P( D, E, A, B, C, R(27) ); P( C, D, E, A, B, R(28) ); P( B, C, D, E, A, R(29) ); P( A, B, C, D, E, R(30) ); P( E, A, B, C, D, R(31) ); P( D, E, A, B, C, R(32) ); P( C, D, E, A, B, R(33) ); P( B, C, D, E, A, R(34) ); P( A, B, C, D, E, R(35) ); P( E, A, B, C, D, R(36) ); P( D, E, A, B, C, R(37) ); P( C, D, E, A, B, R(38) ); P( B, C, D, E, A, R(39) ); #undef K #undef F #define F(x,y,z) ((x & y) | (z & (x | y))) #define K 0x8F1BBCDC P( A, B, C, D, E, R(40) ); P( E, A, B, C, D, R(41) ); P( D, E, A, B, C, R(42) ); P( C, D, E, A, B, R(43) ); P( B, C, D, E, A, R(44) ); P( A, B, C, D, E, R(45) ); P( E, A, B, C, D, R(46) ); P( D, E, A, B, C, R(47) ); P( C, D, E, A, B, R(48) ); P( B, C, D, E, A, R(49) ); P( A, B, C, D, E, R(50) ); P( E, A, B, C, D, R(51) ); P( D, E, A, B, C, R(52) ); P( C, D, E, A, B, R(53) ); P( B, C, D, E, A, R(54) ); P( A, B, C, D, E, R(55) ); P( E, A, B, C, D, R(56) ); P( D, E, A, B, C, R(57) ); P( C, D, E, A, B, R(58) ); P( B, C, D, E, A, R(59) ); #undef K #undef F #define F(x,y,z) (x ^ y ^ z) #define K 0xCA62C1D6 P( A, B, C, D, E, R(60) ); P( E, A, B, C, D, R(61) ); P( D, E, A, B, C, R(62) ); P( C, D, E, A, B, R(63) ); P( B, C, D, E, A, R(64) ); P( A, B, C, D, E, R(65) ); P( E, A, B, C, D, R(66) ); P( D, E, A, B, C, R(67) ); P( C, D, E, A, B, R(68) ); P( B, C, D, E, A, R(69) ); P( A, B, C, D, E, R(70) ); P( E, A, B, C, D, R(71) ); P( D, E, A, B, C, R(72) ); P( C, D, E, A, B, R(73) ); P( B, C, D, E, A, R(74) ); P( A, B, C, D, E, R(75) ); P( E, A, B, C, D, R(76) ); P( D, E, A, B, C, R(77) ); P( C, D, E, A, B, R(78) ); P( B, C, D, E, A, R(79) ); #undef K #undef F ctx->state[0] += A; ctx->state[1] += B; ctx->state[2] += C; ctx->state[3] += D; ctx->state[4] += E; } void sha1_update( sha1_context *ctx, uint8 *input, uint32 length ) { uint32 left, fill; if( ! length ) return; left = ctx->total[0] & 0x3F; fill = 64 - left; ctx->total[0] += length; ctx->total[0] &= 0xFFFFFFFF; if( ctx->total[0] < length ) ctx->total[1]++; if( left && length >= fill ) { memcpy( (void *) (ctx->buffer + left), (void *) input, fill ); sha1_process( ctx, ctx->buffer ); length -= fill; input += fill; left = 0; } while( length >= 64 ) { sha1_process( ctx, input ); length -= 64; input += 64; } if( length ) { memcpy( (void *) (ctx->buffer + left), (void *) input, length ); } } static uint8 sha1_padding[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; void sha1_finish( sha1_context *ctx, uint8 digest[20] ) { uint32 last, padn; uint32 high, low; uint8 msglen[8]; high = ( ctx->total[0] >> 29 ) | ( ctx->total[1] << 3 ); low = ( ctx->total[0] << 3 ); PUT_UINT32( high, msglen, 0 ); PUT_UINT32( low, msglen, 4 ); last = ctx->total[0] & 0x3F; padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); sha1_update( ctx, sha1_padding, padn ); sha1_update( ctx, msglen, 8 ); PUT_UINT32( ctx->state[0], digest, 0 ); PUT_UINT32( ctx->state[1], digest, 4 ); PUT_UINT32( ctx->state[2], digest, 8 ); PUT_UINT32( ctx->state[3], digest, 12 ); PUT_UINT32( ctx->state[4], digest, 16 ); } #ifdef TEST #include #include /* * those are the standard FIPS-180-1 test vectors */ static char *msg[] = { "abc", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", NULL }; static char *val[] = { "a9993e364706816aba3e25717850c26c9cd0d89d", "84983e441c3bd26ebaae4aa1f95129e5e54670f1", "34aa973cd4c4daa4f61eeb2bdbad27316534016f" }; int main( int argc, char *argv[] ) { FILE *f; int i, j; char output[41]; sha1_context ctx; unsigned char buf[1000]; unsigned char sha1sum[20]; if( argc < 2 ) { printf( "\n SHA-1 Validation Tests:\n\n" ); for( i = 0; i < 3; i++ ) { printf( " Test %d ", i + 1 ); sha1_starts( &ctx ); if( i < 2 ) { sha1_update( &ctx, (uint8 *) msg[i], strlen( msg[i] ) ); } else { memset( buf, 'a', 1000 ); for( j = 0; j < 1000; j++ ) { sha1_update( &ctx, (uint8 *) buf, 1000 ); } } sha1_finish( &ctx, sha1sum ); for( j = 0; j < 20; j++ ) { sprintf( output + j * 2, "%02x", sha1sum[j] ); } if( memcmp( output, val[i], 40 ) ) { printf( "failed!\n" ); return( 1 ); } printf( "passed.\n" ); } printf( "\n" ); } else { if( ! ( f = fopen( argv[1], "rb" ) ) ) { perror( "fopen" ); return( 1 ); } sha1_starts( &ctx ); while( ( i = fread( buf, 1, sizeof( buf ), f ) ) > 0 ) { sha1_update( &ctx, buf, i ); } sha1_finish( &ctx, sha1sum ); for( j = 0; j < 20; j++ ) { printf( "%02x", sha1sum[j] ); } printf( " %s\n", argv[1] ); } return( 0 ); } #endif filehash/src/lockfile.c0000644000175100001440000000062013071742312014574 0ustar hornikusers#include #include #include #include SEXP lock_file(SEXP filename) { int fd; SEXP status; if(!isString(filename)) error("'filename' should be character"); PROTECT(status = allocVector(INTSXP, 1)); fd = open(CHAR(STRING_ELT(filename, 0)), O_WRONLY | O_CREAT | O_EXCL, 0666); INTEGER(status)[0] = fd; close(fd); UNPROTECT(1); return status; } filehash/src/init.c0000644000175100001440000000126013071742312013750 0ustar hornikusers#include // for NULL #include #include SEXP sha1_object(SEXP object, SEXP skip_bytes); SEXP sha1_file(SEXP filename, SEXP skip_bytes); SEXP read_key_map(SEXP filename, SEXP map, SEXP filesize, SEXP pos); SEXP lock_file(SEXP filename); static const R_CallMethodDef CallEntries[] = { {"lock_file", (DL_FUNC) &lock_file, 1}, {"read_key_map", (DL_FUNC) &read_key_map, 4}, {"sha1_object", (DL_FUNC) &sha1_object, 2}, {"sha1_file", (DL_FUNC) &sha1_file, 2}, {NULL, NULL, 0} }; void R_init_filehash(DllInfo *info) { R_registerRoutines(info, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(info, FALSE); R_forceSymbols(info, TRUE); } filehash/src/readKeyMap.c0000644000175100001440000000277613071742312015044 0ustar hornikusers#define NEED_CONNECTION_PSTREAMS #include #include SEXP read_key_map(SEXP filename, SEXP map, SEXP filesize, SEXP pos) { SEXP key, datalen; FILE *fp; int status, len; struct R_inpstream_st in; if(!isEnvironment(map)) error("'map' should be an environment"); if(!isString(filename)) error("'filename' should be character"); PROTECT(filesize = coerceVector(filesize, INTSXP)); PROTECT(pos = coerceVector(pos, INTSXP)); fp = fopen(CHAR(STRING_ELT(filename, 0)), "rb"); if(INTEGER(pos)[0] > 0) { status = fseek(fp, INTEGER(pos)[0], SEEK_SET); if(status < 0) error("problem with initial file pointer seek"); } /* Initialize the incoming R file stream */ R_InitFileInPStream(&in, fp, R_pstream_any_format, NULL, NULL); while(INTEGER(pos)[0] < INTEGER(filesize)[0]) { PROTECT(key = R_Unserialize(&in)); PROTECT(datalen = R_Unserialize(&in)); len = INTEGER(datalen)[0]; /* calculate the position of file pointer */ INTEGER(pos)[0] = ftell(fp); if(len <= 0) { /* key has been deleted; set pos to NULL */ defineVar(install(CHAR(STRING_ELT(key, 0))), R_NilValue, map); UNPROTECT(2); continue; } /* create a new entry in the key map */ defineVar(install(CHAR(STRING_ELT(key, 0))), duplicate(pos), map); /* advance to the next key */ status = fseek(fp, len, SEEK_CUR); if(status < 0) { fclose(fp); error("problem with seek"); } INTEGER(pos)[0] = INTEGER(pos)[0] + len; UNPROTECT(2); } UNPROTECT(2); fclose(fp); return map; } filehash/NAMESPACE0000644000175100001440000000230313071157447013301 0ustar hornikusersuseDynLib(filehash, .registration = TRUE, .fixes = "C_") import(methods) ## Classes exportClasses( "filehash", "filehashRDS", "filehashDB1" ) ## Primary interface exportMethods( "dbInsert", "dbFetch", "dbExists", "dbList", "dbDelete", "dbReorganize", "dbUnlink", "dbMultiFetch", "dbCreate", "dbInit", "dbLoad", "dbLazyLoad" ) exportMethods("[[", "[", "[[<-", "$<-", "$") export( "filehashOption", "registerFormatDB", "filehashFormats" ) ## Miscellaneous functions exportMethods( "show", "with", "coerce", "lapply", "names", "length" ) export( "dumpDF", "dumpObjects", "db2env", "dumpImage", "dumpList", "dumpEnv" ) ## Stack and Queue stuff exportClasses("stack", "queue") exportMethods("isEmpty", "top", "push", "pop") exportMethods("mpush") export("createQ", "initQ") export("createS", "initS") filehash/R/0000755000175100001440000000000013071157023012253 5ustar hornikusersfilehash/R/coerce.R0000644000175100001440000000305313042123362013634 0ustar hornikusers###################################################################### ## Copyright (C) 2006, Roger D. Peng ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ## 02110-1301, USA ##################################################################### toDBType <- function(from, type, dbpath = NULL) { if(is.null(dbpath)) dbpath <- dbName(from) if(!dbCreate(dbpath, type = type)) stop("could not create ", type, " database") db <- dbInit(dbpath, type = type) keys <- dbList(from) for(key in keys) dbInsert(db, key, dbFetch(from, key)) invisible(db) } setAs("filehashDB1", "filehashRDS", function(from) { dbpath <- paste(dbName(from), "RDS", sep = "") toDBType(from, "RDS", dbpath) }) setAs("filehashDB1", "list", function(from) { keys <- dbList(from) dbMultiFetch(from, keys) }) filehash/R/queue.R0000644000175100001440000000514613042123362013525 0ustar hornikuserssetClass("queue", representation(queue = "filehashDB1", name = "character") ) setMethod("show", "queue", function(object) { cat(gettextf("\n", object@name)) invisible(object) }) createQ <- function(filename) { dbCreate(filename, "DB1") queue <- dbInit(filename, "DB1") dbInsert(queue, "head", NULL) dbInsert(queue, "tail", NULL) new("queue", queue = queue, name = filename) } initQ <- function(filename) { new("queue", queue = dbInit(filename, "DB1"), name = filename) } ## Public setGeneric("pop", function(db, ...) standardGeneric("pop")) setGeneric("push", function(db, val, ...) standardGeneric("push")) setGeneric("isEmpty", function(db, ...) standardGeneric("isEmpty")) setGeneric("top", function(db, ...) standardGeneric("top")) ################################################################################ ## Methods setMethod("lockFile", "queue", function(db, ...) { paste(db@name, "qlock", sep = ".") }) setMethod("push", c("queue", "ANY"), function(db, val, ...) { ## Create a new tail node node <- list(value = val, nextkey = NULL) key <- sha1(node) createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) if(isEmpty(db)) dbInsert(db@queue, "head", key) else { ## Convert tail node to regular node tailkey <- dbFetch(db@queue, "tail") oldtail <- dbFetch(db@queue, tailkey) oldtail$nextkey <- key dbInsert(db@queue, tailkey, oldtail) } ## Insert new node and point tail to new node dbInsert(db@queue, key, node) dbInsert(db@queue, "tail", key) }) setMethod("isEmpty", "queue", function(db) { is.null(dbFetch(db@queue, "head")) }) setMethod("top", "queue", function(db, ...) { createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) if(isEmpty(db)) stop("queue is empty") h <- dbFetch(db@queue, "head") node <- dbFetch(db@queue, h) node$value }) setMethod("pop", "queue", function(db, ...) { createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) if(isEmpty(db)) stop("queue is empty") h <- dbFetch(db@queue, "head") node <- dbFetch(db@queue, h) dbInsert(db@queue, "head", node$nextkey) dbDelete(db@queue, h) node$value }) filehash/R/filehash-RDS.R0000644000175100001440000001551513042123362014613 0ustar hornikusers###################################################################### ## Copyright (C) 2006, Roger D. Peng ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ## 02110-1301, USA ##################################################################### ################################################################################ ## Class 'filehashRDS' setClass("filehashRDS", representation(dir = "character"), contains = "filehash" ) setValidity("filehashRDS", function(object) { if(length(object@dir) != 1) return("only one directory should be set in 'dir'") if(!file.exists(object@dir)) return(gettextf("directory '%s' does not exist", object@dir)) TRUE }) createRDS <- function(dbName) { if(!file.exists(dbName)) { status <- dir.create(dbName) if(!status) stop(gettextf("unable to create database directory '%s'", dbName)) } else message(gettextf("database '%s' already exists", dbName)) TRUE } initializeRDS <- function(dbName) { ## Trailing '/' causes a problem in Windows? dbName <- sub("/$", "", dbName, perl = TRUE) new("filehashRDS", dir = normalizePath(dbName), name = basename(dbName)) } ## For case-insensitive file systems, objects with the same name but ## differ by capitalization might get clobbered. `mangleName()' ## inserts a "@" before each capital letter and `unMangleName()' ## reverses the operation. mangleName <- function(oname) { if(any(grep("@",oname,fixed=TRUE))) stop("RDS format cannot cope with objects with @ characters", " in their names") gsub("([A-Z])", "@\\1", oname, perl = TRUE) } unMangleName <- function(mname) { gsub("@", "", mname, fixed = TRUE) } ## Function for mapping a key to a path on the filesystem setGeneric("objectFile", function(db, key) standardGeneric("objectFile")) setMethod("objectFile", signature(db = "filehashRDS", key = "character"), function(db, key) { file.path(db@dir, mangleName(key)) }) ################################################################################ ## Interface functions setMethod("dbInsert", signature(db = "filehashRDS", key = "character", value = "ANY"), function(db, key, value, safe = TRUE, ...) { writefile <- if(safe) tempfile() else objectFile(db, key) con <- gzfile(writefile, "wb") writestatus <- tryCatch({ serialize(value, con) }, condition = function(cond) { cond }, finally = { close(con) }) if(inherits(writestatus, "condition")) stop(gettextf("unable to write object '%s'", key)) if(!safe) return(invisible(!inherits(writestatus, "condition"))) cpstatus <- file.copy(writefile, objectFile(db, key), overwrite = TRUE) if(!cpstatus) stop(gettextf("unable to insert object '%s'", key)) else { rmstatus <- file.remove(writefile) if(!rmstatus) warning("unable to remove temporary file") } invisible(cpstatus) }) setMethod("dbFetch", signature(db = "filehashRDS", key = "character"), function(db, key, ...) { ## Create filename from key ofile <- objectFile(db, key) ## Open connection val <- tryCatch({ con<-gzfile(ofile) # note it is necessary to split creating and opening # the connection into two steps so that the connection # can be closed/destroyed successfully if ofile does # not exist (avoiding connection leaks). open(con,"rb") ## Read data unserialize(con) }, condition = function(cond) { cond }, finally = { close(con) }) if(inherits(val, "condition")) stop(gettextf("unable to obtain value for key '%s'", key)) val }) setMethod("dbMultiFetch", signature(db = "filehashRDS", key = "character"), function(db, key, ...) { r <- lapply(key, function(k) dbFetch(db, k)) names(r) <- key r }) setMethod("dbExists", signature(db = "filehashRDS", key = "character"), function(db, key, ...) { key %in% dbList(db) }) setMethod("dbList", "filehashRDS", function(db, ...) { ## list all keys/files in the database fileList <- dir(db@dir, all.files = TRUE, full.names = TRUE) use <- !file.info(fileList)$isdir fileList <- basename(fileList[use]) unMangleName(fileList) }) setMethod("dbDelete", signature(db = "filehashRDS", key = "character"), function(db, key, ...) { ofile <- objectFile(db, key) ## remove/delete the file status <- file.remove(ofile) invisible(isTRUE(all(status))) }) setMethod("dbUnlink", "filehashRDS", function(db, ...) { ## delete the entire database directory d <- db@dir status <- unlink(d, recursive = TRUE) invisible(status) }) filehash/R/dump.R0000644000175100001440000000475513042123362013353 0ustar hornikusers###################################################################### ## Copyright (C) 2006--2008, Roger D. Peng ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ## 02110-1301, USA ##################################################################### dumpEnv <- function(env, dbName) { keys <- ls(env, all.names = TRUE) dumpObjects(list = keys, dbName = dbName, envir = env) } dumpImage <- function(dbName = "Rworkspace", type = NULL) { dumpObjects(list = ls(envir = globalenv(), all.names = TRUE), dbName = dbName, type = type, envir = globalenv()) } dumpObjects <- function(..., list = character(0), dbName, type = NULL, envir = parent.frame()) { names <- as.character(substitute(list(...)))[-1] list <- c(list, names) if(!dbCreate(dbName, type)) stop("could not create database file") db <- dbInit(dbName, type) for(i in seq(along = list)) dbInsert(db, list[i], get(list[i], envir)) db } dumpDF <- function(data, dbName = NULL, type = NULL) { if(is.null(dbName)) dbName <- as.character(substitute(data)) dumpList(as.list(data), dbName = dbName, type = type) } dumpList <- function(data, dbName = NULL, type = NULL) { if(!is.list(data)) stop("'data' must be a list") vnames <- names(data) if(is.null(vnames) || isTRUE("" %in% vnames)) stop("list must have non-empty names") if(is.null(dbName)) dbName <- as.character(substitute(data)) if(!dbCreate(dbName, type)) stop("could not create database file") db <- dbInit(dbName, type) for(i in seq(along = vnames)) dbInsert(db, vnames[i], data[[vnames[i]]]) db } filehash/R/hash.R0000644000175100001440000000042413071156664013333 0ustar hornikuserssha1 <- function(object, skip = 14L) { ## Setting 'skip = 14' gives us the same results as ## 'digest(object, "sha1")' bytes <- serialize(object, NULL) .Call(C_sha1_object, bytes, skip) } sha1_file <- function(filename, skip = 0L) { .Call(C_sha1_file, filename, skip) } filehash/R/filehash.R0000644000175100001440000002322513042123362014162 0ustar hornikusers###################################################################### ## Copyright (C) 2006, Roger D. Peng ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ## 02110-1301, USA ##################################################################### ###################################################################### ## Class 'filehash' setClass("filehash", representation(name = "character")) setValidity("filehash", function(object) { if(length(object@name) == 0) "database name has length 0" else TRUE }) setGeneric("dbName", function(db) standardGeneric("dbName")) setMethod("dbName", "filehash", function(db) db@name) setMethod("show", "filehash", function(object) { if(length(object@name) == 0) stop("database does not have a name") cat(gettextf("'%s' database '%s'\n", as.character(class(object)), object@name)) }) ###################################################################### registerFormatDB <- function(name, funlist) { if(!all(c("initialize", "create") %in% names(funlist))) stop("need both 'initialize' and 'create' functions in 'funlist'") r <- list(list(create = funlist[["create"]], initialize = funlist[["initialize"]])) names(r) <- name do.call("filehashFormats", r) TRUE } filehashFormats <- function(...) { args <- list(...) n <- names(args) for(n in names(args)) assign(n, args[[n]], .filehashFormats) current <- as.list(.filehashFormats) if(length(args) == 0) current else invisible(current) } ###################################################################### ## Create necessary database files. On successful creation, return ## TRUE. If the database already exists, don't do anything but return ## TRUE (and print a message). If there's any other strange ## condition, return FALSE. dbStartup <- function(dbName, type, action = c("initialize", "create")) { action <- match.arg(action) validFormat <- type %in% names(filehashFormats()) if(!validFormat) stop(gettextf("'%s' not a valid database format", type)) formatList <- filehashFormats()[[type]] doFUN <- formatList[[action]] if(!is.function(doFUN)) stop(gettextf("'%s' function for database format '%s' is not valid", action, type)) doFUN(dbName) } setGeneric("dbCreate", function(db, ...) standardGeneric("dbCreate")) setMethod("dbCreate", "ANY", function(db, type = NULL, ...) { if(is.null(type)) type <- filehashOption()$defaultType dbStartup(db, type, "create") }) setGeneric("dbInit", function(db, ...) standardGeneric("dbInit")) setMethod("dbInit", "ANY", function(db, type = NULL, ...) { if(is.null(type)) type <- filehashOption()$defaultType dbStartup(db, type, "initialize") }) ###################################################################### ## Set options and retrieve list of options filehashOption <- function(...) { args <- list(...) n <- names(args) for(n in names(args)) assign(n, args[[n]], .filehashOptions) current <- as.list(.filehashOptions) if(length(args) == 0) current else invisible(current) } ###################################################################### ## Load active bindings into an environment setGeneric("dbLoad", function(db, ...) standardGeneric("dbLoad")) setMethod("dbLoad", "filehash", function(db, env = parent.frame(2), keys = NULL, ...) { if(is.null(keys)) keys <- dbList(db) else if(!is.character(keys)) stop("'keys' should be a character vector") active <- sapply(keys, function(k) { exists(k, env, inherits = FALSE) }) if(any(active)) { warning("keys with active/regular bindings ignored: ", paste(sQuote(keys[active]), collapse = ", ")) keys <- keys[!active] } make.f <- function(k) { key <- k function(value) { if(!missing(value)) { dbInsert(db, key, value) invisible(value) } else { obj <- dbFetch(db, key) obj } } } for(k in keys) makeActiveBinding(k, make.f(k), env) invisible(keys) }) setGeneric("dbLazyLoad", function(db, ...) standardGeneric("dbLazyLoad")) setMethod("dbLazyLoad", "filehash", function(db, env = parent.frame(2), keys = NULL, ...) { if(is.null(keys)) keys <- dbList(db) else if(!is.character(keys)) stop("'keys' should be a character vector") wrap <- function(x, env) { key <- x delayedAssign(x, dbFetch(db, key), environment(), env) } for(k in keys) wrap(k, env) invisible(keys) }) ## Load active bindings into an environment and return the environment db2env <- function(db) { if(is.character(db)) db <- dbInit(db) ## use the default type env <- new.env(hash = TRUE) dbLoad(db, env) env } ###################################################################### ## Other methods setGeneric("names") setMethod("names", "filehash", function(x) { dbList(x) }) setGeneric("length") setMethod("length", "filehash", function(x) { length(dbList(x)) }) setAs("filehash", "list", function(from) { env <- new.env(hash = TRUE) dbLoad(from, env) as.list(env, all.names = TRUE) }) setGeneric("with") setMethod("with", "filehash", function(data, expr, ...) { env <- db2env(data) eval(substitute(expr), env, enclos = parent.frame()) }) setGeneric("lapply") setMethod("lapply", signature(X = "filehash"), function(X, FUN, ..., keep.names = TRUE) { FUN <- match.fun(FUN) keys <- dbList(X) rval <- vector("list", length = length(keys)) for(i in seq(along = keys)) { obj <- dbFetch(X, keys[i]) rval[[i]] <- FUN(obj, ...) } if(keep.names) names(rval) <- keys rval }) ###################################################################### ## Database interface setGeneric("dbMultiFetch", function(db, key, ...) { standardGeneric("dbMultiFetch") }) setGeneric("dbInsert", function(db, key, value, ...) { standardGeneric("dbInsert") }) setGeneric("dbFetch", function(db, key, ...) standardGeneric("dbFetch")) setGeneric("dbExists", function(db, key, ...) standardGeneric("dbExists")) setGeneric("dbList", function(db, ...) standardGeneric("dbList")) setGeneric("dbDelete", function(db, key, ...) standardGeneric("dbDelete")) setGeneric("dbReorganize", function(db, ...) standardGeneric("dbReorganize")) setGeneric("dbUnlink", function(db, ...) standardGeneric("dbUnlink")) ## Other setOldClass(c("file", "connection")) setGeneric("lockFile", function(db, ...) standardGeneric("lockFile")) ###################################################################### ## Extractor/replacement setMethod("[[", signature(x = "filehash", i = "character", j = "missing"), function(x, i, j) { dbFetch(x, i) }) setMethod("$", signature(x = "filehash"), function(x, name) { dbFetch(x, name) }) setReplaceMethod("[[", signature(x = "filehash", i = "character", j = "missing"), function(x, i, j, value) { dbInsert(x, i, value) x }) setReplaceMethod("$", signature(x = "filehash"), function(x, name, value) { dbInsert(x, name, value) x }) ## Need to define these because they're not automatically caught. ## Don't need this if R >= 2.4.0. setReplaceMethod("[[", signature(x = "filehash", i = "numeric", j = "missing"), function(x, i, j, value) { stop("numeric indices not allowed") }) setMethod("[[", signature(x = "filehash", i = "numeric", j = "missing"), function(x, i, j) { stop("numeric indices not allowed") }) setMethod("[", signature(x = "filehash", i = "character", j = "missing", drop = "missing"), function(x, i , j, drop) { dbMultiFetch(x, i) }) filehash/R/filehash-DB1.R0000644000175100001440000003271113071157012014527 0ustar hornikusers###################################################################### ## Copyright (C) 2006--2008, Roger D. Peng ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ## 02110-1301, USA ##################################################################### ###################################################################### ## Class 'filehashDB1' ## Database entries ## ## File format: [key] [nbytes data] [data] ## serialized serialized raw bytes (serialized) ## ###################################################################### ## 'meta' is a list of functions for updating the file size of the ## database and the file map. setClass("filehashDB1", representation(datafile = "character", meta = "list"), contains = "filehash" ) setValidity("filehashDB1", function(object) { if(!file.exists(object@datafile)) return(gettextf("datafile '%s' does not exist", datafile)) TRUE }) createDB1 <- function(dbName) { if(!hasWorkingFtell()) stop("need working 'ftell()' to use 'DB1' format") if(file.exists(dbName)) { message(gettextf("database '%s' already exists", dbName)) return(TRUE) } status <- file.create(dbName) if(!status) stop(gettextf("unable to create database file '%s'", dbName)) TRUE } makeMetaEnv <- function(filename) { dbmap <- NULL ## 'NULL' indicates the map needs to be read dbfilesize <- file.info(filename)$size updatesize <- function(size) { dbfilesize <<- size } updatemap <- function(map) { dbmap <<- map } getsize <- function() { dbfilesize } getmap <- function() { dbmap } list(updatesize = updatesize, updatemap = updatemap, getmap = getmap, getsize = getsize) } initializeDB1 <- function(dbName) { if(!hasWorkingFtell()) stop("need working 'ftell()' to use DB1 format") dbName <- normalizePath(dbName) new("filehashDB1", datafile = dbName, meta = makeMetaEnv(dbName), name = basename(dbName) ) } readKeyMap <- function(con, map = NULL, pos = 0) { if(is.null(map)) { ## using 'hash = TRUE' is critical because it can have a major ## impact on performance for large databases map <- new.env(hash = TRUE, parent = emptyenv()) pos <- 0 } if(pos < 0) stop("'pos' cannot be negative") filename <- path.expand(summary(con)$description) filesize <- file.info(filename)$size if(pos > filesize) stop("'pos' cannot be greater than file size") .Call(C_read_key_map, filename, map, filesize, pos) } readSingleKey <- function(con, map, key) { start <- map[[key]] if(is.null(start)) stop(gettextf("unable to obtain value for key '%s'", key)) seek(con, start, rw = "read") unserialize(con) } readKeys <- function(con, map, keys) { r <- lapply(keys, function(key) readSingleKey(con, map, key)) names(r) <- keys r } gotoEndPos <- function(con) { ## Move connection to the end seek(con, 0, "end") seek(con) } writeNullKeyValue <- function(con, key) { writestart <- gotoEndPos(con) handler <- function(cond) { ## Rewind the file back to where writing began and truncate at ## that position seek(con, writestart, "start", "write") truncate(con) cond } tryCatch({ serialize(key, con) len <- as.integer(-1) serialize(len, con) }, interrupt = handler, error = handler, finally = { flush(con) }) } writeKeyValue <- function(con, key, value) { writestart <- gotoEndPos(con) handler <- function(cond) { ## Rewind the file back to where writing began and ## truncate at that position; this is probably a bad ## idea for files > 2GB seek(con, writestart, "start", "write") truncate(con) cond } tryCatch({ serialize(key, con) byteData <- serialize(value, NULL) len <- length(byteData) serialize(len, con) writeBin(byteData, con) }, interrupt = handler, error = handler, finally = { flush(con) }) } setMethod("lockFile", "file", function(db, ...) { ## Use 3 underscores for lock file sprintf("%s___LOCK", summary(db)$description) }) createLockFile <- function(name) { if(.Platform$OS.type != "windows") status <- .Call(C_lock_file, name) else { ## TODO: are these optimal values for max.attempts ## and sleep.duration? max.attempts <- 4 sleep.duration <- 0.5 attempts <- 0 status <- -1 while ((attempts <= max.attempts) && ! isTRUE(status >= 0)) { attempts <- attempts + 1 status <- .Call(C_lock_file, name) if(!isTRUE(status >= 0)) Sys.sleep(sleep.duration) } } if(!isTRUE(status >= 0)) stop("cannot create lock file ", sQuote(name)) TRUE } deleteLockFile <- function(name) { if(!file.remove(name)) stop(paste('cannot remove lock file "', name, '"', sep='')) TRUE } ################################################################################ ## Internal utilities filesize <- gotoEndPos setGeneric("checkMap", function(db, ...) standardGeneric("checkMap")) setMethod("checkMap", "filehashDB1", function(db, filecon, ...) { old.size <- db@meta$getsize() cur.size <- tryCatch({ filesize(filecon) }, error = function(err) { old.size }) size.change <- old.size != cur.size map <- getMap(db) map0 <- map if(is.null(map)) map <- readKeyMap(filecon) else if(size.change) { ## Modify 'map.old' directly map <- tryCatch({ readKeyMap(filecon, map, old.size) }, error = function(err) { message(conditionMessage(err)) map0 }) } else map <- map0 if(!identical(map, map0)) { db@meta$updatemap(map) db@meta$updatesize(cur.size) } invisible(db) }) setGeneric("getMap", function(db) standardGeneric("getMap")) setMethod("getMap", "filehashDB1", function(db) { db@meta$getmap() }) ################################################################################ ## Interface functions openDBConn <- function(filename, mode) { con <- try({ file(filename, mode) }, silent = TRUE) if(inherits(con, "try-error")) stop("unable to open connection to database") con } setMethod("dbInsert", signature(db = "filehashDB1", key = "character", value = "ANY"), function(db, key, value, ...) { con <- openDBConn(db@datafile, "ab") on.exit(close(con)) lockname <- lockFile(con) createLockFile(lockname) on.exit(deleteLockFile(lockname), add = TRUE) invisible(writeKeyValue(con, key, value)) }) setMethod("dbFetch", signature(db = "filehashDB1", key = "character"), function(db, key, ...) { con <- openDBConn(db@datafile, "rb") on.exit(close(con)) lockname <- lockFile(con) createLockFile(lockname) on.exit(deleteLockFile(lockname), add = TRUE) checkMap(db, con) map <- getMap(db) val <- readSingleKey(con, map, key) val }) setMethod("dbMultiFetch", signature(db = "filehashDB1", key = "character"), function(db, key, ...) { con <- openDBConn(db@datafile, "rb") on.exit(close(con)) lockname <- lockFile(con) createLockFile(lockname) on.exit(deleteLockFile(lockname), add = TRUE) checkMap(db, con) map <- getMap(db) readKeys(con, map, key) }) setMethod("dbExists", signature(db = "filehashDB1", key = "character"), function(db, key, ...) { dbkeys <- dbList(db) key %in% dbkeys }) setMethod("dbList", "filehashDB1", function(db, ...) { con <- openDBConn(db@datafile, "rb") on.exit(close(con)) lockname <- lockFile(con) createLockFile(lockname) on.exit(deleteLockFile(lockname), add = TRUE) checkMap(db, con) map <- getMap(db) if(length(map) == 0) character(0) else { keys <- as.list(map, all.names = TRUE) use <- !sapply(keys, is.null) names(keys[use]) } }) setMethod("dbDelete", signature(db = "filehashDB1", key = "character"), function(db, key, ...) { con <- openDBConn(db@datafile, "ab") on.exit(close(con)) lockname <- lockFile(con) createLockFile(lockname) on.exit(deleteLockFile(lockname), add = TRUE) invisible(writeNullKeyValue(con, key)) }) setMethod("dbUnlink", "filehashDB1", function(db, ...) { file.remove(db@datafile) }) reorganizeDB <- function(db, ...) { datafile <- db@datafile ## Find a temporary file name tempdata <- paste(datafile, "Tmp", sep = "") i <- 0 while(file.exists(tempdata)) { i <- i + 1 tempdata <- paste(datafile, "Tmp", i, sep = "") } if(!dbCreate(tempdata, type = "DB1")) { warning("could not create temporary database") return(FALSE) } on.exit(file.remove(tempdata)) tempdb <- dbInit(tempdata, type = "DB1") keys <- dbList(db) ## Copy all keys to temporary database nkeys <- length(keys) cat("Reorganizing database: ") for(i in seq_along(keys)) { key <- keys[i] msg <- sprintf("%d%% (%d/%d)", round (100 * i / nkeys), i, nkeys) cat(msg) dbInsert(tempdb, key, dbFetch(db, key)) back <- paste(rep("\b", nchar(msg)), collapse = "") cat(back) } cat("\n") status <- file.rename(tempdata, datafile) if(!isTRUE(status)) { on.exit() warning("temporary database could not be renamed and is left in ", tempdata) return(FALSE) } on.exit() cat("Finished; reload database with 'dbInit'\n") TRUE } setMethod("dbReorganize", "filehashDB1", reorganizeDB) ################################################################################ ## Test system's ftell() hasWorkingFtell <- function() { tfile <- tempfile() con <- file(tfile, "wb") tryCatch({ bytes <- raw(10) begin <- seek(con) if(begin != 0) return(FALSE) writeBin(bytes, con) end <- seek(con) offset <- end - begin isTRUE(offset == 10) }, error = function(e) { FALSE }, finally = { close(con) unlink(tfile) }) } ###################################################################### filehash/R/stack.R0000644000175100001440000000465013042123362013505 0ustar hornikuserssetClass("stack", representation(stack = "filehashDB1", name = "character")) setMethod("show", "stack", function(object) { cat(gettextf("\n", object@name)) invisible(object) }) createS <- function(filename) { dbCreate(filename, "DB1") stack <- dbInit(filename, "DB1") dbInsert(stack, "top", NULL) new("stack", stack = stack, name = filename) } initS <- function(filename) { new("stack", stack = dbInit(filename, "DB1"), name = filename) } setMethod("lockFile", "stack", function(db, ...) { paste(db@name, "slock", sep = ".") }) setMethod("push", c("stack", "ANY"), function(db, val, ...) { node <- list(value = val, nextkey = dbFetch(db@stack, "top")) topkey <- sha1(node) createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) dbInsert(db@stack, topkey, node) dbInsert(db@stack, "top", topkey) }) setGeneric("mpush", function(db, vals, ...) standardGeneric("mpush")) setMethod("mpush", c("stack", "ANY"), function(db, vals, ...) { if(!is.list(vals)) vals <- as.list(vals) createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) topkey <- dbFetch(db@stack, "top") for(i in seq_along(vals)) { node <- list(value = vals[[i]], nextkey = topkey) topkey <- sha1(node) dbInsert(db@stack, topkey, node) dbInsert(db@stack, "top", topkey) } }) setMethod("isEmpty", "stack", function(db, ...) { h <- dbFetch(db@stack, "top") is.null(h) }) setMethod("top", "stack", function(db, ...) { createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) if(isEmpty(db)) stop("stack is empty") h <- dbFetch(db@stack, "top") node <- dbFetch(db@stack, h) node$value }) setMethod("pop", "stack", function(db, ...) { createLockFile(lockFile(db)) on.exit(deleteLockFile(lockFile(db))) if(isEmpty(db)) stop("stack is empty") h <- dbFetch(db@stack, "top") node <- dbFetch(db@stack, h) dbInsert(db@stack, "top", node$nextkey) dbDelete(db@stack, h) node$value }) filehash/R/zzz.R0000644000175100001440000000143513042123362013233 0ustar hornikusers.onLoad <- function(lib, pkg) { assign("defaultType", "DB1", .filehashOptions) for(type in c("DB1", "RDS")) { cname <- paste("create", type, sep = "") iname <- paste("initialize", type, sep = "") r <- list(create = get(cname, mode = "function"), initialize = get(iname, mode="function")) assign(type, r, .filehashFormats) } } .onAttach <- function(lib, pkg) { dcf <- read.dcf(file.path(lib, pkg, "DESCRIPTION")) msg <- gettextf("%s: %s (%s)", dcf[, "Package"], dcf[, "Title"], as.character(dcf[, "Version"])) packageStartupMessage(paste(strwrap(msg), collapse = "\n")) } .filehashOptions <- new.env() .filehashFormats <- new.env() filehash/vignettes/0000755000175100001440000000000013071742312014063 5ustar hornikusersfilehash/vignettes/filehash.Rnw0000644000175100001440000004462313042123362016343 0ustar hornikusers\documentclass{article} %%\VignetteIndexEntry{The filehash Package} %%\VignetteDepends{filehash} \usepackage{charter} \usepackage{courier} \usepackage[noae]{Sweave} \usepackage[margin=1in]{geometry} \usepackage{natbib} \title{Interacting with Data using the \textbf{filehash} Package for R} \author{Roger D. Peng $<$rpeng@jhsph.edu$>$\\\textit{Department of Biostatistics}\\\textit{Johns Hopkins Bloomberg School of Public Health}} \date{} \newcommand{\pkg}{\textbf} \newcommand{\code}{\texttt} \begin{document} \maketitle \begin{abstract} The \pkg{filehash} package for R implements a simple key-value style database where character string keys are associated with data values that are stored on the disk. A simple interface is provided for inserting, retrieving, and deleting data from the database. Utilities are provided that allow \pkg{filehash} databases to be treated much like environments and lists are already used in R. These utilities are provided to encourage interactive and exploratory analysis on large datasets. Three different file formats for representing the database are currently available and new formats can easily be incorporated by third parties for use in the \pkg{filehash} framework. \end{abstract} <>= options(width=60) @ \section{Overview and Motivation} Working with large datasets in R can be cumbersome because of the need to keep objects in physical memory. While many might generally see that as a feature of the system, the need to keep whole objects in memory creates challenges to those who might want to work interactively with large datasets. Here we take a simple definition of ``large dataset'' to be any dataset that cannot be loaded into R as a single R object because of memory limitations. For example, a very large data frame might be too large for all of the columns and rows to be loaded at once. In such a situation, one might load only a subset of the rows or columns, if that is possible. In a key-value database, an arbitrary data object (a ``value'') has a ``key'' associated with it, usually a character string. When one requests the value associated with a particular key, it is the database's job to match up the key with the correct value and return the value to the requester. The most straightforward example of a key-value database in R is the global environment. Every object in R has a name and a value associated with it. When you execute at the R prompt <>= x <- 1 print(x) @ the first line assigns the value 1 to the name/key ``x''. The second line requests the value of ``x'' and prints out 1 to the console. R handles the task of finding the appropriate value for ``x'' by searching through a series of environments, including the namespaces of the packages on the search list. In most cases, R stores the values associated with keys in memory, so that the value of \code{x} in the example above was stored in and retrieved from physical memory. However, the idea of a key-value database can be generalized beyond this particular configuration. For example, as of R 2.0.0, much of the R code for R packages is stored in a lazy-loaded database, where the values are initially stored on disk and loaded into memory on first access~\citep{Rnews:Ripley:2004}. Hence, when R starts up, it uses relatively little memory, while the memory usage increases as more objects are requested. Data could also be stored on other computers (e.g. websites) and retrieved over the network. The general S language concept of a database is described in Chapter 5 of the Green Book~\citep{cham:1998} and earlier in~\cite{cham:1991}. Although the S and R languages have different semantics with respect to how variable names are looked up and bound to values, the general concept of using a key-value database applies to both languages. Duncan Temple Lang has implemented this general database framework for R in the \pkg{RObjectTables} package of Omegahat~\citep{TempleLang:2002}. The \pkg{RObjectTables} package provides an interface for connecting R with arbitrary backend systems, allowing data values to be stored in potentially any format or location. While the package itself does not include a specific implementation, some examples are provided on the package's website. The \pkg{filehash} package provides a full read-write implementation of a key-value database for R. The package does not depend on any external packages (beyond those provided in a standard R installation) or software systems and is written entirely in R, making it readily usable on most platforms. The \pkg{filehash} package can be thought of as a specific implementation of the database concept described in~\cite{cham:1991}, taking a slightly different approach to the problem. Both~\cite{TempleLang:2002} and~\cite{cham:1991} focus on generalizing the notion of ``attach()-ing'' a database in an R/S session so that variable names can be looked up automatically via the search list. The \pkg{filehash} package represents a database as an instance of an S4 class and operates directly on the S4 object via various methods. Key-value databases are sometimes called hash tables and indeed, the name of the package comes from the idea of having a ``file-based hash table''. With \pkg{filehash} the values are stored in a file on the disk rather than in memory. When a user requests the values associated with a key, \pkg{filehash} finds the object on the disk, loads the value into R and returns it to the user. The package offers two formats for storing data on the disk: The values can be stored (1) concatenated together in a single file or (2) separately as a directory of files. \section{Related R packages} There are other packages on CRAN designed specifically to help users work with large datasets. Two packages that come immediately to mind are the \pkg{g.data} package by David Brahm~\citep{brahm:2002} and the \pkg{biglm} package by Thomas Lumley. The \pkg{g.data} package takes advantage of the lazy evaluation mechanism in R via the \code{delayedAssign} function. Briefly, objects are loaded into R as promises to load the actual data associated with an object name. The first time an object is requested, the promise is evaluated and the data are loaded. From then on, the data reside in memory. The mechanism used in \pkg{g.data} is similar to the one used by the lazy-loaded databases described in~\cite{Rnews:Ripley:2004}. The \pkg{biglm} package allows users to fit linear models on datasets that are too large to fit in memory. However, the \pkg{biglm} package does not provide methods for dealing with large datasets in general. The \pkg{filehash} package also draws inspiration from Luke Tierney's experimental \pkg{gdbm} package which implements a key-value database via the GNU dbm (GDBM) library. The use of GDBM creates an external dependence since the GDBM C library has to be compiled on each system. In addition, I encountered a problem where databases created on 32-bit machines could not be transferred to and read on 64-bit machines (and vice versa). However, with the increasing use of 64-bit machines in the future, it seems this problem will eventually go away. The R Special Interest Group on Databases has developed a number of packages that provide an R interface to commonly used relational database management systems (RDBMS) such as MySQL (\pkg{RMySQL}), PostgreSQL (\pkg{RPgSQL}), and Oracle (\pkg{ROracle}). These packages use the S4 classes and generics defined in the \pkg{DBI} package and have the advantage that they offer much better database functionality, inherited via the use of a true database management system. However, this benefit comes with the cost of having to install and use third-party software. While installing an RDBMS may not be an issue---many systems have them pre-installed and the \pkg{RSQLite} package comes bundled with the source for the RDBMS---the need for the RDBMS and knowledge of structured query language (SQL) nevertheless adds some overhead. This overhead may serve as an impediment for users in need of a database for simpler applications. \section{Creating a filehash database} Databases can be created with \pkg{filehash} using the \code{dbCreate} function. The one required argument is the name of the database, which we call here ``mydb''. <>= library(filehash) dbCreate("mydb") db <- dbInit("mydb") @ You can also specify the \code{type} argument which controls how the database is represented on the backend. We will discuss the different backends in further detail later. For now, we use the default backend which is called ``DB1''. Once the database is created, it must be initialized in order to be accessed. The \code{dbInit} function returns an S4 object inheriting from class ``filehash''. Since this is a newly created database, there are no objects in it. \section{Accessing a filehash database} <>= set.seed(100) @ The primary interface to filehash databases consists of the functions \code{dbFetch}, \code{dbInsert}, \code{dbExists}, \code{dbList}, and \code{dbDelete}. These functions are all generic---specific methods exists for each type of database backend. They all take as their first argument an object of class ``filehash''. To insert some data into the database we can simply call \code{dbInsert} <>= dbInsert(db, "a", rnorm(100)) @ Here we have associated with the key ``a'' 100 standard normal random variates. We can retrieve those values with \code{dbFetch}. <>= value <- dbFetch(db, "a") mean(value) @ The function \code{dbList} lists all of the keys that are available in the database, \code{dbExists} tests to see if a given key is in the database, and \code{dbDelete} deletes a key-value pair from the database <>= dbInsert(db, "b", 123) dbDelete(db, "a") dbList(db) dbExists(db, "a") @ While using functions like \code{dbInsert} and \code{dbFetch} is straightforward it can often be easier on the fingers to use standard R subset and accessor functions like \code{\$}, \code{[[}, and \code{[}. Filehash databases have methods for these functions so that objects can be accessed in a more compact manner. Similarly, replacement methods for these functions are also available. The \verb+[+ function can be used to access multiple objects from the database, in which case a list is returned. <>= db$a <- rnorm(100, 1) mean(db$a) mean(db[["a"]]) db$b <- rnorm(100, 2) dbList(db) @ For all of the accessor functions, only character indices are allowed. Numeric indices are caught and an error is given. <>= e <- local({ err <- function(e) e tryCatch(db[[1]], error = err) }) conditionMessage(e) @ Finally, there is method for the \code{with} generic function which operates much like using \code{with} on lists or environments. The following three statements all return the same value. <>= with(db, c(a = mean(a), b = mean(b))) @ When using \code{with}, the values of ``a'' and ``b'' are looked up in the database. <>= sapply(db[c("a", "b")], mean) @ Here, using \code{[} on \code{db} returns a list with the values associated with ``a'' and ``b''. Then \code{sapply} is applied in the usual way on the returned list. <>= unlist(lapply(db, mean)) @ In the last statement we call \code{lapply} directly on the ``filehash'' object. The \pkg{filehash} package defines a method for \code{lapply} that allows the user to apply a function on all the elements of a database directly. The method essentially loops through all the keys in the database, loads each object separately and applies the supplied function to each object. \code{lapply} returns a named list with each element being the result of applying the supplied function to an object in the database. There is an argument \code{keep.names} to the \code{lapply} method which, if set to \code{FALSE}, will drop all the names from the list. <>= dbUnlink(db) rm(list = ls(all = TRUE)) @ \section{Loading filehash databases} <>= set.seed(200) @ An alternative way of working with a filehash database is to load it into an environment and access the element names directly, without having to use any of the accessor functions. The \pkg{filehash} function \code{dbLoad} works much like the standard R \code{load} function except that \code{dbLoad} loads active bindings into a given environment rather than the actual data. The active bindings are created via the \code{makeActiveBinding} function in the \pkg{base} package. \code{dbLoad} takes a filehash database and creates symbols in an environment corresponding to the keys in the database. It then calls \code{makeActiveBinding} to associate with each key a function which loads the data associated with a given key. Conceptually, active bindings are like pointers to the database. After calling \code{dbLoad}, anytime an object with an active binding is accessed the associated function (installed by \code{makeActiveBinding}) loads the data from the database. We can create a simple database to demonstrate the active binding mechanism. <>= dbCreate("testDB") db <- dbInit("testDB") db$x <- rnorm(100) db$y <- runif(100) db$a <- letters dbLoad(db) ls() @ Notice that we appear to have some additional objects in our workspace. However, the values of these objects are not stored in memory---they are stored in the database. When one of the objects is accessed, the value is automatically loaded from the database. <>= mean(y) sort(a) @ If I assign a different value to one of these objects, its associated value is updated in the database via the active binding mechanism. <>= y <- rnorm(100, 2) mean(y) @ If I subsequently remove the database and reload it later, the updated value for ``y'' persists. <>= rm(list = ls()) db <- dbInit("testDB") dbLoad(db) ls() mean(y) @ Perhaps one disadvantage of the active binding approach taken here is that whenever an object is accessed, the data must be reloaded into R. This behavior is distinctly different from the the delayed assignment approach taken in \pkg{g.data} where an object must only be loaded once and then is subsequently in memory. However, when using delayed assignments, if one cycles through all of the objects in the database, one could eventually exhaust the available memory. <>= dbUnlink(db) rm(list = ls(all = TRUE)) @ \section{Other filehash utilities} There are a few other utilities included with the \pkg{filehash} package. Two of the utilities, \code{dumpObjects} and \code{dumpImage}, are analogues of \code{save} and \code{save.image}. Rather than save objects to an R workspace, \code{dumpObjects} saves the given objects to a ``filehash'' database so that in the future, individual objects can be reloaded if desired. Similarly, \code{dumpImage} saves the entire workspace to a ``filehash'' database. The function \code{dumpList} takes a list and creates a ``filehash'' database with values from the list. The list must have a non-empty name for every element in order for \code{dumpList} to succeed. \code{dumpDF} creates a ``filehash'' database from a data frame where each column of the data frame is an element in the database. Essentially, \code{dumpDF} converts the data frame to a list and calls \code{dumpList}. \section{Filehash database backends} Currently, the \pkg{filehash} package can represent databases in two different formats. The default format is called ``DB1'' and it stores the keys and values in a single file. From experience, this format works well overall but can be a little slow to initialize when there are many thousands of keys. Briefly, the ``filehash'' object in R stores a map which associates keys with a byte location in the database file where the corresponding value is stored. Given the byte location, we can \code{seek} to that location in the file and read the data directly. Before reading in the data, a check is made to make sure that the map is up to date. This format depends critically on having a working \code{ftell} at the system level and a crude check is made when trying to initialize a database of this format. The second format is called ``RDS'' and it stores objects as separate files on the disk in a directory with the same name as the database. This format is the most straightforward and simple of the available formats. When a request is made for a specific key, \pkg{filehash} finds the appropriate file in the directory and reads the file into R. The only catch is that on operating systems that use case-insensitive file names, objects whose names differ only in case will collide on the filesystem. To workaround this, object names with capital letters are stored with mangled names on the disk. An advantage of this format is that most of the organizational work is delegated to the filesystem. \section{Extending filehash} The \pkg{filehash} package has a mechanism for developing new backend formats, should the need arise. The function \code{registerFormatDB} can be used to make \pkg{filehash} aware of a new database format that may be implemented in a separate R package or a file. \code{registerFormatDB} takes two arguments: a \code{name} for the new format (like ``DB1'' or ``RDS'') and a list of functions. The list should contain two functions: one function named ``create'' for creating a database, given the database name, and another function named ``initialize'' for initializing the database. In addition, one needs to define methods for \code{dbInsert}, \code{dbFetch}, etc. A list of available backend formats can be obtained via the \code{filehashFormats} function. Upon registering a new backend format, the new format will be listed when \code{filehashFormats} is called. The interface for registering new backend formats is still experimental and could change in the future. \section{Discussion} The \pkg{filehash} package has been designed be useful in both a programming setting and an interactive setting. Its main purpose is to allow for simpler interaction with large datasets where simultaneous access to the full dataset is not needed. While the package may not be optimal for all settings, one goal was to write a simple package in pure R that users to could install with minimal overhead. In the future I hope to add functionality for interacting with databases stored on remote computers and perhaps incorporate a ``real'' database backend. Some work has already begun on developing a backend based on the \pkg{RSQLite} package. \bibliographystyle{alpha} \bibliography{combined} \end{document} filehash/vignettes/combined.bib0000644000175100001440000000224013042123362016313 0ustar hornikusers @Manual{ templelang:2002, title = {RObjectTables: User-level attach()'able table support}, author = {Duncan {Temple Lang}}, year = {2002}, note = {{R} package version 0.3-1}, url = {http://www.omegahat.org/RObjectTables} } @Article{ rnews:ripley:2004, author = {Brian D. Ripley}, title = {Lazy Loading and Packages in {R} 2.0.0}, journal = {R News}, year = 2004, volume = 4, number = 2, pages = {2--4}, month = {September}, url = http, pdf = rnews2004-2 } @TechReport{ cham:1991, author = {John M. Chambers}, title = {Data Management in {S}}, institution = {AT\&T Bell Laboratories Statistics Research}, year = {1991}, number = {99}, month = {December}, note = {http://stat.bell-labs.com/doc/93.15.ps} } @Book{ cham:1998, author = {John M. Chambers}, title = {Programming with Data: A Guide to the {S} Language}, publisher = {Springer}, year = {1998} } @Article{ brahm:2002, author = {David E. Brahm}, title = {Delayed Data Packages}, journal = {R News}, year = 2002, volume = 2, number = 3, pages = {11--12}, month = {December}, url = {http://CRAN.R-project.org/doc/Rnews/} } filehash/MD50000644000175100001440000000431013071747306012371 0ustar hornikusersf81a68a5ced9bc1be6ec8ecfaa284a8d *DESCRIPTION 010f9467e36e0921ea402278070cbe7b *NAMESPACE 45232e1ac4dac258e45556bd5f43123c *R/coerce.R 90f64221f6f44767deed77323c0d4db2 *R/dump.R a8bd746b0bb1edddfacb7e4a5c83f1ef *R/filehash-DB1.R 792728111ce93475ae36b03871c7fb51 *R/filehash-RDS.R b0769cf1a3599dced35d14ce286a651c *R/filehash.R 9aafffd8d97e0765c852ea081f3b0b81 *R/hash.R b096fd971b2464562c885b8f6d3caeab *R/queue.R a75a45aef48fc227a52cc6144180eed0 *R/stack.R 017227163eed2f4e14226ad119129c81 *R/zzz.R 5d965e25cfd7c6d486dbe4c31debbaa2 *build/vignette.rds 7e77175e004788ca21e49f63b303381a *inst/CITATION b128d2038f8d0c5c554b72de80298e4a *inst/COPYING 44422cadef3c067ffd43b9f00a18aa9d *inst/NEWS 6b5ee8f3a31a761dd38bd0238efbdfc1 *inst/doc/filehash.R 7fb6c57e7c3a9b572359b21e60e07d3f *inst/doc/filehash.Rnw 616e47c0154f01ee4bc9611485c6abb1 *inst/doc/filehash.pdf 196634a9bcac54d5a8c2f30c622caf8d *man/createQ.Rd 76d0e8fb13ec5e82d2db2fe69c153399 *man/createS.Rd 994907132b6735cd18cb2e58fbe38246 *man/db2env.Rd b58d9742e3fb9a199ea91a8486ee7e9b *man/dbInit.Rd 7540db5f93596a70ed9a552bda857059 *man/dump.Rd fd3538ab1ab32963e4450a394c1970e6 *man/filehash-class.Rd 45f7f3a21e0d0cb2afbed678f4cf1e44 *man/filehashFormats.Rd fe2418282768cb27bdcef448742c263e *man/filehashOption.Rd 19f14bdb7f0a406d23e38ea6d0a22405 *man/push.Rd 3206913d23a67ae002e0214ee2093e94 *man/queue-class.Rd d6937904dedc8cd4e083e1b941d78b70 *man/stack-class.Rd e3ae87905dbded19881cffd6c80a8be4 *src/hash.c aabbbc8b771fb3caf53a314f8ae37a1f *src/init.c 1f69eebc69b381da6e96c802a8c93003 *src/lockfile.c 8a9e91209bf674c2db625d29298caad2 *src/readKeyMap.c 2212ffe253fda0b122c209ae4e220c71 *src/sha1.c 7cf279b5c8a6743e49a2c37d105733f5 *src/sha1.h d83a9585d249e9a093cb3c545cf49834 *tests/SHA1SUM f00793baa7e5a70812957058ec569332 *tests/misc/create-testdb.R 4900ff9e4624ba08887b7ca8a702df8c *tests/reg-tests.R 4872c25b98a848e6f7ef872f9dfbf617 *tests/reg-tests.Rout.save 5b7464763d85ba9406c9e4dddce80d97 *tests/testdb-v1.1 5b7464763d85ba9406c9e4dddce80d97 *tests/testdb-v2.0 40829c8958672fbc650561dde99ea5a0 *tests/versions.R 6e36a99376f5c4281e1acc0885d9d91c *tests/versions.Rout.save b2631aaa4f28eae69ba9e6b9b5bbbe80 *vignettes/combined.bib 7fb6c57e7c3a9b572359b21e60e07d3f *vignettes/filehash.Rnw filehash/build/0000755000175100001440000000000013071742312013152 5ustar hornikusersfilehash/build/vignette.rds0000644000175100001440000000032113071742312015505 0ustar hornikusersuQ0 - Q&&|_Ax!o~XԸ[^mATh3)`ѹNBffnX Gt$$eMFvOdX)%^dKOֲL^xU439q = 3.0.0), methods Collate: filehash.R filehash-DB1.R filehash-RDS.R coerce.R dump.R hash.R queue.R stack.R zzz.R Title: Simple Key-Value Database Author: Roger D. Peng Maintainer: Roger D. Peng Description: Implements a simple key-value style database where character string keys are associated with data values that are stored on the disk. A simple interface is provided for inserting, retrieving, and deleting data from the database. Utilities are provided that allow 'filehash' databases to be treated much like environments and lists are already used in R. These utilities are provided to encourage interactive and exploratory analysis on large datasets. Three different file formats for representing the database are currently available and new formats can easily be incorporated by third parties for use in the 'filehash' framework. License: GPL (>= 2) URL: http://github.com/rdpeng/filehash NeedsCompilation: yes Packaged: 2017-04-07 16:56:42 UTC; rdpeng Repository: CRAN Date/Publication: 2017-04-07 17:39:18 UTC filehash/man/0000755000175100001440000000000013071166400012624 5ustar hornikusersfilehash/man/createQ.Rd0000644000175100001440000000141013042123362014471 0ustar hornikusers\name{createQ} \alias{createQ} \alias{initQ} %- Also NEED an '\alias' for EACH other topic documented here. \title{Create/Initialize Queue} \description{ Create or initialize a queue data structure using \code{filehash} databases } \usage{ createQ(filename) initQ(filename) } %- maybe also 'usage' for other objects documented here. \arguments{ \item{filename}{character, file name for storing the queue data structure} } \details{ A new queue can be created using \code{createQ}, which creates a file for storing the queue information and returns an object of class \code{"queue"}. } \value{ The \code{createQ} and \code{initQ} functions both return an object of class \code{"queue"}. } \author{Roger D. Peng \email{rpeng@jhsph.edu}} \keyword{database} filehash/man/filehashFormats.Rd0000644000175100001440000000157113042123362016234 0ustar hornikusers\name{filehashFormats} \alias{filehashFormats} \alias{registerFormatDB} \title{List and register filehash formats} \description{ List and register filehash backend database formats. } \usage{ registerFormatDB(name, funlist) filehashFormats(...) } %- maybe also 'usage' for other objects documented here. \arguments{ \item{name}{character, name of database format} \item{funlist}{list of functions for creating and initializing a database format} \item{\dots}{list of functions for registering a new database format} } \details{ \code{registerFormatDB} can be used to register new filehash backend database formats. \code{filehashFormats} called with no arguments lists information on available formats. } \value{ \code{filehashFormats} returns a list containing information on the available filehash formats. } \keyword{utilities}% at least one, from doc/KEYWORDS filehash/man/stack-class.Rd0000644000175100001440000000325413042123362015325 0ustar hornikusers\name{stack-class} \docType{class} \alias{stack-class} \alias{isEmpty,stack-method} \alias{mpush,stack-method} \alias{pop,stack-method} \alias{push,stack-method} \alias{show,stack-method} \alias{top,stack-method} \title{Class "stack"} \description{A stack implementation using a \code{filehash} database} \section{Objects from the Class}{ Objects can be created by calls of the form \code{new("stack", ...)} or by calling \code{createS}. Existing queues can be initialized with \code{initS}. } \section{Slots}{ \describe{ \item{\code{stack}:}{Object of class \code{"filehashDB1"}} \item{\code{name}:}{Object of class \code{"character"}: the name of the stack (default is the file name in which the stack data are stored)} } } \section{Methods}{ \describe{ \item{isEmpty}{\code{signature(db = "stack")}: returns \code{TRUE}/\code{FALSE} depending on whether there are elements in the stack.} \item{pop}{\code{signature(db = "stack")}: returns the value of the top of the stack and subsequently removes that element from the stack; an error is signaled if the stack is empty} \item{push}{\code{signature(db = "stack")}: adds an element to the top of the stack} \item{show}{\code{signature(object = "stack")}: prints the name of the stack} \item{top}{\code{signature(db = "stack")}: returns the value of the top of the stack; an error is signaled if the stack is empty} \item{mpush}{\code{signature(db = "stack")}: works like \code{push} except it can push multiple objects in a list on to the stack} } } \author{Roger D. Peng \email{rpeng@jhsph.edu}} \examples{ showClass("stack") } \keyword{classes} filehash/man/dump.Rd0000644000175100001440000000375313042123362014066 0ustar hornikusers\name{dumpObjects} \alias{dumpObjects} \alias{dumpImage} \alias{dumpDF} \alias{dumpList} \alias{dumpEnv} \title{Dump objects of database} \description{ Dump R objects to a filehash database } \usage{ dumpObjects(..., list = character(0), dbName, type = NULL, envir = parent.frame()) dumpImage(dbName = "Rworkspace", type = NULL) dumpDF(data, dbName = NULL, type = NULL) dumpList(data, dbName = NULL, type = NULL) dumpEnv(env, dbName) } \arguments{ \item{\dots}{R objects to dump} \item{list}{character vector of names of objects to dump} \item{dbName}{character, name of database to which objects should be dumped} \item{type}{type of database to create} \item{envir}{environment from which to obtain objects} \item{data}{a data frame or a list} \item{env}{an environment} } \details{ Objects dumped to a database can later be loaded via \code{dbLoad} or can be accessed with \code{dbFetch}, \code{dbList}, etc. Alternatively, the \code{with} method can be used to evaluate code in the context of a database. If a database with name \code{dbName} already exists, objects will be inserted into the existing database (and values for already-existing keys will be overwritten). \code{dumpDF} is different in that each variable in the data frame is stored as a separate object in the database. So each variable can be read from the database separately rather than having to load the entire data frame into memory. \code{dumpList} works in a simlar way. The \code{dumpEnv} function takes an environment and stores each element of the environment in a \code{filehash} database. } \value{ An object of class \code{"filehash"} is returned and a database is created. } \author{Roger D. Peng} \examples{ data <- data.frame(y = rnorm(100), x = rnorm(100), z = rnorm(100)) db <- dumpDF(data, dbName = "dataframe.dump") fit <- with(db, lm(y ~ x + z)) summary(fit) db <- dumpList(list(a = 1, b = 2, c = 3), "list.dump") db$a } \keyword{database}% at least one, from doc/KEYWORDS filehash/man/createS.Rd0000644000175100001440000000141013042123362014473 0ustar hornikusers\name{createS} \alias{createS} \alias{initS} %- Also NEED an '\alias' for EACH other topic documented here. \title{Create/Initialize Stack} \description{ Create or initialize a stack data structure using \code{filehash} databases } \usage{ createS(filename) initS(filename) } %- maybe also 'usage' for other objects documented here. \arguments{ \item{filename}{character, file name for storing the stack data structure} } \details{ A new stack can be created using \code{createS}, which creates a file for storing the stack information and returns an object of class \code{"stack"}. } \value{ The \code{createS} and \code{initS} functions both return an object of class \code{"stack"}. } \author{Roger D. Peng \email{rpeng@jhsph.edu}} \keyword{database} filehash/man/queue-class.Rd0000644000175100001440000000305413042123362015342 0ustar hornikusers\name{queue-class} \docType{class} \alias{queue-class} \alias{isEmpty,queue-method} \alias{pop,queue-method} \alias{push,queue-method} \alias{show,queue-method} \alias{top,queue-method} \title{Class "queue"} \description{A queue implementation using a \code{filehash} database} \section{Objects from the Class}{ Objects can be created by calls of the form \code{new("queue", ...)} or by calling \code{createQ}. Existing queues can be initialized with \code{initQ}. } \section{Slots}{ \describe{ \item{\code{queue}:}{Object of class \code{"filehashDB1"}} \item{\code{name}:}{Object of class \code{"character"}: the name of the queue (default is the file name in which the queue data are stored)} } } \section{Methods}{ \describe{ \item{isEmpty}{\code{signature(db = "queue")}: returns \code{TRUE}/\code{FALSE} depending on whether there are elements in the queue.} \item{pop}{\code{signature(db = "queue")}: returns the value of the "top" (i.e. head) of the queue and subsequently removes that element from the queue; an error is signaled if the queue is empty} \item{push}{\code{signature(db = "queue")}: adds an element to the tail ("bottom") of the queue} \item{show}{\code{signature(object = "queue")}: prints the name of the queue} \item{top}{\code{signature(db = "queue")}: returns the value of the "top" (i.e. head) of the queue; an error is signaled if the queue is empty} } } \author{Roger D. Peng \email{rpeng@jhsph.edu}} \examples{ showClass("queue") } \keyword{classes} filehash/man/db2env.Rd0000644000175100001440000000550613042123362014277 0ustar hornikusers\name{dbLoad} \alias{dbLoad} \alias{dbLoad,filehash-method} \alias{dbLazyLoad} \alias{dbLazyLoad,filehash-method} \alias{db2env} \title{Load database into environment} \description{ Load entire database into an environment } \usage{ db2env(db) dbLoad(db, ...) dbLazyLoad(db, ...) \S4method{dbLoad}{filehash}(db, env = parent.frame(2), keys = NULL, ...) \S4method{dbLazyLoad}{filehash}(db, env = parent.frame(2), keys = NULL, ...) } \arguments{ \item{db}{database object} \item{env}{an environment} \item{keys}{character vector of database keys to load} \item{...}{other arguments passed to methods} } \details{ \code{db2env} loads the entire database \code{db} into an environment via calls to \code{makeActiveBinding}. Therefore, the data themselves are not stored in the environment, but a function pointing to the data in the database is stored. When an element of the environment is accessed, the function is called to retrieve the data from the database. If the data in the database is changed, the changes will be reflected in the environment. \code{dbLoad} loads objects in the database directly into the environment specified, like \code{load} does except with active bindings. \code{dbLoad} takes a second argument \code{env}, which is an environment, and the default for \code{env} is \code{parent.frame()}. The use of \code{makeActiveBinding} in \code{db2env} and \code{dbLoad} allows for potentially large databases to, at least conceptually, be used in R, as long as you don't need simultaneous access to all of the elements in the database. With \code{dbLazyLoad} database objects are "lazy-loaded" into the environment. Promises to load the objects are created in the environment specified by \code{env}. Upon first access, those objects are copied into the environment and will from then on reside in memory. Changes to the database will not be reflected in the object residing in the environment after first access. Conversely, changes to the object in the environment will not be reflected in the database. This type of loading is useful for read-only databases. } \value{ For \code{db2env}, an environment is returned, the elements of which are the keys of the database. For \code{dbLoad} and \code{dbLazyLoad}, a character vector is returned (invisibly) containing the keys associated with the values loaded into the environment. } \author{Roger D. Peng} \seealso{ \code{\link{dbInit}} and \code{\link{filehash-class}} } \examples{ dbCreate("myDB") db <- dbInit("myDB") dbInsert(db, "a", rnorm(100)) dbInsert(db, "b", 1:10) env <- db2env(db) ls(env) ## "a", "b" print(env$b) mean(env$a) env$a <- rnorm(100) mean(env$a) env$b[1:5] <- 5:1 print(env$b) env <- new.env() dbLoad(db, env) ls(env) env <- new.env() dbLazyLoad(db, env) ls(env) as(db, "list") } \keyword{database} filehash/man/dbInit.Rd0000644000175100001440000000302513071166373014335 0ustar hornikusers\name{dbInit} \alias{dbInit} \alias{dbInitialize} \alias{dbCreate} \alias{dbCreate,ANY-method} \alias{dbInit,ANY-method} %\alias{dbInitialize} \title{Simple file-based hash table} \description{ Interface for creating and initializing a simple file-based hash table } \usage{ dbCreate(db, ...) dbInit(db, ...) \S4method{dbCreate}{ANY}(db, type = NULL, ...) \S4method{dbInit}{ANY}(db, type = NULL, ...) } \arguments{ \item{db}{name of database or a database object} \item{type}{type of database format. If missing, the default type will be used} \item{...}{other arguments passed to methods} } \details{ \code{dbCreate} creates the necessary files or directory for the database. If those files already exist nothing is done. \code{dbInit} takes a database name and returns an object inheriting from class \code{"filehash"}. The \code{type} argument specifies the format in which the database should be stored on the disk. If not specified, the default type will be used (as specified by \code{filehashOption}). } \note{ The function \code{dbInitialize} has been deprecated. Use \code{dbInit} instead. } \value{ \code{dbCreate} returns \code{TRUE} upon success and \code{FALSE} in the event of an error. \code{dbInit} returns an object inheriting from class \code{"filehash"} } \author{Roger D. Peng} \seealso{ See \code{\link{filehash-class}} more information and examples and \code{\link{filehashOption}} for setting the default database type. } \keyword{database}% at least one, from doc/KEYWORDS filehash/man/filehash-class.Rd0000644000175100001440000001242013042123362015776 0ustar hornikusers\name{filehash-class} \docType{class} \alias{filehash-class} \alias{filehashDB-class} \alias{filehashRDS-class} \alias{filehashDB1-class} \alias{dbFetch} \alias{dbMultiFetch} \alias{dbInsert} \alias{dbExists} \alias{dbList} \alias{dbDelete} \alias{dbReorganize} \alias{dbUnlink} \alias{dbDelete,filehashDB,character-method} \alias{dbExists,filehashDB,character-method} \alias{dbFetch,filehashDB,character-method} \alias{dbInsert,filehashDB,character-method} \alias{dbList,filehashDB-method} \alias{dbUnlink,filehashDB-method} \alias{dbReorganize,filehashDB-method} \alias{dbMultiFetch,filehashDB1-method} \alias{dbDelete,filehashDB1,character-method} \alias{dbExists,filehashDB1,character-method} \alias{dbFetch,filehashDB1,character-method} \alias{dbMultiFetch,filehashDB1,character-method} \alias{dbInsert,filehashDB1,character-method} \alias{dbList,filehashDB1-method} \alias{dbUnlink,filehashDB1-method} \alias{dbReorganize,filehashDB1-method} \alias{dbDelete,filehashRDS,character-method} \alias{dbExists,filehashRDS,character-method} \alias{dbFetch,filehashRDS,character-method} \alias{dbMultiFetch,filehashRDS,character-method} \alias{dbInsert,filehashRDS,character-method} \alias{dbList,filehashRDS-method} \alias{dbUnlink,filehashRDS-method} \alias{show,filehash-method} \alias{with,filehash-method} \alias{coerce,filehashDB,filehashRDS-method} \alias{coerce,filehashRDS,filehashDB-method} \alias{coerce,filehashDB1,filehashRDS-method} \alias{coerce,filehashDB1,list-method} \alias{coerce,filehashDB,filehashDB1-method} \alias{coerce,filehash,list-method} \alias{lapply,filehash-method} \alias{names,filehash-method} \alias{length,filehash-method} \alias{[,filehash,character,missing,missing-method} \alias{[[,filehash,character,missing-method} \alias{[[,filehash,numeric,missing-method} \alias{[[<-,filehash,character,missing-method} \alias{[[<-,filehash,numeric,missing-method} \alias{$<-,filehash-method} \alias{$,filehash-method} \title{Class "filehash"} \description{ These functions form the interface for a simple file-based key-value database (i.e. hash table). } \section{Objects from the Class}{ Objects can be created by calls of the form \code{new("filehash", ...)}. } \section{Slots}{ \describe{ \item{\code{name}:}{Object of class \code{"character"}, name of the database.} } } \section{Additional slots for "filehashDB1"}{ \describe{ \item{\code{datafile}:}{full path to the database file.} \item{\code{meta}:}{list containing an environment for database metadata.} } } \section{Additional slots for "filehashRDS"}{ \describe{ \item{dir:}{Directory where files are stored.} } } \section{Methods}{ \describe{ \item{dbDelete}{The \code{dbDelete} function is for deleting elements, but for the \code{"DB1"} format all it does is remove the key from the lookup table. The actual data are still in the database (but inaccessible). If you reinsert data for the same key, the new data are simply appended on to the end of the file. Therefore, it's possible to have multiple copies of data lying around after a while, potentially making the database file big. The \code{"RDS"} format does not have this problem.} \item{dbExists}{check to see if a key exists.} \item{dbFetch}{retrieve the value associated with a given key.} \item{dbMultiFetch}{retrieve values associated with multiple keys (a list of those values is returned).} \item{dbInsert}{insert a key-value pair into the database. If that key already exists, its associated value is overwritten. For \code{"RDS"} type databases, there is a \code{safe} option (defaults to \code{TRUE}) which allows the user to insert objects somewhat more safely (objects should not be lost in the event of an interrupt).} \item{dbList}{list all keys in the database.} \item{dbReorganize}{The \code{dbReorganize} function is there for the purpose of rewriting the database to remove all of the stale entries. Basically, this function creates a new copy of the database and then overwrites the old copy. This function has not been tested extensively and so should be considered \emph{experimental}. \code{dbReorganize} is not needed when using the \code{"RDS"} format.} \item{dbUnlink}{delete an entire database from the disk} \item{show}{print method} \item{with}{allows \code{with} to be used with \code{"filehash"} objects much like it can be used with lists or data frames} \item{[[,[[<-}{elements of a database can be accessed using the \code{[[} operator much like a list or environment, but only character indices are allowed} \item{$,$<-}{elements of a database can be accessed using the \code{$} operator much like with a list or environment} \item{lapply}{works much like \code{lapply} with lists; a list is returned.} \item{names}{returns all of the keys in the database} \item{length}{returns the number of elements in the database} } } \author{Roger D. Peng \email{rpeng@jhsph.edu}} \examples{ dbCreate("myDB") ## Create database 'myDB' db <- dbInit("myDB") dbInsert(db, "a", 1:10) dbInsert(db, "b", rnorm(1000)) dbExists(db, "b") ## 'TRUE' dbList(db) ## c("a", "b") dbDelete(db, "a") dbList(db) ## "b" with(db, mean(b)) } \keyword{classes} filehash/man/filehashOption.Rd0000644000175100001440000000101313042123362016060 0ustar hornikusers\name{filehashOption} \alias{filehashOption} \title{Set filehash options} \description{ Set global filehash options } \usage{ filehashOption(...) } \arguments{ \item{\dots}{name-value pairs for options} } \details{ Currently, the only option that can be set is the default database type (\code{defaultType}) which can be "DB1", "RDS" or "DB". } \value{ \code{filehashOptions} returns a list of current settings for all options. } \author{Roger D. Peng} \keyword{database}% at least one, from doc/KEYWORDS filehash/man/push.Rd0000644000175100001440000000252213042123362014071 0ustar hornikusers\name{stackqueue} \alias{stackqueue} \alias{push} \alias{pop} \alias{mpush} \alias{top} \alias{isEmpty} %- Also NEED an '\alias' for EACH other topic documented here. \title{Operations on Stacks/Queues} \description{ Functions for interacting with stack and queue data structures implemented using \code{filehash} databases. } \usage{ push(db, val, ...) mpush(db, vals, ...) pop(db, ...) top(db, ...) isEmpty(db, ...) } %- maybe also 'usage' for other objects documented here. \arguments{ \item{db}{an object of class \code{"stack"} or \code{"queue"}} \item{val}{an R object} \item{vals}{a list of R objects} \item{\dots}{arguments passed to other methods} } \details{ Note that for \code{mpush}, if \code{vals} is not a list it will be coerced to a list via \code{as.list}. Currently, \code{mpush} is only implemented for \code{"stack"}s. } \value{ \code{push} and \code{mpush} return nothing useful; \code{pop} returns a value from the stack/queue and deletes that value from the stack/queue; \code{top} returns the "top" value from the stack/queue; \code{isEmpty} returns \code{TRUE}/\code{FALSE} depending on whether the stack/queue is empty or not. Both \code{pop} and \code{top} signal an error if the stack/queue is empty. } \author{Roger D. Peng \email{rpeng@jhsph.edu}} \keyword{database}% __ONLY ONE__ keyword per line