Генерация псевдослучайной двоичной последовательности, в которой одно и то же число не встречается более двух раз подряд

Я хочу иметь возможность генерировать (псевдо)случайную двоичную последовательность (например, последовательность R и L, например RLLRRLR), которая уравновешивается таким образом, что один и тот же элемент не встречается в последовательности более двух раз подряд, и если я Например, есть последовательность из 20, я получаю по 10 штук. Есть ли в R функция, которая может сделать что-то подобное?

Я сам пытался написать такую ​​функцию. Вот попытка:

RL_seq <- function(n_L = 10, n_R = 10, max_consec = 2, initial_seq = NULL) {
  while(n_L > 0 | n_R > 0){
    side <- sample(c("R", "L"), 1)
    
    if (side == "R" & n_R > 0 & length(grep("R", tail(initial_seq, max_consec))) != max_consec) {
      initial_seq <- append(initial_seq, side)
      n_R <- n_R - 1
    } else if (side == "L" & n_L > 0 & length(grep("L", tail(initial_seq, max_consec))) != max_consec) {
      initial_seq <- append(initial_seq, side)
      n_L <- n_L - 1
    }
  }
  print(initial_seq)
}

# The function does not stop with the following seed
set.seed(1)
RL_seq()

Однако зависит от случая, застрянет код или нет. Я также надеялся, что смогу изменить правила последовательности (например, допустив 3 последовательных R), но код имеет тенденцию ломаться, если я касаюсь аргументов. На этом этапе я был бы счастлив, если бы мог запустить его с аргументами по умолчанию и не допустить зависания.

Я искал вокруг, но не могу найти ответа.

Два ответа, которые мы отправили до сих пор, предполагают, что n_L и n_R ненамного больше 20 или 30, поскольку розыгрыши, соответствующие вашим критериям max_consec, станут исчезающе маловероятными для более длинных последовательностей. Если вы хотите создавать более крупные последовательности, этот подход перестанет иметь смысл.

Jon Spring 13.03.2024 19:22

Стронг согласен с Джоном здесь - с более длинной последовательностью вы могли бы применить конструктивный подход: начать со случайного выбора LRLRLR... или RLRLRL, а затем случайным образом выбрать равное количество L и R для удвоения.

Gregor Thomas 13.03.2024 19:25

Я бы подумал о генераторе цепи Маркова на дублетах/тройках (чтобы за RR, LL никогда не следовал третий повтор), но я не знаю, как обеспечить баланс.

Ben Bolker 14.03.2024 01:29

Связанные бумага и последовательность.

jblood94 14.03.2024 12:41

Комментарий к вашей попытке: строка initial_seq <- append(initial_seq, side) — это антишаблон, называемый «выращивание объекта в цикле». С каждой итерацией вашего цикла whileinitial_seq становится длиннее, и это дорогостоящая операция, поскольку ей может не хватить места, где бы она ни хранилась на вашем диске, и ее придется скопировать в другое место, где будет достаточно места. Гораздо эффективнее «предварительно выделить» вектор, создав его уже с его окончательной длиной, а затем заполняя значения по ходу дела. Подробнее читайте в The R Inferno

Gregor Thomas 14.03.2024 14:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
5
240
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Здесь я сначала определяю сочетание элементов, затем несколько раз перемешиваю, пока не получится ничья без слишком большого количества последовательных выборов. Я уверен, что существует более эффективный в вычислительном отношении подход (например, когда мы не перезаписываем seq на каждой итерации), но этого может быть достаточно. Например, для max_consec = 2 требуется ~0,002 секунды или несколько секунд, чтобы найти розыгрыш без повторов (max_consec = 1), даже если для поиска потребуется более 100 тысяч розыгрышей.

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

RL_seq <- function(n_L = 10, n_R = 10, max_consec = 2) {
  # make a sequence that is all the Ls then all the Rs
  seq = c(rep("L", n_L), rep("R", n_R))
  consec = max_consec + 1 # we know the first attempt won't work
  while(consec > max_consec) {
    # overwrite the sequence with a shuffle of it
    seq = sample(seq, length(seq), replace = FALSE)
    # what is the maximum "run length encoding" (rle) length of the sequence?
    consec = max(rle(seq)[1]$lengths)
  }
  seq
}


RL_seq()

Ну, в частном случае max_consec = 1 вы будете более эффективны в вычислениях с if (runif (1) < .5) rep(c("L", "R"), n_L) else rep(c("R", "L"), n_L) ;)

Gregor Thomas 13.03.2024 19:21

Отличный момент, возможно, стоит добавить, если этот случай необходим для большей эффективности. Мне интересно, какие подходы будут иметь смысл, когда n_L/n_R станут больше, например, 40, 50 или даже 1000, так что простое случайное рисование вряд ли найдет рабочую последовательность при первом, скажем, миллионе или миллиарде розыгрышей.

Jon Spring 13.03.2024 19:27

При таком подходе мы генерируем n_try последовательности-кандидаты с равным количеством единиц и нулей (при желании вы можете изменить их на R и L в конце). Затем мы отфильтровываем любые последовательности, которые не удовлетворяют условию максимального последовательного значения.

С помощью этого начального числа генерация 100 последовательностей-кандидатов длиной 10 дает 13 подходящих результатов. (Считайте каждый столбец результата последовательностью.)

set.seed(47)
n_try = 1e2
out_length = 10
n_l = out_length / 2
n_r = out_length / 2
max_in_a_row = 2
letter_space = c(rep(1, n_l), rep(0, n_r))
r = replicate(n_try, sample(letter_space, size = out_length, replace = FALSE))

r = r[, apply(r, MARGIN = 2, FUN = \(x) sum(diff(x) == 0) <= max_in_a_row)]
r
#       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13]
#  [1,]    1    0    0    0    0    1    0    0    0     1     0     0     0
#  [2,]    0    1    1    1    1    1    1    1    1     0     1     1     1
#  [3,]    0    0    1    0    0    0    0    0    0     1     0     1     0
#  [4,]    1    0    0    0    1    1    1    1    1     0     0     0     0
#  [5,]    0    1    1    1    0    0    0    0    1     0     1     1     1
#  [6,]    1    1    0    1    1    0    1    1    0     1     0     0     1
#  [7,]    0    0    0    0    0    1    1    0    1     0     1     1     0
#  [8,]    1    1    1    1    1    0    0    1    0     1     0     0     1
#  [9,]    0    0    0    0    1    1    1    1    1     1     1     0     0
# [10,]    1    1    1    1    0    0    0    0    0     0     1     1     1

Для результата в 20 длин я поднял n_try до 1e5, в результате чего получилось 108 квалификационных последовательностей менее чем за секунду.

ОП/Саймон отметил, что мой подход был быстрым, но чрезмерно ограничительным; с принудительными проверками, неблагоприятно ограничивающими диапазон возможностей в начале строк; В одном из комментариев Грегора упоминалась идея сделать половину, а затем удвоить; поэтому мой окончательный подход использует именно такую ​​идею; принудительная ребалансировка произойдет только во второй половине строящейся колонны. У меня было предупреждение на случай сбоя; Я оставляю старое решение под новым:

library(tidyverse)

RL_v2 <- function(desired_length, max_consec = 2) {
  stopifnot(desired_length %% 2==0)
  half_length <- desired_length / 2
  possible_tokens <- c("R", "L")
  # later will use the following tokens when building an explain list
  #  explain_tokens <- c("1","2","O","F")
  # 1 - we had to add 1/R to balance 
  # 2 - we had to add 2/L to balance
  # we had to choose opposite to previous to not make too long consecutive
  # we had a free choice 
  rlseq <- character(0)
  explain_seq <- character(0)
  while (length(rlseq) < desired_length) {
    
    current_tail <- tail(rlseq, max_consec)
    suppressWarnings(max_tail <- max(rle(current_tail)[1L]$lengths))
    if (max_tail < max_consec) {
      # we are free to choose either R or L ; 
      # must we choose one or the other to maintain 'parity'? 
      sumdiff <- (sum(rlseq==possible_tokens[[1]])-
                    sum(rlseq==possible_tokens[[2]]))
      if (sumdiff<0 & length(rlseq)>=half_length){
        # more 2 than 1 so must add 1 
        rlseq <- c(rlseq, possible_tokens[[1]])
        explain_seq <- c(explain_seq,"1")
      } else if (
        sumdiff>0  & length(rlseq)>=half_length
      ){
        # more 1 than 2 so must add 2
        rlseq <- c(rlseq, possible_tokens[[2]])
        explain_seq <- c(explain_seq,"2")
      } else {
        
        # a blessed free choice
        rlseq <- c(rlseq, sample(possible_tokens, size = 1L))
        explain_seq <- c(explain_seq,"F")
      }
      
    } else {
      # forced opposite
      rlseq <- c(rlseq, setdiff(possible_tokens,tail(rlseq,1L)))
      explain_seq <- c(explain_seq,"O")
    }
  }
  sumdiff <- (sum(rlseq==possible_tokens[[1]])-
                sum(rlseq==possible_tokens[[2]]))
  if (sumdiff!=0){
    warning(paste0("Wasnt able to balance\t:",sumdiff))
    }
  list(seq=rlseq,
       explain=explain_seq)
}

#is RRLRR inside 20 length sequences ? 
sapply(1:200,\(x){
  (res <- RL_v2(20,max_consec = 2))
  str_detect(paste0(res$seq,collapse = ""),
             pattern = fixed("RRLRR"))
}) |> table()
# FALSE  TRUE 
# 104    96 

# about half the time 

#are we beginning with RRLL? 
sapply(1:200,\(x){
  (res <- RL_v2(20,max_consec = 2))
  str_detect(paste0(res$seq,collapse = ""),
             pattern = "^RRLL")
}) |> table()
# about 10%

ОТРЕДАКТИРОВАНО: Мой метод работает конструктивно, проверяя хвост того, что было выбрано, и если у нас есть свобода, мы используем его, а если нет, мы принимаем принудительный выбор, он должен работать в линейном времени, что делает его масштабируемым. расширен, чтобы проверить, не является ли струна несбалансированной, и выполнить ее балансировку по ходу дела. возврат представляет собой список результатов и пояснение, показывающее, когда в соответствии с правилами был сделан свободный выбор, а не принудительный выбор.

RLx <- function(desired_length, max_consec = 2) {
  possible_tokens <- c("R", "L")
# later will use the following tokens when building an explain list
#  explain_tokens <- c("1","2","O","F")
                                      # 1 - we had to add 1/R to balance 
                                      # 2 - we had to add 2/L to balance
                                      # we had to choose opposite to previous to not make too long consecutive
                                      # we had a free choice 
  rlseq <- character(0)
  explain_seq <- character(0)
  while (length(rlseq) < desired_length) {
 
    current_tail <- tail(rlseq, max_consec)
    suppressWarnings(max_tail <- max(rle(current_tail)[1L]$lengths))
    if (max_tail < max_consec) {
      # we are free to choose either R or L ; 
      # must we choose one or the other to maintain 'parity'? 
      sumdiff <- (sum(rlseq==possible_tokens[[1]])-
                   sum(rlseq==possible_tokens[[2]]))
      if (sumdiff<0){
        # more 2 than 1 so must add 1 
        rlseq <- c(rlseq, possible_tokens[[1]])
        explain_seq <- c(explain_seq,"1")
      } else if (
        sumdiff>0
      ){
        # more 1 than 2 so must add 2
        rlseq <- c(rlseq, possible_tokens[[2]])
        explain_seq <- c(explain_seq,"2")
      } else {
        # a blessed free choice
      rlseq <- c(rlseq, sample(possible_tokens, size = 1L))
      explain_seq <- c(explain_seq,"F")
      }
    } else {
      # forced opposite
      rlseq <- c(rlseq, setdiff(possible_tokens,tail(rlseq,1L)))
      explain_seq <- c(explain_seq,"O")
    }
}
  list(seq=rlseq,
       explain=explain_seq)
}

(res <- RLx(300,max_consec = 2))

Кажется, это проверяет только последовательное ограничение, но я думаю, что оно игнорирует ограничение, заключающееся в том, что в конечной последовательности имеется равное количество L и R?

Gregor Thomas 13.03.2024 20:27

Спасибо за этот комментарий; Я отредактирую, чтобы включить это ограничение

Nir Graham 13.03.2024 20:48

Мне нравится этот подход, но мне кажется, что он слишком агрессивно меняет баланс. Например, последовательность никогда не может начинаться или заканчиваться RR или LL. Вы также не можете получить такие последовательности, как RRLRR, в последовательности длиной 20, хотя на это нет никаких ограничений. Я попытался изменить это с помощью (length(rlseq) > (desired_length*0.9) & sumdiff < 0) | sumdiff < -1 вместо sumdiff<0 и наоборот для sumdiff>0, то есть строгое ограничение применяется только в конце. Это позволяет использовать начало и конец RR и LL, а также более свободный выбор, но не решает проблему RRLRR.

Simon 18.03.2024 16:30

Надеюсь, это редактирование поможет

Nir Graham 18.03.2024 17:14

Идея

На самом деле вы можете искусственно создать «псевдо» процесс, используя rmultinom, скажем, x_1 + x_2 + ... + x_k = n для категорий L и R, но с ограничением на x_i, например, x_i <= l, где k определяет минимальное расстояние для двух последовательных L, разделенных R, например.

Как только набор {x_i} сохранится в результате ограниченной выборки, вы можете выполнить следующие два шага:

  1. сделайте {x_i} и {y_i} := sample({x_i}) для распределения последовательных L и R соответственно
  2. Чередуйте L и R вместе с соответствующими повторениями, чтобы построить последовательность, например, [rep('L',x_1), rep('R',y_1), rep('L',x_2), rep('R',y_2),..., rep('L',x_k), rep('R',y_k)]

Код

f <- function(Ntot, l = 2) {
   n <- Ntot / 2
   k <- ceiling(n / l) + 1
   repeat {
      d <- c(rmultinom(1, n, rep(1, k)))
      if (max(d) <= l && min(d) > 0) break
   }
   cnts <- c(rbind(d, sample(d)))
   rep(rep(sample(c("L", "R")), length.out = length(cnts)), cnts)
}

Пример вывода

Для воспроизводимости вы можете установить set.seed(0) перед выполнением следующих тестов.

> (out <- f(60, 3))
 [1] "L" "L" "L" "R" "R" "L" "L" "L" "R" "R" "R" "L" "L" "L" "R" "R" "R" "L" "L"
[20] "L" "R" "R" "R" "L" "L" "L" "R" "R" "R" "L" "L" "L" "R" "R" "R" "L" "L" "R"
[39] "R" "R" "L" "L" "R" "R" "L" "L" "L" "R" "R" "R" "L" "L" "R" "R" "L" "L" "L"
[58] "R" "R" "R"

> table(out)
out
 L  R
30 30

> max(rle(out)$lengths)
[1] 3

и

> (out2 <- f(82, 5))
 [1] "R" "R" "R" "L" "L" "L" "L" "L" "R" "R" "R" "R" "L" "L" "L" "L" "R" "R" "R"
[20] "L" "L" "L" "L" "R" "R" "R" "L" "L" "L" "L" "L" "R" "R" "R" "R" "R" "L" "L"
[39] "L" "R" "R" "R" "R" "R" "L" "L" "L" "R" "R" "R" "R" "R" "L" "L" "L" "L" "R"
[58] "R" "R" "R" "L" "L" "L" "L" "L" "R" "R" "R" "R" "R" "L" "L" "L" "L" "L" "R"
[77] "R" "R" "R" "L" "L" "L"

> table(out2)
out2
 L  R
41 41

> max(rle(out2)$lengths)
[1] 5

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

Полученные выборки не будут независимыми, но средние значения функций из них будут сходиться к среднему значению генеральной совокупности.

Или, если вас не слишком заботит распределение, просто запускайте его со случайной перестановкой, пока не получите одно значение, а затем начните все сначала. Эти последовательности будут независимыми, но могут не быть равномерно распределены по приемлемым перестановкам.

Вот код, который можно использовать для генерации 5 последовательностей:

RL_seq <- function(n_L = 10, n_R = 10, max_consec = 2, n = 1,
                   alpha = 0.1 ) {
  
  # alpha is a tuning parameter.  Smaller values make the chain
  # move faster, but it will also move to sequences you don't
  # want, so you might get slower output.
  
  score <- function(seq) {
    # Compute a penalty score for violating the rule
    # An acceptable solution has score 0
    lens <- rle(seq)$lengths
    lens <- lens - max_consec
    sum(lens[lens > 0])
  }
  X <- sample(c(rep("L", n_L), rep("R", n_R)))
  Xscore <- score(X)
  
  result <- matrix(NA, n, length(X))
  count <- 0
  while (count < n) {
    Y <- X
    # Randomly select a pair of entries that are different.
    repeat {
      i <- sample(length(X), 2)
      if (Y[i[1]] != Y[i[2]])
        break
    }
    # Swap them
    temp <- Y[i[1]]
    Y[i[1]] <- Y[i[2]]
    Y[i[2]] <- temp
    Yscore <- score(Y)

    # Always accept the proposal if it is at least as good, 
    # and sometimes if it is worse, using the Metropolis ratio
    
    if (Xscore >= Yscore || runif (1) < exp(alpha * (Xscore - Yscore))) {
      X <- Y
      Xscore <- Yscore
    } 
    if (Xscore == 0) {
      count <- count + 1
      result[count,] <- X
    }
  }
  result
}

set.seed(1)
RL_seq(10, 10, n = 5)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14]
#> [1,] "R"  "L"  "R"  "R"  "L"  "R"  "L"  "L"  "R"  "R"   "L"   "L"   "R"   "L"  
#> [2,] "R"  "R"  "L"  "R"  "L"  "R"  "L"  "R"  "L"  "R"   "L"   "L"   "R"   "R"  
#> [3,] "R"  "R"  "L"  "L"  "R"  "L"  "R"  "R"  "L"  "R"   "R"   "L"   "L"   "R"  
#> [4,] "R"  "R"  "L"  "L"  "R"  "L"  "R"  "R"  "L"  "R"   "R"   "L"   "L"   "R"  
#> [5,] "R"  "R"  "L"  "L"  "R"  "R"  "L"  "R"  "L"  "R"   "R"   "L"   "L"   "R"  
#>      [,15] [,16] [,17] [,18] [,19] [,20]
#> [1,] "R"   "R"   "L"   "L"   "R"   "L"  
#> [2,] "L"   "L"   "R"   "R"   "L"   "L"  
#> [3,] "L"   "R"   "L"   "L"   "R"   "L"  
#> [4,] "L"   "L"   "R"   "L"   "R"   "L"  
#> [5,] "L"   "L"   "R"   "L"   "R"   "L"

Created on 2024-03-13 with reprex v2.1.0

В данном случае все 5 последовательностей различны, но очень похожи.

Ниже представлена ​​функция rbinvec2, которая эффективно осуществляет равномерную выборку с заменой из набора допустимых векторов длины m. Он способен генерировать 100 тысяч действительных выборок вектора длиной 101 менее чем за 1 секунду.

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

Функция

library(RcppAlgos) # for `permuteGeneral`
library(Rfast) # for `colShuffle`

rbinvec2 <- function(n, m) {
  # handle small `m` values with `RcppAlgos`
  if (m == 1) {
    return(sample(0:1, n, 1))
  } else if (m == 2) {
    return(matrix(c(0:1, 1:0), 2, 2)[sample(1:2, n, 1),])
  } else if (m == 3) {
    return(permuteGeneral(0:1, 3, TRUE, NULL, 2, 7)[sample(6, n, 1),])
  }
  
  odd <- m%%2 == 1
  kk <- (m - sqrt(m^2%%8))/2 # maximum number of doubles
  k <- 0:kk
  mk <- m - k
  mk1 <- (mk + 1)%/%2
  mk <- mk%/%2
  m1 <- m - 1
  m2 <- m%/%2
  # calculate the exact (log) PMF of `k`, including the special cases when `m`
  # is odd
  p <- cbind(lchoose(mk1, k%/%2) + lchoose(mk, (k + 1)%/%2), -Inf)
  i <- seq(1, kk + 1, 2)
  if (odd) { # special cases
    p[-i, 1] <- p[-i, 1] + log(2)
    p[i, 2] <- lchoose(mk1[i], -1:(kk%/%2 - 1)) + lchoose(mk[i], 1:(kk%/%2 + 1))
  }
  # sample the PMF
  k <- array(c(rmultinom(1, n, exp(p - max(p[1,])))), dim(p))
  # k[-i,] <- rowSums(k[-i,])
  s <- rowSums(k)
  b <- vector("list", kk + 1) # initialize the list of compositions
  # special case: no doubles
  if (s[1]) b[[1]] <- rep(1, m*s[1])
  s[1] <- 0L
  
  if (odd) { # odd `m`
    for (i in which(s != 0L)) {
      b[[i]] <- if (i%%2L) {
        # even number of doubles
        j1 <- (m - i)/2 # 1 partition length
        j0 <- j1 + 1 # 0 partition length
        i2 <- (i - 1)/2
        d0 <- j0 - i2
        d1 <- j1 - i2
        c(
          if (k[i, 1]) { # balanced doubles--1 more 0 than 1
            rbind(
              # 0 composition
              c(colShuffle(matrix(rep.int(1:2, c(d0, i2)), j0, k[i, 1]))),
              # 1 composition
              c(rbind(
                colShuffle(matrix(rep.int(1:2, c(d1, i2)), j1, k[i, 1])),
                0L
              ))
            )
          } else integer(0)
          ,
          if (k[i, 2]) { # unbalanced doubles--1 more 1 than 0
            rbind( 
              # 0 composition
              c(colShuffle(matrix(rep.int(1:2, c(d0 + 1, i2 - 1)), j0, k[i, 2]))),
              # 1 composition
              c(rbind(
                colShuffle(matrix(rep.int(1:2, c(d1 - 1, i2 + 1)), j1, k[i, 2])),
                0L
              ))
            )
          } else integer(0)
        )
      } else {
        # odd number of doubles
        i2 <- i/2 - 1 # min number of (0,0)
        j <- m2 - i2 # even/odd composition lengths
        idx <- sample(1:2, s[i], 1) # extra 0, or extra 1?
        bb <- cbind(rep.int(1:2, c(j - i2, i2)), # extra 0
                    rep.int(1:2, c(j - i2 - 1, i2 + 1))) # extra 1
        c(
          rbind(
            c(colShuffle(matrix(bb[,idx], j))), # 0 compositions
            c(colShuffle(matrix(bb[,3 - idx], j))) # 1 compositions
          )
        )
      }
    }
  } else { # even `m`
    for (i in which(s != 0L)) {
      i2 <- (i - 1)%/%2 # number of (0,0)
      j <- m2 - i2 # 0 composition length
      b[[i]] <- c(
        if (i%%2L) {
          # even number of doubles--equivalent partition for 0 and 1
          matrix(
            colShuffle(matrix(rep.int(1:2, c(j - i2, i2)), j, 2*s[i])),
            2, j*s[i], 1
          )
        } else { # odd number of doubles--1 more double 1 than double 0
          rbind(
            # 0-composition
            c(colShuffle(matrix(rep.int(1:2, c(j - i2, i2)), j, s[i]))),
            # 1 composition
            c(rbind(
              colShuffle(
                matrix(rep.int(1:2, c(j - i2 - 2, i2 + 1)), j - 1, s[i])
              ), 0L
            ))
          )
        }
      )
    }
  }
  
  abs(
    matrix(
      # convert the compositions to 0's and 1's
      rep.int(rep(0:1, length.out = sum(lengths(b))), unlist(b)), n, m, 1
      # shuffle the n samples and perform a full 0 <-> 1 swap in random vectors
    )[sample(n),] - sample(0:1, n, 1)
  )
}

Пример использования

rbinvec2(10, 8) # 10 samples of vector of length 8
#>       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
#>  [1,]    0    1    0    1    0    1    0    1
#>  [2,]    0    1    0    1    0    0    1    1
#>  [3,]    0    1    0    0    1    0    1    1
#>  [4,]    0    0    1    1    0    1    1    0
#>  [5,]    0    0    1    0    1    0    1    1
#>  [6,]    1    0    1    0    1    1    0    0
#>  [7,]    1    0    1    0    1    0    0    1
#>  [8,]    1    0    1    0    1    0    1    0
#>  [9,]    1    0    1    0    1    0    1    0
#> [10,]    1    0    0    1    0    0    1    1
rbinvec2(10, 9)# 10 samples of vector of length 9
#>       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
#>  [1,]    0    0    1    1    0    0    1    0    1
#>  [2,]    0    1    1    0    1    1    0    0    1
#>  [3,]    1    0    0    1    0    1    1    0    0
#>  [4,]    1    0    0    1    0    1    0    1    0
#>  [5,]    1    1    0    0    1    1    0    0    1
#>  [6,]    1    1    0    1    0    0    1    0    0
#>  [7,]    0    1    0    1    0    0    1    0    1
#>  [8,]    1    0    1    0    0    1    0    0    1
#>  [9,]    1    1    0    0    1    0    1    1    0
#> [10,]    0    0    1    0    0    1    1    0    1

Тайминг

system.time(rbinvec2(1e6, 10)) # 1M samples of vector of length 10
#>    user  system elapsed 
#>    1.52    0.12    1.75
system.time(rbinvec2(1e5, 101)) # 100K samples of vector of length 101
#>    user  system elapsed 
#>    0.69    0.09    0.82

Проверьте правильность поведения

library(data.table) # for checks

fcheck1 <- function(m) {
  # returns the number of valid vectors of length `m`
  
  # generate all balanced permutations
  x <- if (m%%2) {
    rbind(
      permuteGeneral(0:1, m, TRUE, c((m + 1)%/%2, m%/%2)),
      permuteGeneral(0:1, m, TRUE, c(m%/%2, (m + 1)%/%2))
    )
  } else {
    permuteGeneral(0:1, m, TRUE, c(m%/%2, m%/%2))
  }
  # count the permutations with no more than two sequential repeated values
  sum(!rowSums(x[,-1:-2] == x[,c(-1, -m)] & x[,c(-1, -m)] == x[,-m:(1 - m)]))
}

fcheck2 <- function(n, m) {
  # return the unique samples along with their counts
  setorder(as.data.table(rbinvec2(n, m))[,.(.N), eval(paste0("V", 1:m))])[]
}

fcheck1(6) # the number of valid vectors of length 6
#> [1] 14
fcheck2(1e6, 6)
#>        V1    V2    V3    V4    V5    V6     N
#>     <int> <int> <int> <int> <int> <int> <int>
#>  1:     0     0     1     0     1     1 71195
#>  2:     0     0     1     1     0     1 71249
#>  3:     0     1     0     0     1     1 71237
#>  4:     0     1     0     1     0     1 71588
#>  5:     0     1     0     1     1     0 71469
#>  6:     0     1     1     0     0     1 71482
#>  7:     0     1     1     0     1     0 71242
#>  8:     1     0     0     1     0     1 71368
#>  9:     1     0     0     1     1     0 71638
#> 10:     1     0     1     0     0     1 71606
#> 11:     1     0     1     0     1     0 71645
#> 12:     1     0     1     1     0     0 71867
#> 13:     1     1     0     0     1     0 71107
#> 14:     1     1     0     1     0     0 71307
fcheck1(7)
#> [1] 36 # the number of valid vectors of length 7
fcheck2(1e6, 7)
#>        V1    V2    V3    V4    V5    V6    V7     N
#>     <int> <int> <int> <int> <int> <int> <int> <int>
#>  1:     0     0     1     0     0     1     1 27612
#>  2:     0     0     1     0     1     0     1 27728
#>  3:     0     0     1     0     1     1     0 27451
#>  4:     0     0     1     1     0     0     1 27707
#>  5:     0     0     1     1     0     1     0 27689
#>  6:     0     0     1     1     0     1     1 27460
#>  7:     0     1     0     0     1     0     1 27891
#>  8:     0     1     0     0     1     1     0 27562
#>  9:     0     1     0     1     0     0     1 27805
#> 10:     0     1     0     1     0     1     0 27947
#> 11:     0     1     0     1     0     1     1 27761
#> 12:     0     1     0     1     1     0     0 27893
#> 13:     0     1     0     1     1     0     1 28044
#> 14:     0     1     1     0     0     1     0 27899
#> 15:     0     1     1     0     0     1     1 27756
#> 16:     0     1     1     0     1     0     0 27477
#> 17:     0     1     1     0     1     0     1 27769
#> 18:     0     1     1     0     1     1     0 27756
#> 19:     1     0     0     1     0     0     1 27955
#> 20:     1     0     0     1     0     1     0 27726
#> 21:     1     0     0     1     0     1     1 27931
#> 22:     1     0     0     1     1     0     0 27896
#> 23:     1     0     0     1     1     0     1 27652
#> 24:     1     0     1     0     0     1     0 27720
#> 25:     1     0     1     0     0     1     1 28005
#> 26:     1     0     1     0     1     0     0 28072
#> 27:     1     0     1     0     1     0     1 27802
#> 28:     1     0     1     0     1     1     0 27579
#> 29:     1     0     1     1     0     0     1 27593
#> 30:     1     0     1     1     0     1     0 27846
#> 31:     1     1     0     0     1     0     0 27745
#> 32:     1     1     0     0     1     0     1 27760
#> 33:     1     1     0     0     1     1     0 27840
#> 34:     1     1     0     1     0     0     1 27704
#> 35:     1     1     0     1     0     1     0 27884
#> 36:     1     1     0     1     1     0     0 28083
#>        V1    V2    V3    V4    V5    V6    V7     N

Распределение каждого уникального вектора соответствует равномерной выборке полного набора допустимых векторов для обоих случаев.

Другие вопросы по теме