对包含语义版本的字符向量进行排序

Posted

技术标签:

【中文标题】对包含语义版本的字符向量进行排序【英文标题】:Sorting character vector containing semantic versions 【发布时间】:2014-11-19 23:22:59 【问题描述】:

似乎是一个非常基本的问题,但我真的想不出一个“简单”的方法来做到这一点。

我想使用 基本 R 功能对包含 semantic version numbers 的 character 向量进行排序:

vsns  <- c("1", "10", "1.1", "1.10", "1.2", "1.1.1", 
           "1.1.10", "1.1.2", "1.1.1.1", "1.1.1.10", "1.1.1.2")

排序后应该是这样的:

# [1] "1"        "1.1"      "1.1.1"    "1.1.1.1"  "1.1.1.2"  "1.1.1.10"
# [7] "1.1.2"    "1.1.10"   "1.2"      "1.10"     "10"    

这并没有得到我想要的,当然,因为 R 只是按字母顺序对整个内容进行排序:

sort(vsns)
# [1] "1"        "1.1"      "1.1.1"    "1.1.1.1"  "1.1.1.10" "1.1.1.2"  "1.1.10"  
# [8] "1.1.2"    "1.10"     "1.2"      "10"    
vsns[order(vsns)]
# [1] "1"        "1.1"      "1.1.1"    "1.1.1.1"  "1.1.1.10" "1.1.1.2"  "1.1.10"  
# [8] "1.1.2"    "1.10"     "1.2"      "10"    

尝试对其进行规范化(有点像post),但我想不出适合语义版本结构的匹配/替换方案:

tmp <- gsub("\\.", "", vsns)
# [1] "011"  "021"  "0101" "0201"
tmp_nchar <- sapply(tmp, nchar)
to_add <- max(tmp_nchar) - tmp_nchar
tmp <- sapply(1:length(tmp), function(ii) 
  paste0(tmp[ii], paste(rep("A", to_add[ii]), collapse = ""))
)
# [1] "10"       "1.10"     "1.1.10"   "1.1.1.10" "1.1.1.1"  "1.1.1.2"  "1.1.1"   
# [8] "1.1.2"    "1.1"      "1.2"      "1"   
vsns[order(tmp)]
#  [1] "1AAAA" "10AAA" "11AAA" "110AA" "12AAA" "111AA" "1110A" "112AA" "1111A" "11110"
# [11] "1112A"

到目前为止我能想到的最好的就是这个,但它看起来很漂亮......参与;-)

sortVersionNumbers <- function(x, decreasing = FALSE) 
  tmp <- strsplit(x, split = "\\.")  
  tmp_l <- sapply(tmp, length)  
  idx_max <- which.max(tmp_l)[1]
  tmp_l_max <- tmp_l[idx_max]
  tmp_n <- lapply(tmp, function(ii) 
    ii_l <- length(ii)
    if (ii_l < tmp_l_max) 
      c(ii, rep(NA, (tmp_l_max - ii_l)))
     else 
      ii
    
  )
  tmp <- matrix(as.numeric(unlist(tmp_n)), nrow = length(tmp_n), byrow = TRUE)
  tmp_cols <- ncol(tmp)
  expr <- paste0("order(", paste(paste0("tmp[,", 1:tmp_cols, "]"), 
    collapse = ", "), ", na.last = FALSE",
    ifelse(decreasing, ", decreasing = FALSE)", ")"))
  idx <- eval(parse(text = expr))
  tmp_2 <- tmp[idx,]  
  sapply(1:nrow(tmp_2), function(ii) 
    paste(na.omit(tmp_2[ii,]), collapse = ".")
  )

sortVersionNumbers(vsns)
# [1] "1"        "1.1"      "1.1.1"    "1.1.1.1"  "1.1.1.2"  "1.1.1.10" "1.1.2"   
# [8] "1.1.10"   "1.2"      "1.10"     "10" 
sortVersionNumbers(sort(vsns))
# [1] "1"        "1.1"      "1.1.1"    "1.1.1.1"  "1.1.1.2"  "1.1.1.10" "1.1.2"   
# [8] "1.1.10"   "1.2"      "1.10"     "10" 

【问题讨论】:

我们是否可以假设所有版本号都井井有条并且具有相同数量的.s? 不,因为我想让我选择使用 major.minor.patch.dev 方案。所以它可以是 1 到 4 个“语义块”。 你能给出几个不同的可能输入和预期输出吗? 是的,对不起。刚刚意识到这个例子还没有强调这一点。我会更新的 【参考方案1】:

尝试使用新的 vsns 数据:

vsns  <- c("1", "10", "1.1", "1.10", "1.2", "1.1.1", "1.1.10", "1.1.2", "1.1.1.1", "1.1.1.10", "1.1.1.2")
dd = data.frame(vsns)
library(splitstackshape)
dd2 = concat.split.expanded(dd, 'vsns', '.', fill = 0, drop = TRUE)
dd3 = cbind(dd, dd2)
dd4= with(dd3, dd3[order(vsns_1, vsns_2, vsns_3, vsns_4),])
dd4[is.na(dd4)]=0
dd4
       vsns vsns_1 vsns_2 vsns_3 vsns_4
9   1.1.1.1      1      1      1      1
11  1.1.1.2      1      1      1      2
10 1.1.1.10      1      1      1     10
6     1.1.1      1      1      1      0
8     1.1.2      1      1      2      0
7    1.1.10      1      1     10      0
3       1.1      1      1      0      0
5       1.2      1      2      0      0
4      1.10      1     10      0      0
1         1      1      0      0      0
2        10     10      0      0      0
> 
apply(dd4[,2:5], 1, paste, collapse='.')
          9          11          10           6           8           7           3           5           4           1 
 " 1.1.1.1"  " 1.1.1.2" " 1.1.1.10"  " 1.1.1.0"  " 1.1.2.0" " 1.1.10.0"  " 1.1.0.0"  " 1.2.0.0" " 1.10.0.0"  " 1.0.0.0" 
          2 
 "10.0.0.0" 

【讨论】:

也不错。 splitstackshape 包可能有一天会派上用场,但还不知道。谢谢!【参考方案2】:

来自 ?numeric_version

> sort(numeric_version(vsns))
 [1] '1'        '1.1'      '1.1.1'    '1.1.1.1'  '1.1.1.2'  '1.1.1.10'
 [7] '1.1.2'    '1.1.10'   '1.2'      '1.10'     '10'  

看看这是如何实现的相对有趣。 numeric_version 将单个版本字符串拆分为整数部分,并将版本向量存储为整数向量列表。 xtfrmsort() 使用)上的一个方法将组成每个版本字符串的整数向量转换为数值,其中胆量为

base <- max(unlist(x), 0, na.rm = TRUE) + 1                                 
x <- vapply(x, function(t) sum(t/base^seq.int(0, length.out = length(t))), 
    1)

结果是一个数字向量,可用于以标准方式对原始向量进行排序。因此,临时解决方案是

xtfrm.my_version <- function(x) 
    x <- lapply(strsplit(x, ".", fixed=TRUE), as.integer)
    base <- max(unlist(x), 0, na.rm = TRUE) + 1
    vapply(x, function(t) sum(t/base^seq.int(0, length.out = length(t))), 1)


vsns  <- c("1", "10", "1.1", "1.10", "1.2", "1.1.1",
           "1.1.10", "1.1.2", "1.1.1.1", "1.1.1.10", "1.1.1.2")
class(vsns) = "my_version"
sort(vsns)

【讨论】:

是的!那些日子,当你发现一个总是在那里但你从来不知道的小帮手的时候 ;-) 非常感谢,正是我正在寻找的!【参考方案3】:

这是一个解决方案,它可以推广到具有不同块数的版本号(缩进的sapply + ifelse 行),并且可以处理混合数字和字母(mixedsort 行)。

library(gtools)
vsns  <- c("0.1.1", "0.10", "0.2.1", "0.2.1a", "0.20", "0.20.1.3")
v <- strsplit(vsns, "\\.")
tmp <- data.frame(sapply(1:max(sapply(v, length)), function(i)
    vv <- sapply(v, "[", i)
    ifelse(is.na(vv), "0", vv)
), stringsAsFactors=FALSE)
vsns[do.call(mixedorder, tmp)]

[1] "0.1.1"    "0.2.1"    "0.2.1a"   "0.10"     "0.20"     "0.20.1.3"

【讨论】:

感谢您的指点。但我宁愿为此使用基本 R 功能。另外,使用我的vnsn,它引发了错误。 当然可以,但奇怪的是您的vsns 出现异常。它对我来说很好。 我明白了:Error in (function (x) : unused arguments (X1 = c("0", "0", "0", "0", "0", "0"), X2 = c("1", "10", "2", "2", "20", "20"), X3 = c("1", "0", "1", "1a", "0", "1"), X4 = c("0", "0", "0", "0", "0", "3")) 好的,因为您已经接受了最佳答案,所以不会进一步调试它。【参考方案4】:

这个有用吗

vsns  <- c("1", "10", "1.1", "1.10", "1.2", "1.1.1", 
       "1.1.10", "1.1.2", "1.1.1.1", "1.1.1.10", "1.1.1.2")
x <- strsplit(vsns, "\\.")
max.length <- max(sapply(x, function(i) max(nchar(i))))
y <- lapply(x, function(i) sprintf(as.numeric(i), fmt = paste0("%0", max.length, "d")))
y <- sapply(y, paste, collapse = ".")
vsns[order(y)]
# [1] "1"        "1.1"      "1.1.1"    "1.1.1.1"  "1.1.1.2"  "1.1.1.10"
# [7] "1.1.2"    "1.1.10"   "1.2"      "1.10"     "10"      

【讨论】:

看起来不错! [1] "1" "1.1" "1.1.1" "1.1.1.1" "1.1.1.2" "1.1.1.10" "1.1.2" [8] "1.1.10" "1.2" "1.10" "10" 是的,我不知道你想如何准确地订购它。但是,如果您在数字较少的数字之前添加零,那么对字符串进行排序就可以了:)【参考方案5】:

试试:

ll  = strsplit(vsns,'\\.')
dd = data.frame(t(sapply(ll, c)))
dd = data.frame(apply(dd, 2, function(x) as.numeric(as.character(x))))
dd = with(dd, dd[order(X1,X2,X3),])
ans = apply(dd, 1, paste, collapse=".")
ans
       1        2        3        4 
 "0.1.1"  "0.2.1" "0.10.1" "0.20.1" 

【讨论】:

这引发了一些错误,但您也有过时的 vsns 字符串开头。抱歉,我相应地更新了我的问题

以上是关于对包含语义版本的字符向量进行排序的主要内容,如果未能解决你的问题,请参考以下文章

使用 SQL 进行语义版本排序

潜在语义分析(LSA)

语义分割之车道线检测Lanenet(tensorflow版)

使用 Python 语义对 R 中的嵌套列表进行排序

首先创建字符串然后通过移动语义将其添加到向量或在向量中创建元素是不是具有内存效率?

潜在语义分析的介绍