Подтвердить что ты не робот

Сгенерировать множественные перестановки вектора с неповторяющимися элементами

У меня есть вектор:

seq1<-c('a','b','c','b','a','b','c','b','a','b','c')

Я хочу переставить элементы этого вектора для создания нескольких (в идеале до 5000) векторов с условием, что перестановленные векторы не могут иметь повторяющиеся элементы внутри вектора в последовательных элементах. например "abbca...." не допускается, так как "b-b" является повторением.

Я понимаю, что для этого небольшого примера, вероятно, не 5000 решений. Обычно я имею дело с гораздо большими векторами. Я также согласен рассмотреть выборку с заменой, хотя в настоящее время я работаю над решениями без замены.

Я ищу лучшие решения, чем мое текущее мышление.

Вариант 1. - грубая сила.

Здесь я просто повторяю выборку и проверяю, являются ли какие-либо последующие элементы дублирующими.

set.seed(18)
seq1b <-  sample(seq1a)
seq1b
#[1] "b" "b" "a" "a" "c" "b" "b" "c" "a" "c" "b"
sum(seq1b[-length(seq1b)]==seq1b[-1])  #3

Это не решение, так как есть три повторяющихся последовательных элемента. Я также понимаю, что lag, вероятно, лучший способ проверить дублирующиеся элементы, но по какой-то причине он тонкий (я думаю, что он замаскирован другим загруженным мной пакетом).

set.seed(1000)
res<-NULL
for (i in 1:10000){res[[i]]<-sample(seq1a)}
res1 <- lapply(res, function(x) sum(x[-length(x)]==x[-1]))
sum(unlist(res1)==0) #228

Это дает 228 опций из 10000 итераций. Но посмотрим, сколько уникальных:

res2 <- res[which(unlist(res1)==0)]
unique(unlist(lapply(res2, paste0, collapse="")))  #134

Из 10000 попыток мы получаем только 134 уникальных из этого короткого примерного вектора.

Вот 3 из 134 примерных последовательностей:

# "bcbabcbabca" "cbabababcbc" "bcbcababacb"

На самом деле, если я попробую более 500 000 образцов, я могу получить только 212 уникальных последовательностей, которые соответствуют моим не повторяющимся критериям. Вероятно, это близко к верхнему пределу возможных.

Вариант 2. - итеративно

Вторая идея, которую я имел, должна быть более итеративной в отношении подхода.

seq1a
table(seq1a)
#a b c 
#3 5 3

Мы могли бы выбрать одну из этих букв в качестве отправной точки. Затем выберите другой из остальных, проверьте, совпадает ли он с ранее выбранным, а если нет, добавьте его в конец. И так далее и т.д.

set.seed(10)
newseq <- sample(seq1a,1)  #b
newseq #[1] "b"

remaining <-seq1a[!seq1a %in% newseq | duplicated(seq1a)]
table(remaining)
#a b c 
#3 4 3 

set.seed(10)
newone <- sample(remaining,1) #c

#check if newone is same as previous one.
newone==newseq[length(newseq)] #FALSE
newseq <- c(newseq, newone) #update newseq
newseq #[1] "b" "c"

remaining <-seq1a[!seq1a %in% newseq | duplicated(seq1a)] #update remaining
remaining
table(remaining)

#a b c 
#3 4 2 

Это может сработать, но я также вижу, что он сталкивается с множеством проблем - например. мы могли бы пойти:

# "a" "c" "a" "c" "a" "b"  ...

а затем останется еще 3 b, которые не могут идти в конце, так как они будут дублировать.

Конечно, это было бы намного проще, если бы я разрешил выборку с заменой, но на данный момент я пытаюсь сделать это без замены.

4b9b3361

Ответ 1

Вы можете использовать пакет iterpc для работы с комбинациями и итерациями. Я не слышал об этом, пока не попытался ответить на этот вопрос, поэтому могут быть более эффективные способы использования одного и того же пакета.

Здесь я использовал iterpc для установки итератора и getall, чтобы найти все комбинации вектора на основе этого итератора. Кажется, это просто сообщает о уникальных комбинациях, что делает его немного приятнее, чем поиск всех комбинаций с expand.grid.

#install.packages("iterpc")
require("iterpc")

seq1 <- c('a','b','c','b','a','b','c','b','a','b','c')

I <- iterpc(n = table(seq1), ordered=TRUE)

all_seqs <- getall(I)

# result is a matrix with permutations as rows:
head(all_seqs)
#     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
#[1,] "a"  "a"  "a"  "b"  "b"  "b"  "b"  "b"  "c"  "c"   "c"  
#[2,] "a"  "a"  "a"  "b"  "b"  "b"  "b"  "c"  "b"  "c"   "c"  
#[3,] "a"  "a"  "a"  "b"  "b"  "b"  "b"  "c"  "c"  "b"   "c"  
#[4,] "a"  "a"  "a"  "b"  "b"  "b"  "b"  "c"  "c"  "c"   "b"  
#[5,] "a"  "a"  "a"  "b"  "b"  "b"  "c"  "b"  "b"  "c"   "c"  
#[6,] "a"  "a"  "a"  "b"  "b"  "b"  "c"  "b"  "c"  "b"   "c" 

Функция rle сообщает нам о последовательных значениях, равных друг другу в векторе. Компонент lengths выводит нам, сколько раз повторяется каждый элемент values:

rle(c("a", "a", "b", "b", "b", "c", "b"))

# Run Length Encoding
#   lengths: int [1:3] 2 3 1 1
#   values : chr [1:3] "a" "b" "c" "b"

Длина values или lengths будет равна длине исходного вектора только для комбинаций, у которых нет последовательных повторов.

Поэтому вы можете применить rle к каждой строке, вычислить длину values или lengths и сохранить строки из all_seqs, где вычисленное значение будет таким же, как длина seqs1.

#apply the rle function 
all_seqs_rle <- apply(getall(I), 1, function(x) length(rle(x)$values))

# keep rows which have an rle with a length equal to length(seq1)
all_seqs_good <- all_seqs[which(all_seqs_rle == length(seq1)), ]

all_seqs_good имеет nrow из 212, предполагая, что вы действительно нашли все возможные комбинации для вашего вектора-примера.

nrow(all_seqs_good)
# 212 

Технически это все еще грубое форсирование (за исключением того, что он не вычисляет все возможные комбинации - только уникальные), но довольно быстро для вашего примера. Я не уверен, насколько хорошо он справится с большими векторами...

Изменить: это, похоже, не работает для больших векторов. Одним из решений было бы разбить большие векторы на более мелкие куски, затем обработать эти куски, как указано выше, и объединить их - сохранив только комбинации, соответствующие вашим критериям.

Например, разбивая вектор длиной 24 на два вектора длиной 12, объединение этих результатов может дать вам 200 000 комбинаций, которые соответствуют вашему критерию и довольно быстро (около 1 минуты для меня):

# function based on the above solution
seq_check <- function(mySeq){
I = iterpc(n = table(mySeq), ordered=TRUE)
all_seqs <- getall(I)
all_seqs_rle <- apply(getall(I), 1, function(x) length(rle(x)$values))
all_seqs_good <- all_seqs[which(all_seqs_rle == length(mySeq)), ]
return(all_seqs_good)
}

set.seed(1)
seq1<-sample(c(rep("a", 8), rep("b", 8), rep("c", 8)),24)

seq1a <- seq1[1:12]
seq1b <- seq1[13:24]

#get all permutations with no consecutive repeats
seq1a = apply(seq_check(seq1a), 1, paste0, collapse="")
seq1b = apply(seq_check(seq1b), 1, paste0, collapse="")

#combine seq1a and seq1b: 
combined_seqs <- expand.grid(seq1a, seq1b)
combined_seqs <- apply(combined_seqs, 1, paste0, collapse="") 

#function to calculate rle lengths
rle_calc <- function(x) length(rle(unlist(strsplit(x, "")))$values)

#keep combined sequences which have rle lengths of 24
combined_seqs_rle <- sapply(combined_seqs, rle_calc)
passed_combinations <- combined_seqs[which(combined_seqs_rle == 24)]

#find number of solutions
length(passed_combinations)
#[1] 245832
length(unique(passed_combinations))
#[1] 245832

Вам может потребоваться переупорядочить стартовый вектор для получения наилучших результатов. Например, если seq1 в приведенном выше примере началось с "a" восемь раз подряд, не было бы пропущенных решений. Например, попробуйте расщепить решение с помощью seq1 <- c(rep("a", 8), rep("b", 8), rep("c", 8)), и вы не получите никаких решений, даже несмотря на то, что существует случайное количество решений для случайной последовательности.

Не похоже, что вам нужно найти все возможные комбинации прохождения, но если вы сделаете это для более крупных векторов, вам, вероятно, потребуется выполнить итерацию через I с помощью функции getnext из iterpc и проверить каждый в цикле, который будет очень медленным.

Ответ 2

Здесь другое решение. Пожалуйста, ознакомьтесь с комментариями в коде для объяснения алгоритма. В некотором роде он похож на ваш второй (итеративный) подход, но включает

  • a while, который гарантирует, что следующий элемент действителен
  • и критерий остановки для случая, когда остальные элементы обязательно образуют недопустимую комбинацию

Алгоритм также эффективен с более длинными векторами seq1, указанными в одном из ваших комментариев. Но я думаю, что производительность ухудшится, если у вас есть более уникальные элементы в seq1.

Здесь код: Сначала несколько определений

set.seed(1234)
seq1=c('a','b','c','b','a','b','c','b','a','b','c')

#number of attempts to generate a valid combination
Nres=10000

#this list will hold the results
#we do not have to care about memory allocation
res_list=list()

Теперь создайте комбинации

#the outer loop creates the user-defined number of combination attempts
for (i in 1:Nres) {
  #create a "population" from seq1
  popul=seq1
  #pre-allocate an NA vector of the same length as seq1
  res_vec=rep(NA_character_,length(seq1))
  #take FIRST draw from the population
  new_draw=sample(popul,1)
  #remove draw from population
  popul=popul[-match(new_draw,popul)]
  #save new draw
  res_vec[1]=new_draw

  #now take remaining draws
  for (j in 2:length(seq1)) {
    #take new draws as long as
    #1) new_draw is equal to the last draw and
    #2) as long as there are any valid elements left in popul
    while((new_draw==res_vec[j-1])&any(res_vec[j-1]!=popul)) {
      #take new draw
      new_draw=sample(popul,1)
    }
    #if we did not find a valid draw break inner loop
    if (new_draw==res_vec[j-1]) {
      break
    }
    #otherwise save new_draw ...
    res_vec[j]=new_draw
    #... and delete new draw from population
    popul=popul[-match(new_draw,popul)]
  }
  #this is to check whether we had to break the inner loop
  #if not, save results vector
  if (sum(is.na(res_vec[j]))==0) res_list[[length(res_list)+1]]=res_vec
}

Теперь проверьте результаты

#for each result vector in res_list:
#1) check whether all subsequent elements are different ---> sum(x[-1]==x[-length(x)])==0
#2) and whether we have the same number of elements as in seq1 ---> all.equal(table(x),table(seq1),check.attributes=FALSE)
sum(sapply(res_list,function(x) (sum(x[-1]==x[-length(x)])==0)&all.equal(table(x),table(seq1),check.attributes=FALSE)))
#6085

#the previous number should be the same as the length of res_list
length(res_list)
#6085

#check the number of unique solutions
length(unique(res_list))
#212

Ответ 3

Скорость вашей фактической работы будет зависеть от множества факторов (например, сколько возможных комбинаций прохождения существует), но я думаю, что вы можете сделать это относительно быстро, используя 2 цикла (аналогично тому, как вы наметили, но, возможно, быстрее)

  • Перенесите свой набор переменных и убедитесь, что нет последовательные значения.
  • Оцените, является ли переходящая перестановка уникальной для тех, которые уже выбраны

В следующем примере вы устанавливаете два значения для управления процессом поиска: nsuccess - желаемое количество многих уникальных перестановок; nmax - максимальное количество перестановок (устанавливает верхний предел времени вычисления)

Пример

seq1 <- c('a','b','c','b','a','b','c','b','a','b','c')
seq1

set.seed(1)
nsuccess <- 200
nmax <- 30000
res <- matrix(NA, nrow=length(seq1), ncol=nsuccess)
i <- 1
j <- 1
while(i <= nsuccess & j <= nmax){
  s1 <- sample(seq1)
  s1str <- paste(s1, collapse=",")
  test <- rle(s1)$lengths
  if(sum(test) == length(test)) { # check that no values are consecutive
    U <- unique(apply(res, 2, function(x){paste(x, collapse=",")}))
    if(!s1str %in% U){ # check if new permutation is unique
      res[,i] <- s1
      i <- i+1
    }
  }
  j <-j+1
}
print(paste("i =", i, "; j =", j))
res # view the unique permutations