【问题标题】:Split vector into balanced list (balancing sum of list elements)将向量拆分为平衡列表(平衡列表元素的总和)
【发布时间】:2018-07-18 13:11:19
【问题描述】:

很难找到以下问题的有效解决方案。这个问题非常冗长,因为我不确定我是否让这个问题变得更难。

给定一个命名向量

t <- c(2, 0, 0, 30, 0, 0, 10, 2000, 0, 20, 0, 40, 60, 10)
names(t) <- c(1, 0, 0, 2, 0, 0, 3, 4, 0, 5, 0, 6, 7, 8)

我想将t 拆分为一个包含 4 个元素的列表,该列表基于结果列表元素的总和进行平衡,同时保持元素的顺序,并且仅拆分非零元素。预期结果

L[1] <- c(2, 0, 0, 30, 0, 0, 10) # sum = 42
L[2] <- c(2000, 0)               # sum = 2000
L[3] <- c(20, 0, 40)             # sum = 60
L[4] <- c(60, 10)                # sum = 70

我使用的错误函数是最小化sd(rowSums(L))sd(sapply(L, sum))

尝试使用类似以下的方法分割向量并不完全有效

split(t, cut(cumsum(t), 4))

# $`(-0.17,544]`
 # 1  0  0  2  0  0  3 
 # 2  0  0 30  0  0 10 

# $`(544,1.09e+03]`
# named numeric(0)

# $`(1.09e+03,1.63e+03]`
# named numeric(0)

# $`(1.63e+03,2.17e+03]`
   # 4    0    5    0    6    7    8 
# 2000    0   20    0   40   60   10 

我写了一个函数来按照我想要的方式拆分列表(参见上面的错误函数)

break_at <- function(val, nchunks) {
    nchunks <- nchunks - 1
    nonzero <- val[val != 0]
    all_groupings <- as.matrix(gtools::permutations(n = 2, r = length(nonzero), v = c(1, 0), repeats.allowed = TRUE))
    all_groupings <- all_groupings[rowSums(all_groupings) == nchunks, ]
    which_grouping <- which.min(
    sapply(
        1:nrow(all_groupings), 
        function(i) { 
            sd(
                sapply(
                    split(
                        nonzero, 
                        cumsum(all_groupings[i,])
                    ), 
                    sum
                )
            )
        }
    )
    )
    mark_breaks <- rep(0, length(val))
    mark_breaks[names(val) %in% which(all_groupings[which_grouping,]==1)] <- 1
    return(mark_breaks)
}

你可以看到效果好多了

break_at(t, 4)
# 0 0 0 0 0 0 0 1 0 1 0 0 1 0

split(t, cumsum(break_at(t, 4)))

# $`0`
 # 1  0  0  2  0  0  3 
 # 2  0  0 30  0  0 10 

# $`1`
   # 4    0 
# 2000    0 

# $`2`
 # 5  0  6 
# 20  0 40 

# $`3`
 # 7  8 
# 60 10 

它通过使用gtools::permutations(n = 2, r = length(nonzero), v = c(1, 0), repeats.allowed = TRUE) 来查看所有潜在的分裂。看看以上对r = 3的工作原理

     # [,1] [,2] [,3]
# [1,]    0    0    0
# [2,]    0    0    1
# [3,]    0    1    0
# [4,]    0    1    1
# [5,]    1    0    0
# [6,]    1    0    1
# [7,]    1    1    0
# [8,]    1    1    1

然后我过滤,all_groupings[rowSums(all_groupings) == nchunks, ]。这仅关注产生nchunks 的潜在分裂。

我的问题是,由于涉及的排列数量,这对我的真实数据来说非常有效。

hard <- structure(c(2, 0, 1, 2, 0, 1, 1, 1, 5, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1,
1, 1, 2, 0, 2, 0, 1, 4, 0, 0, 0, 1, 3, 0, 0, 4, 0, 0, 0, 2, 0,
1, 1, 1, 3, 0, 0, 1, 1, 1, 1, 2, 0, 1, 2, 0, 1, 1, 2, 0, 1, 6,
0, 0, 0, 0, 0, 1, 1, 1, 3, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0,
1, 1, 2, 0, 1, 2, 0, 1, 1, 4, 0, 0, 0, 1, 1, 3, 0, 0, 1, 2, 0,
1, 1, 2, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1, 1, 1, 2, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 2, 0, 3,
0, 0, 1, 1, 2, 0, 1, 2, 0, 1, 1, 1, 2, 0, 2, 0, 1, 3, 0, 0, 1,
1, 1, 1, 1, 2, 0, 1, 1, 1, 2, 0, 1, 2, 0, 1, 1, 1, 1, 1, 1, 2,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
0, 1, 1, 1, 1, 1, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 2, 0, 1, 1, 1, 2, 0, 1, 1, 1, 2, 0, 8, 0, 0, 0, 0, 0, 0, 0,
1, 2, 0, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1,
3, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1,
1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 3, 0,
0, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1,
1, 1, 1, 2, 0, 1, 1, 1, 1, 5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 2, 0, 1, 1, 1, 1, 2, 0, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 2, 0, 1, 1, 2, 0, 1, 2, 0, 1, 8, 0, 0, 0, 0, 0, 0, 0, 2,
0, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 0, 0, 0, 1, 1, 1,
1, 6, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 1, 3, 0, 0, 1, 1, 1, 3,
0, 0, 7, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1), .Names = c("1", "0",
"2", "3", "0", "4", "5", "6", "7", "0", "0", "0", "0", "8", "9",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
"21", "22", "23", "24", "0", "0", "25", "26", "27", "28", "29",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "0",
"40", "41", "42", "43", "0", "44", "45", "46", "47", "48", "49",
"50", "51", "52", "0", "53", "0", "54", "55", "0", "0", "0",
"56", "57", "0", "0", "58", "0", "0", "0", "59", "0", "60", "61",
"62", "63", "0", "0", "64", "65", "66", "67", "68", "0", "69",
"70", "0", "71", "72", "73", "0", "74", "75", "0", "0", "0",
"0", "0", "76", "77", "78", "79", "0", "0", "80", "81", "82",
"83", "84", "85", "86", "87", "88", "0", "89", "90", "91", "0",
"92", "93", "0", "94", "95", "96", "0", "0", "0", "97", "98",
"99", "0", "0", "100", "101", "0", "102", "103", "104", "0",
"105", "106", "0", "0", "107", "108", "0", "0", "109", "110",
"111", "112", "0", "113", "114", "115", "116", "117", "118",
"119", "120", "121", "122", "123", "124", "125", "126", "127",
"128", "129", "130", "131", "0", "132", "133", "134", "0", "135",
"0", "0", "136", "137", "138", "0", "139", "140", "0", "141",
"142", "143", "144", "0", "145", "0", "146", "147", "0", "0",
"148", "149", "150", "151", "152", "153", "0", "154", "155",
"156", "157", "0", "158", "159", "0", "160", "161", "162", "163",
"164", "165", "166", "0", "167", "168", "169", "170", "171",
"172", "173", "174", "175", "176", "177", "178", "179", "180",
"181", "182", "183", "184", "185", "186", "0", "187", "188",
"189", "190", "191", "192", "0", "0", "0", "0", "0", "0", "0",
"0", "0", "0", "193", "194", "195", "196", "197", "0", "198",
"199", "200", "201", "0", "202", "203", "204", "205", "0", "206",
"0", "0", "0", "0", "0", "0", "0", "207", "208", "0", "209",
"210", "211", "212", "213", "214", "215", "0", "216", "217",
"218", "219", "220", "221", "0", "222", "223", "224", "225",
"0", "0", "226", "227", "228", "229", "230", "231", "232", "233",
"234", "235", "236", "237", "238", "239", "240", "0", "241",
"242", "243", "244", "245", "246", "247", "248", "0", "249",
"250", "251", "252", "253", "254", "0", "255", "256", "257",
"258", "259", "260", "0", "0", "261", "262", "263", "264", "0",
"265", "266", "267", "268", "269", "270", "271", "272", "273",
"274", "0", "275", "276", "277", "278", "279", "280", "281",
"282", "0", "283", "284", "285", "286", "287", "0", "0", "0",
"0", "288", "0", "0", "0", "0", "0", "289", "290", "291", "292",
"293", "294", "295", "296", "297", "298", "299", "300", "301",
"302", "303", "304", "305", "306", "307", "308", "309", "310",
"311", "312", "313", "314", "315", "316", "317", "318", "319",
"320", "321", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0",
"0", "0", "322", "323", "324", "325", "326", "327", "328", "329",
"330", "331", "332", "333", "334", "335", "336", "337", "338",
"339", "340", "341", "0", "342", "343", "344", "345", "346",
"0", "347", "0", "348", "349", "350", "351", "352", "353", "354",
"355", "356", "357", "358", "359", "360", "0", "361", "362",
"363", "0", "364", "365", "0", "366", "367", "0", "0", "0", "0",
"0", "0", "0", "368", "0", "369", "370", "0", "0", "0", "0",
"0", "0", "0", "0", "371", "0", "0", "372", "0", "0", "0", "373",
"374", "375", "376", "377", "0", "0", "0", "0", "0", "378", "0",
"0", "0", "0", "0", "379", "380", "0", "0", "381", "382", "383",
"384", "0", "0", "385", "0", "0", "0", "0", "0", "0", "386",
"387", "388", "0", "389", "390", "391", "392", "393", "394",
"395", "396", "397", "398", "399", "400", "401", "402", "0",
"403", "404", "405", "406", "407", "408", "409"))

【问题讨论】:

  • 我会尽可能提供赏金......
  • 我尝试了一半,spleq &lt;- function(x, n) { sc &lt;- sum(x)/n; split(x, (cumsum(x) + floor(sc) ) %/% (sum(x) / n)) } 之类的东西似乎可以正常工作,但仍有一些问题。
  • @thelatemail 它非常接近...无论如何我都会考虑你的想法
  • 没有严格定义“平衡”是什么意思。您可能应该定义一个要最小化的误差函数。
  • @algrid 你是对的。我应该更清楚地说明这一点。更新了我的帖子

标签: r algorithm vector split


【解决方案1】:

我不知道是否有一些分析解决方案。但是,如果您将其视为integer programming problem,则可以使用在optim 中实现的“SANN”启发式算法。例如,考虑一些(次优)随机分割点来切割向量t

> startpar <- sort(sample(length(t)-1, 3))
> startpar
[1] 5 6 9
> # result in a sub-optimal split
> split(t, cut(1:length(t), c(0, startpar, length(t)), labels = 1:4))
$`1`
 1  0  0  2  0 
 2  0  0 30  0 

$`2`
0 
0 

$`3`
   3    4    0 
  10 2000    0 

$`4`
 5  0  6  7  8 
20  0 40 60 10 

误差函数可以写成

> # from manual: A function to be minimized (or maximized)
> fn <- function(par, vec){
+   ind_vec <- cut(1:length(vec), c(0, par, length(vec)), labels = 1:4)
+   sd(unlist(lapply(split(vec, ind_vec), sum)))
+ }
> # evaluated at the starting parameters
> fn(startpar, t)
[1] 979.5625

“SANN”启发式 (Simulated annealing) 需要一种方法来生成新的候选解决方案。可以有更复杂的方法来选择函数或起始值,但目前的选择仍然导致/an [edit:] 接近 最佳解决方案(并且可能在可接受的时间内?)。

> # from manual: For the "SANN" method it specifies a function to generate a new candidate point
> gr <- function(par, vec){
+   ind <- sample(length(par), 1)
+   par[ind] <- par[ind] + sample(-1:1, 1)
+   par[ind] <- max(c(par[ind], ifelse(ind == 1, 1, par[ind - 1] + 1)))
+   par[ind] <- min(c(par[ind], ifelse(ind == 3, length(vec) - 1, par[ind + 1] - 1)))
+   par
+ }

应用于玩具数据

> optimpar <- optim(startpar, fn, gr, method = "SANN", vec = t)$par
> split(t, cut(1:length(t), c(0, optimpar, length(t)), labels = 1:4))
$`1`
 1  0  0  2 
 2  0  0 30 

$`2`
 0  0  3 
 0  0 10 

$`3`
   4 
2000 

$`4`
 0  5  0  6  7  8 
 0 20  0 40 60 10 

> fn(optimpar, t)
[1] 972.7329
> 

应用于真实数据

> # use for "hard"
> startpar <- sort(sample(length(hard)-1, 3))
> optimpar <- optim(startpar, fn, gr, method = "SANN", vec = hard)
> optimpar
$par
[1] 146 293 426

$value
[1] 4.573474
...[output shortened]

[编辑],因为我的初始结果并不理想。

我确定您自己已经找到了足够的替代方案,但为了完整起见:关于当前的玩具和真实数据示例,gr 是更好的选择(我将其称为gr2 以供以后参考)将具有不同的采样长度(例如,取决于数据的长度)以生成新的候选者,该候选者将更少地依赖于现任(当前解决方案)。例如

> gr2 <- function(par, vec){
+   ind <- sample(length(par), 1)
+   l <- round(log(length(vec), 2))
+   par[ind] <- par[ind] + sample(-l:l, 1)
+   par[ind] <- max(c(par[ind], ifelse(ind == 1, 1, par[ind - 1] + 1)))
+   par[ind] <- min(c(par[ind], ifelse(ind == 3, length(vec) - 1, par[ind + 1] - 1)))
+   par
+ }

对于导致的真实数据

> set.seed(1337)
> 
> startpar <- sort(sample(length(hard)-1, 3))
> opt <- optim(startpar, fn, gr2, method = "SANN", vec = hard)
> opt$value
[1] 4.5
> lapply(split(hard, cut(1:length(hard), c(0, opt$par, length(hard)), labels = 1:4)), sum)
$`1`
[1] 140

$`2`
[1] 141

$`3`
[1] 144

$`4`
[1] 150

对于玩具数据导致

> startpar <- sort(sample(length(t)-1, 3))
> opt <- optim(startpar, fn, gr2, method = "SANN", vec = t)
> opt$value
[1] 971.4024
> split(t, cut(1:length(t), c(0, opt$par, length(t)), labels = 1:4))
$`1`
 1  0  0  2  0  0  3 
 2  0  0 30  0  0 10 

$`2`
   4 
2000 

$`3`
 0  5  0  6 
 0 20  0 40 

$`4`
 7  8 
60 10 

关于真实数据的最优性(使用gr2),我从不同的起始参数运行了 100 次优化运行的简短模拟:每次运行都以 4.5 的值终止。

【讨论】:

  • 谢谢汤姆 - 我想知道模拟退火方法,这给了我一个开始的地方。它产生的分割不是 100% 理想的,但我会尝试一下,看看是否能产生我想要的输出。
  • 我的荣幸。啊,对;我没有彻底检查结果。您可以稍微更改 gr 函数以生成更复杂的新候选者,例如通过将采样间隔扩大到 [-3,3] (par[ind]
  • 这很棒。我会尽可能提供赏金(14 小时左右)。另外,让我玩几天。起初我并没有真正理解您的渐变功能,但您的更新有助于澄清一些事情。我也有一个问题;在模拟退火中,通常有一个影响“重新采样”的温度计划,这在这里起作用吗?如果有的话,它是如何影响梯度函数的?
  • 那太好了。您可以通过控制选项(temptmaxmaxit;请参阅optim 手册了解详细信息)控制温度及其冷却方案。在当前的gr-方法中,tempt 都不会进入新候选的。我实际上从未尝试从optim-call 中的grfn 访问对象(例如控制参数)(甚至不知道这是否可能)。当前温度至少会影响更差新候选人被选为新在职者而不是更好的当前解决方案的概率(以避免局部最优)。
【解决方案2】:

通过使用动态规划,您可以在 O(N^2) 时间内获得真正的最优值。诀窍是看到最小化标准偏差与最小化 rowSums 的平方和相同。由于每个子向量的误差贡献是独立的,我们可以减少搜索空间 通过忽略子向量的次优拆分的扩展来进行可能的拆分。

如果例如(3, 5)V[1:7] 更适合(2, 4),那么 以(3, 5, 8,...) 开头的V 的每个拆分都比以(3, 5, 8,...) 开头的每个拆分都好 (2, 4, 8, ...)。 因此,如果我们为每个 1 &lt; k &lt; len(V) 找到 'V[1:k]' 的最佳 2 组拆分, 通过仅考虑子向量V[1:k] 的最佳 2 组拆分的扩展,我们可以将每个 V[1:k] 的最佳拆分为 3 组。一般来说,我们通过扩展最优的 n 组分裂来找到最好的 (n+1) 组。

下面的balanced.split 函数接受一个值向量和分割数,并返回一个子向量列表。这会在硬集上产生行和 140,141,144,150 的解决方案。

balanced.split <- function(all.values, n.splits) {
    nonzero.idxs <- which(all.values!=0)
    values <- all.values[nonzero.idxs]
    cumsums = c(0, cumsum(values))
    error.table <- outer(cumsums, cumsums, FUN='-')**2
    # error.table[i, j] = error contribution of segment
    # values[i:(j-1)]

    # Iteratively find best i splits
    index.matrix <- array(dim=c(n.splits-1, ncol(error.table)))
    cur.best.splits <- error.table[1, ]
    for (i in 1:(n.splits-1)){
        error.sums <- cur.best.splits + error.table
        index.matrix[i, ] <- apply(error.sums, 2, which.min)
        # index.matrix[i, k] = last split of optimal (i+1)-group
        # split of values[1:k]
        cur.best.splits <- apply(error.sums, 2, min)
        # cur.best.splits[k] = minimal error function
        # of (i+1)-group split of values[1:k]
    }
    # Trace best splits
    cur.idx <- ncol(index.matrix)
    splits <- vector("numeric", n.splits-1)
    for (i in (n.splits-1):1) {
        cur.idx = index.matrix[i, cur.idx]
        splits[i] <- cur.idx
    }
    # Split values vector
    splits <- c(1, nonzero.idxs[splits], length(all.values)+1)
    chunks <- list()
    for (i in 1:n.splits)
        chunks[[i]] <- all.values[splits[i]:(splits[i+1]-1)]
    return(chunks)
}

下面是相同算法的更详细代码

# Matrix containing the error contribution of 
# subsegments [i:j]
.makeErrorTable <- function(values) {
    cumsums = c(0, cumsum(values))
    return(outer(cumsums, cumsums, FUN='-')**2)
}

# Backtrace the optimal split points from an index matrix
.findPath <- function(index.matrix){
    nrows <- nrow(index.matrix)
    cur.idx <- ncol(index.matrix) 
    path <- vector("numeric", nrows)
    for (i in nrows:1) {
        cur.idx = index.matrix[i, cur.idx]
        path[i] <- cur.idx
    }
    return(path)
}

.findSplits <- function(error.table, n.splits) {
    n.diffs <- nrow(error.table)
    max.val <- error.table[1, n.diffs]

    # Table used to backtrace the optimal path
    idx.table <- array(dim=c(n.splits-1, n.diffs))
    cur.best.splits <- error.table[1, ]
    for (i in 1:(n.splits-1)){
        error.sums <- cur.best.splits + error.table
        idx.table[i, ] <- apply(error.sums, 2, which.min)
        cur.best.splits <- apply(error.sums, 2, min)
    }
    return(.findPath(idx.table))
}

# Split values at given split points
.splitChunks <- function(values, splits) {
    splits <- c(1, splits, length(values)+1)
    chunks <- list()
    for (i in 1:(length(splits)-1))
        chunks[[i]] <- values[splits[i]:(splits[i+1]-1)]
    return(chunks)
}

#' Main function that splits all.values into n.splits
#' chunks, minimizing sd(sum(chunk))    
balanced.split <- function(all.values, n.splits) {
    nonzero.idxs <- which(all.values!=0)
    values <- all.values[nonzero.idxs]
    error.table <- .makeErrorTable(values)
    splits <- .findSplits(error.table, n.splits)
    full.splits <- nonzero.idxs[splits]
    return(.splitChunks(all.values, full.splits))
}

【讨论】:

  • 这看起来很理想。它考虑了拆分的所有组合(AFAIK),产生了我正在寻找的拆分,并且主要使用矢量化操作而不会太贪内存。我喜欢它,但我需要了解您所做的事情,并将其性能与 SA 方法进行比较。
  • 我现在添加了更多解释和 cmets。它考虑了所有拆分组合,但这可能是一个弱点,因为它的搜索空间仍然很大。
  • 嘿@kuppern87:看起来没有办法为同一个问题授予多个赏金(根据 Meta Stack)。这与模拟退火 IMO 一样好。但是,因为这使用了矢量化,所以它比模拟退火更消耗内存。因此,当内存有限时,模拟退火是可行的方法。如果内存没有限制,这就是要走的路。希望 stackoverflow 将来允许多个正确答案。 (我会投票赞成你之前的一些答案,以“奖励”你一些你共同应得的赏金)。
【解决方案3】:

以下解决方案是“将 [ting] t 拆分为 4 个元素的列表,根据结果列表元素的总和进行平衡,同时保持元素的顺序,并且仅拆分非零元素。”。

虽然它并没有产生您确切的预期输出,但据我了解,您的优化规则不是要求,而只是您试图获得这些平衡列表的东西。它应该是有效的:)。

t <- c(2, 0, 0, 30, 0, 0, 10, 2000, 0, 20, 0, 40, 60, 10)
groups <- cut(cumsum(t),
              breaks=quantile(cumsum(t),
                              probs=seq(0, 1, 0.25)),
              include.lowest =TRUE)

lapply(unique(groups),function(x) t[groups==x])

# [[1]]
# [1]  2  0  0 30  0  0
# 
# [[2]]
# [1] 10
# 
# [[3]]
# [1] 2000    0   20    0
# 
# [[4]]
# [1] 40 60 10

在您的hard 数据上,结果非常“平衡”:

t2 <- as.numeric(hard)
groups <- cut(cumsum(t2),
              breaks=quantile(cumsum(t2),
                              probs=seq(0, 1, 0.25)),
              include.lowest =TRUE)    

L2 <- lapply(unique(groups),function(x) t2[groups==x])
sapply(L2,sum)
# [1] 144 145 149 137

使用当前选择的解决方案与138 143 144 150 进行比较。

【讨论】:

  • 谢谢@Moody;这显然非常有效,但它不会产生我正在寻找的确切拆分(并且与 thelatemail 在 cmets 中的答案没有太大不同)。由于存在产生我正在寻找的确切拆分的答案,因此我需要考虑性能/结果的权衡。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多