Ответ 1
Использование большого количества некоторых идиом R:
> split(v, cumsum(c(1, diff(v) != 1)))
$`1`
[1] 1
$`2`
[1] 3 4 5
$`3`
[1] 9 10
$`4`
[1] 17
$`5`
[1] 29 30
У меня есть вектор, такой как c(1, 3, 4, 5, 9, 10, 17, 29, 30)
, и я хотел бы объединить "соседние" элементы, которые образуют регулярную последовательную последовательность в оборванном векторе, в результате чего:
L1:1
L2: 3,4,5
L3: 9,10
L4: 17
L5: 29,30
Наивный код (программиста ex-C):
partition.neighbors <- function(v)
{
result <<- list() #jagged array
currentList <<- v[1] #current series
for(i in 2:length(v))
{
if(v[i] - v [i-1] == 1)
{
currentList <<- c(currentList, v[i])
}
else
{
result <<- c(result, list(currentList))
currentList <<- v[i] #next series
}
}
return(result)
}
Теперь я понимаю, что
a) R не является C (несмотря на фигурные скобки)
b) глобальные переменные являются чистым злом
c), что является ужасно неэффективным способом достижения результата
поэтому любые лучшие решения приветствуются.
Использование большого количества некоторых идиом R:
> split(v, cumsum(c(1, diff(v) != 1)))
$`1`
[1] 1
$`2`
[1] 3 4 5
$`3`
[1] 9 10
$`4`
[1] 17
$`5`
[1] 29 30
daroczig пишет: "вы можете написать много кода neater на основе diff
"...
Здесь один из способов:
split(v, cumsum(diff(c(-Inf, v)) != 1))
EDIT (добавленные тайминги):
Томми обнаружил, что это может быть быстрее, если вы будете осторожны с типами; причина, по которой он ускоряется, заключается в том, что split
быстрее целых чисел и на самом деле быстрее работает на факторах.
Здесь решение Джошуа; результат из cumsum
является числовым, поскольку он c
'd с 1
, поэтому он является самым медленным.
system.time({
a <- cumsum(c(1, diff(v) != 1))
split(v, a)
})
# user system elapsed
# 1.839 0.004 1.848
Просто c
ing с 1L
, поэтому результат представляет собой целое число, значительно ускоряющее его.
system.time({
a <- cumsum(c(1L, diff(v) != 1))
split(v, a)
})
# user system elapsed
# 0.744 0.000 0.746
Это решение Tommy, для справки; он также расщепляется на целое число.
> system.time({
a <- cumsum(c(TRUE, diff(v) != 1L))
split(v, a)
})
# user system elapsed
# 0.742 0.000 0.746
Здесь мое оригинальное решение; он также разбивается на целое число.
system.time({
a <- cumsum(diff(c(-Inf, v)) != 1)
split(v, a)
})
# user system elapsed
# 0.750 0.000 0.754
Здесь Joshua's, с результатом, преобразованным в целое число до split
.
system.time({
a <- cumsum(c(1, diff(v) != 1))
a <- as.integer(a)
split(v, a)
})
# user system elapsed
# 0.736 0.002 0.740
Все версии, которые split
для целочисленного вектора примерно одинаковы; он может быть еще быстрее, если этот целочисленный вектор уже был фактором, поскольку преобразование из целого числа в фактор фактически занимает около половины времени. Здесь я делаю это напрямую; это не рекомендуется вообще, потому что это зависит от структуры факторного класса. Это делается здесь только для сравнения.
system.time({
a <- cumsum(c(1L, diff(v) != 1))
a <- structure(a, class = "factor", levels = 1L:a[length(a)])
split(v,a)
})
# user system elapsed
# 0.356 0.000 0.357
Вы можете создать data.frame
и назначить элементы для групп с помощью diff
, ifelse
и cumsum
, затем скомпилировать с помощью tapply
:
v.df <- data.frame(v = v)
v.df$group <- cumsum(ifelse(c(1, diff(v) - 1), 1, 0))
tapply(v.df$v, v.df$group, function(x) x)
$`1`
[1] 1
$`2`
[1] 3 4 5
$`3`
[1] 9 10
$`4`
[1] 17
$`5`
[1] 29 30
Джошуа и Аарон были на месте. Однако их код можно сделать более чем в два раза быстрее, тщательно используя правильные типы, целые числа и логические элементы:
split(v, cumsum(c(TRUE, diff(v) != 1L)))
v <- rep(c(1:5, 19), len = 1e6) # Huge vector...
system.time( split(v, cumsum(c(1, diff(v) != 1))) ) # Joshua code
# user system elapsed
# 2.64 0.00 2.64
system.time( split(v, cumsum(c(TRUE, diff(v) != 1L))) ) # Modified code
# user system elapsed
# 1.09 0.00 1.12
Вы легко можете определить разрезы:
which(diff(v) != 1)
Исходя из этой попытки:
v <- c(1,3,4,5,9,10,17,29,30)
cutpoints <- c(0, which(diff(v) != 1), length(v))
ragged.vector <- vector("list", length(cutpoints)-1)
for (i in 2:length(cutpoints)) ragged.vector[[i-1]] <- v[(cutpoints[i-1]+1):cutpoints[i]]
Результат:
> ragged.vector
[[1]]
[1] 1
[[2]]
[1] 3 4 5
[[3]]
[1] 9 10
[[4]]
[1] 17
[[5]]
[1] 29 30
Этот алгоритм не очень хороший, но вы можете написать много кода neater на основе diff
:) Удачи!