Drop the PTXQC dependency and bump to 1.3.0.9000
PTXQC was imported for exactly two small string functions, lcpCount()
and lcsCount(), both used only by queryStringToLabel(). It brought
rmzqc, jsonvalidate, seqinr and V8 with it, and V8 was the only
dependency in the whole tree that required a libv8 installation, which
made it by far the most expensive part of installing RKorAPClient on
Linux. The recursive dependency closure shrinks from 128 to 90 packages.
Both functions are pure R in PTXQC, so reimplementing them costs no
compiled code. The replacements determine the length of the common
prefix and suffix by binary search over vectorized startsWith() and
endsWith() calls, instead of comparing character by character (and, for
the suffix, reversing every string with strsplit/rev/paste first). That
makes them 10 to 65 times faster, depending on the number and length of
the strings:
PTXQC here
10 query strings, 37 chars prefix 183 us 16 us
suffix 127 us 13 us
121 vcs, 42 chars prefix 413 us 29 us
suffix 1029 us 26 us
50 strings, 563 chars prefix 1405 us 64 us
suffix 4362 us 67 us
Results are unchanged: the two functions were compared against their
PTXQC counterparts over 3020 inputs, and queryStringToLabel() over 8044
input/argument combinations, without a single difference. Empty vectors,
empty strings and the single string case (where a string is its own
prefix and suffix) behave exactly as before. The only deviation is for
NA input, where PTXQC's lcsCount() silently treats NA as the literal
string "NA"; queryStringToLabel() never reaches it in that case, because
the prefix function errors on NA in both implementations.
Since V8 is gone, and keyring no longer needs sodium either, libv8-dev
and libsodium-dev can be dropped from the Linux installation
instructions in the Readme.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: Icad4bc76b773b1586922dd05458b82409d112932
diff --git a/tests/testthat/test-common-affixes.R b/tests/testthat/test-common-affixes.R
new file mode 100644
index 0000000..0314864
--- /dev/null
+++ b/tests/testthat/test-common-affixes.R
@@ -0,0 +1,59 @@
+lcp <- RKorAPClient:::longestCommonPrefixLength
+lcs <- RKorAPClient:::longestCommonSuffixLength
+
+test_that("longestCommonPrefixLength counts the shared prefix", {
+ expect_equal(lcp(c("abc", "abd")), 2)
+ expect_equal(lcp(c("abc", "abc")), 3)
+ expect_equal(lcp(c("xyz", "abc")), 0)
+ expect_equal(lcp(c("aaa", "aa", "a")), 1)
+ expect_equal(lcp(c("prefix_A_suffix", "prefix_B_suffix")), 7)
+ expect_equal(lcp(c("üöä-test", "üöä-rest")), 4)
+})
+
+test_that("longestCommonSuffixLength counts the shared suffix", {
+ expect_equal(lcs(c("abc", "dbc")), 2)
+ expect_equal(lcs(c("abc", "abc")), 3)
+ expect_equal(lcs(c("xyz", "abc")), 0)
+ expect_equal(lcs(c("aaa", "aa", "a")), 1)
+ expect_equal(lcs(c("prefix_A_suffix", "prefix_B_suffix")), 7)
+ expect_equal(lcs(c("abc-üöä", "xyz-üöä")), 4)
+})
+
+test_that("empty vectors, empty strings and single strings behave as before", {
+ # a single string is its own prefix and suffix, as in the PTXQC functions
+ # these replace
+ expect_equal(lcp(character(0)), 0)
+ expect_equal(lcs(character(0)), 0)
+ expect_equal(lcp("single"), 6)
+ expect_equal(lcs("single"), 6)
+ expect_equal(lcp(c("", "")), 0)
+ expect_equal(lcs(c("", "")), 0)
+ expect_equal(lcp(c("abc", "")), 0)
+ expect_equal(lcs(c("", "abc")), 0)
+})
+
+test_that("queryStringToLabel clips common prefixes and suffixes", {
+ expect_equal(
+ queryStringToLabel(paste("textType = /Zeit.*/ & pubDate in", 2010:2019)),
+ as.character(2010:2019)
+ )
+ expect_equal(
+ queryStringToLabel(c("[marmot/m=mood:subj]", "[marmot/m=mood:ind]")),
+ c("subj", "ind")
+ )
+ expect_equal(
+ queryStringToLabel(c("wegen dem [tt/p=NN]", "wegen des [tt/p=NN]")),
+ c("dem", "des")
+ )
+})
+
+test_that("queryStringToLabel honours pubDateOnly and excludePubDate", {
+ vc <- paste("textType = /Zeit.*/ & pubDate in", 2010:2012)
+ expect_equal(queryStringToLabel(vc, pubDateOnly = TRUE), as.character(2010:2012))
+ expect_equal(
+ queryStringToLabel(c("textDomain = /Wirtschaft.*/ & pubDate in 2010",
+ "textDomain != /Wirtschaft.*/ & pubDate in 2010"),
+ excludePubDate = TRUE),
+ c("=", "!=")
+ )
+})