Я бы хотел случайным образом выбрать из массива определенное количество элементов таким образом, чтобы они всегда соблюдали ограничение в их взаимном расстоянии.
Например, имея вектор a <- seq(1,1000)
, как я могу выбрать 20 элементов с минимальным расстоянием 15 между собой?
На данный момент я использую простую итерацию, для которой я отклоняю выбор всякий раз, когда он находится слишком близко к какому-либо элементу, но он громоздок и имеет тенденцию быть длинным, если число элементов для выбора велико. Есть ли передовая практика / функция для этого?
РЕДАКТИРОВАТЬ - Сводка ответов и анализа
До сих пор у меня было два рабочих ответа, которые я обернул в две конкретные функции.
# dash2 approach
# ---------------
rand_pick_min <- function(ar, min.dist, n.picks){
stopifnot(is.numeric(min.dist),
is.numeric(n.picks), n.picks%%1 == 0)
if(length(ar)/n.picks < min.dist)
stop('The number of picks exceeds the maximum number of divisions that the array allows which is: ',
floor(length(ar)/min.dist))
picked <- array(NA, n.picks)
copy <- ar
for (i in 1:n.picks) {
stopifnot(length(copy) > 0)
picked[i] <- sample(copy, 1)
copy <- copy[ abs(copy - picked[i]) >= min.dist ]
}
return(picked)
}
# denis approach
# ---------------
rand_pick_min2 <- function(ar, min.dist, n.picks){
require(Surrogate)
stopifnot(is.numeric(min.dist),
is.numeric(n.picks), n.picks%%1 == 0)
if(length(ar)/n.picks < min.dist)
stop('The number of picks exceeds the maximum number of divisions that the array allows which is: ',
floor(length(ar)/min.dist))
lar <- length(ar)
dist <- Surrogate::RandVec(a=min.dist, b=(lar-(n.picks)*min.dist),
s=lar, n=(n.picks+1), m=1, Seed=sample(1:lar, size = 1))$RandVecOutput
return(cumsum(round(dist))[1:n.picks])
}
Используя тот же пример, я запустил 3 теста. Во-первых, эффективная действительность минимального лимита
# Libs
require(ggplot2)
require(microbenchmark)
# Inputs
a <- seq(1, 1000) # test vector
md <- 15 # min distance
np <- 20 # number of picks
# Run
dist_vec <- c(sapply(1:500, function(x) c(dist(rand_pick_min(a, md, np))))) # sol 1
dist_vec2 <- c(sapply(1:500, function(x) c(dist(rand_pick_min2(a, md, np))))) # sol 2
# Tests - break the min
cat('Any distance breaking the min in sol 1?', any(dist_vec < md), '\n') # FALSE
cat('Any distance breaking the min in sol 2?', any(dist_vec2 < md), '\n') # FALSE
Во-вторых, я проверил распределение полученных расстояний, получив первые два графика в порядке решения (sol1 [A] - это sol для dash2, а sol2 [B] - для denis ').
pa <- ggplot() + theme_classic() +
geom_density(aes_string(x = dist_vec), fill = 'lightgreen') +
geom_vline(aes_string(xintercept = mean(dist_vec)), col = 'darkred') + xlab('Distances')
pb <- ggplot() + theme_classic() +
geom_density(aes_string(x = dist_vec2), fill = 'lightgreen') +
geom_vline(aes_string(xintercept = mean(dist_vec)), col = 'darkred') + xlab('Distances')
print(pa)
print(pb)
Наконец, я вычислил время вычислений, необходимое для двух подходов, следующим образом и получив последнюю цифру.
comp_times <- microbenchmark::microbenchmark(
'solution_1' = rand_pick_min(a, md, np),
'solution_2' = rand_pick_min2(a, md, np),
times = 500
)
ggplot2::autoplot(comp_times); ggsave('stckoverflow2.png')
Ободрившись результатами, я спрашиваю себя, должно ли быть ожидаемое распределение расстояний или это отклонение из-за примененных методов.
EDIT2 - Ответ на последний вопрос после комментария, сделанного Денисом
Используя намного больше процедур отбора проб (5000), я подготовил PDF-файл с результирующими позициями, и ваш подход действительно содержит некоторый артефакт, который заставляет ваше решение (B) отклоняться от того, которое мне было нужно. Тем не менее, было бы интересно иметь возможность обеспечить конкретное окончательное распределение позиций.