تقطيع سلسلة في متجه من عناصر حرف العرض الثابت
سؤال
لدي كائن يحتوي على سلسلة نصية:
x <- "xxyyxyxy"
وأريد تقسيم ذلك إلى متجه مع كل عنصر يحتوي على حرفين:
[1] "xx" "yy" "xy" "xy"
يبدو وكأنه strsplit
يجب أن تكون تذكرتي، ولكن نظرا لعدم وجود تعبير منتظم فو، لا يمكنني معرفة كيفية جعل هذه الوظيفة تقطع السلسلة إلى قطع بالطريقة التي أريدها. كيف أفعل ذلك؟
المحلول
استخدام substring
هو أفضل نهج:
substring(x, seq(1, nchar(x), 2), seq(2, nchar(x), 2))
ولكن هنا حل مع plyr:
library("plyr")
laply(seq(1, nchar(x), 2), function(i) substr(x, i, i+1))
نصائح أخرى
فيما يلي حل سريع ينقسم السلسلة إلى أحرف، ثم يلصقان معا العناصر حتى العناصر الفردية.
x <- "xxyyxyxy"
sst <- strsplit(x, "")[[1]]
paste0(sst[c(TRUE, FALSE)], sst[c(FALSE, TRUE)])
إعداد معيار:
library(microbenchmark)
GSee <- function(x) {
sst <- strsplit(x, "")[[1]]
paste0(sst[c(TRUE, FALSE)], sst[c(FALSE, TRUE)])
}
Shane1 <- function(x) {
substring(x, seq(1,nchar(x),2), seq(2,nchar(x),2))
}
library("plyr")
Shane2 <- function(x) {
laply(seq(1,nchar(x),2), function(i) substr(x, i, i+1))
}
seth <- function(x) {
strsplit(gsub("([[:alnum:]]{2})", "\\1 ", x), " ")[[1]]
}
geoffjentry <- function(x) {
idx <- 1:nchar(x)
odds <- idx[(idx %% 2) == 1]
evens <- idx[(idx %% 2) == 0]
substring(x, odds, evens)
}
drewconway <- function(x) {
c<-strsplit(x,"")[[1]]
sapply(seq(2,nchar(x),by=2),function(y) paste(c[y-1],c[y],sep=""))
}
KenWilliams <- function(x) {
n <- 2
sapply(seq(1,nchar(x),by=n), function(xx) substr(x, xx, xx+n-1))
}
RichardScriven <- function(x) {
regmatches(x, gregexpr("(.{2})", x))[[1]]
}
المعيار 1:
x <- "xxyyxyxy"
microbenchmark(
GSee(x),
Shane1(x),
Shane2(x),
seth(x),
geoffjentry(x),
drewconway(x),
KenWilliams(x),
RichardScriven(x)
)
# Unit: microseconds
# expr min lq median uq max neval
# GSee(x) 8.032 12.7460 13.4800 14.1430 17.600 100
# Shane1(x) 74.520 80.0025 84.8210 88.1385 102.246 100
# Shane2(x) 1271.156 1288.7185 1316.6205 1358.5220 3839.300 100
# seth(x) 36.318 43.3710 45.3270 47.5960 67.536 100
# geoffjentry(x) 9.150 13.5500 15.3655 16.3080 41.066 100
# drewconway(x) 92.329 98.1255 102.2115 105.6335 115.027 100
# KenWilliams(x) 77.802 83.0395 87.4400 92.1540 163.705 100
# RichardScriven(x) 55.034 63.1360 65.7545 68.4785 108.043 100
المعيار 2:
الآن، مع بيانات أكبر.
x <- paste(sample(c("xx", "yy", "xy"), 1e5, replace=TRUE), collapse="")
microbenchmark(
GSee(x),
Shane1(x),
Shane2(x),
seth(x),
geoffjentry(x),
drewconway(x),
KenWilliams(x),
RichardScriven(x),
times=3
)
# Unit: milliseconds
# expr min lq median uq max neval
# GSee(x) 29.029226 31.3162690 33.603312 35.7046155 37.805919 3
# Shane1(x) 11754.522290 11866.0042600 11977.486230 12065.3277955 12153.169361 3
# Shane2(x) 13246.723591 13279.2927180 13311.861845 13371.2202695 13430.578694 3
# seth(x) 86.668439 89.6322615 92.596084 92.8162885 93.036493 3
# geoffjentry(x) 11670.845728 11681.3830375 11691.920347 11965.3890110 12238.857675 3
# drewconway(x) 384.863713 438.7293075 492.594902 515.5538020 538.512702 3
# KenWilliams(x) 12213.514508 12277.5285215 12341.542535 12403.2315015 12464.920468 3
# RichardScriven(x) 11549.934241 11730.5723030 11911.210365 11989.4930080 12067.775651 3
ماذا عن
strsplit(gsub("([[:alnum:]]{2})", "\\1 ", x), " ")[[1]]
أساسا، إضافة فاصل (هنا ") و من ثم استعمال strsplit
StrSplit سيكون مشكلة، انظر إلى Regexp مثل هذا
strsplit(z, '[[:alnum:]]{2}')
سوف ينقسم في النقاط الصحيحة ولكن لم يتم ترك أي شيء.
يمكنك استخدام Substring & Friends
z <- 'xxyyxyxy'
idx <- 1:nchar(z)
odds <- idx[(idx %% 2) == 1]
evens <- idx[(idx %% 2) == 0]
substring(z, odds, evens)
إليك طريقة واحدة، ولكن لا تستخدم Regexen:
a <- "xxyyxyxy"
n <- 2
sapply(seq(1,nchar(a),by=n), function(x) substr(a, x, x+n-1))
مجموع الاختراق، دينار، لكنه يحصل عليه
x <- "xxyyxyxy"
c<-strsplit(x,"")[[1]]
sapply(seq(2,nchar(x),by=2),function(y) paste(c[y-1],c[y],sep=""))
[1] "xx" "yy" "xy" "xy"
وظيفة المساعد:
fixed_split <- function(text, n) {
strsplit(text, paste0("(?<=.{",n,"})"), perl=TRUE)
}
fixed_split(x, 2)
[[1]]
[1] "xx" "yy" "xy" "xy"
الانتباه مع السلسلة الفرعية، إذا كان طول السلسلة ليس مضاعفا طولك المطلوب، فستحتاج إلى + (N-1) في التسلسل الثاني:
substring(x,seq(1,nchar(x),n),seq(n,nchar(x)+n-1,n))
حسنا، استخدمت التعليمات الزائفة التالية لتلبية هذه المهمة:
- إدراج تسلسل خاص في كل قطعة من الطول ن.
- انقسام السلسلة عن طريق التسلسل المذكور.
في الكود، فعلت
chopS <- function( text, chunk_len = 2, seqn)
{
# Specify select and replace patterns
insert <- paste("(.{",chunk_len,"})", sep = "")
replace <- paste("\\1", seqn, sep = "")
# Insert sequence with replaced pattern, then split by the sequence
interp_text <- gsub( pattern, replace, text)
strsplit( interp_text, seqn)
}
هذا إرجاع قائمة مع متجه الانقسام في الداخل، على الرغم من عدم وجود ناقلات.
باستخدام C ++ واحد يمكن أن يكون أسرع. بالمقارنه مع نسخة GSEE.:
GSee <- function(x) {
sst <- strsplit(x, "")[[1]]
paste0(sst[c(TRUE, FALSE)], sst[c(FALSE, TRUE)])
}
rstub <- Rcpp::cppFunction( code = '
CharacterVector strsplit2(const std::string& hex) {
unsigned int length = hex.length()/2;
CharacterVector res(length);
for (unsigned int i = 0; i < length; ++i) {
res(i) = hex.substr(2*i, 2);
}
return res;
}')
x <- "xxyyxyxy"
all.equal(GSee(x), rstub(x))
#> [1] TRUE
microbenchmark::microbenchmark(GSee(x), rstub(x))
#> Unit: microseconds
#> expr min lq mean median uq max neval
#> GSee(x) 4.272 4.4575 41.74284 4.5855 4.7105 3702.289 100
#> rstub(x) 1.710 1.8990 139.40519 2.0665 2.1250 13722.075 100
set.seed(42)
x <- paste(sample(c("xx", "yy", "xy"), 1e5, replace = TRUE), collapse = "")
all.equal(GSee(x), rstub(x))
#> [1] TRUE
microbenchmark::microbenchmark(GSee(x), rstub(x))
#> Unit: milliseconds
#> expr min lq mean median uq max neval
#> GSee(x) 17.931801 18.431504 19.282877 18.738836 19.47943 27.191390 100
#> rstub(x) 3.197587 3.261109 3.404973 3.341099 3.45852 4.872195 100
من الاختبارات الخاصة بي، يكون الكود أدناه أسرع من الأساليب السابقة التي تم قياسها. Stri_sub سريع جدا، و Seq.int أفضل من SEQ. من السهل أيضا تغيير حجم السلاسل عن طريق تغيير كل 2Ls إلى شيء آخر.
library(stringi)
split_line <- function(x) {
row_length <- stri_length(x)
stri_sub(x, seq.int(1L, row_length, 2L), seq.int(2L, row_length, 2L))
}
لم ألاحظ اختلافا عندما كانت قطع السلسلة شخصا طويلان، ولكن لكبرك أكبر، هذا أفضل قليلا.
split_line <- function(x) {
stri_sub(x, seq.int(1L, stri_length(x), 109L), length = 109L)
}
هنا خيار واحد باستخدام stringi::stri_sub()
. وبعد محاولة:
x <- "xxyyxyxy"
stringi::stri_sub(x, seq(1, stringi::stri_length(x), by = 2), length = 2)
# [1] "xx" "yy" "xy" "xy"