Фильтрация результатов Expand.Grid

Я пытаюсь создать список всех комбинаций чисел, удовлетворяющих всем следующим условиям:

  • Любая комбинация состоит ровно из 6 цифр.
  • Возможные числа только 1,5,7.
  • За 1 может следовать только 1 или 5
  • За 5 может следовать только 5 или 7
  • за 7 может следовать только 7
  • Должно быть как минимум две единицы

Я попытался сделать это с помощью функцииexpand.grid.

Шаг 1: Сначала я создал список всех 6 комбинаций длины с 1,5,7:

numbers <- c(1, 5, 7)
all_combinations <- data.frame(expand.grid(rep(list(numbers), 6)))

Шаг 2. Затем я попытался добавить переменные, чтобы удовлетворить условиям:

all_combinations$starts_with_1 <- ifelse(all_combinations$Var1 == 1, "yes", "no")
all_combinations$numbers_ascending  <- apply(all_combinations, 1, function(x) all(diff(as.numeric(x)) >= 0))


all_combinations$numbers_ascending  <- ifelse(all_combinations$numbers_ascending , "yes", "no")


all_combinations$at_least_two_ones <- apply(all_combinations, 1, function(x) sum(x == 1) >= 2)

all_combinations$at_least_two_ones <- ifelse(all_combinations$at_least_two_ones, "yes", "no")

Шаг 3. Наконец, я постарался сохранить строки, удовлетворяющие всем трем условиям:

all_combinations <- all_combinations[all_combinations$starts_with_1 == "yes" & all_combinations$numbers_ascending == "yes" & all_combinations$at_least_two_ones == "yes", ]

all_combinations

Однако все результаты NA:

      Var1 Var2 Var3 Var4 Var5 Var6 starts_with_1 numbers_ascending at_least_two_ones
NA      NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.1    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.2    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.3    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.4    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.5    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.6    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.7    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.8    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.9    NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.10   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.11   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.12   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.13   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.14   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.15   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.16   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.17   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.18   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.19   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>
NA.20   NA   NA   NA   NA   NA   NA          <NA>              <NA>              <NA>

Примечание. Я пытаюсь сделать это гибким способом, чтобы, если мне нужно что-то изменить (например, добавить как минимум три единицы или изменить так, чтобы 7 появлялось раньше 5), я мог быстро создать переменную для проверки этого условия. Вот почему я использую подходexpand.grid.

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

Ответы 6

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

Думаю, мы это будем корректировать, но как насчет regex подхода?
Проверь это:

library(tidyverse)

# ----------------
my_numbers <- c(1, 5, 7)
my_combinations <- data.frame(expand.grid(rep(list(my_numbers), 6)))

# Patterns
looking <- str_c(
  sep = "|",
  "1{2}")      # At least two "1"

not_looking <- str_c(
  sep = "|",
  "17",        # 1 can only be followed by either 1 or 5
  "51",        # 5 can only be followed by either 5 or 7
  "71", "75")  # 7 can only be followed by 7

# ----------------
my_output <- my_combinations %>% 
  rowwise() %>% 
  mutate(combo = str_flatten(c_across(starts_with("var")))) %>% 
  filter(str_detect(combo, looking), !str_detect(combo, not_looking))

Выход:

> my_output
# A tibble: 11 × 7
# Rowwise: 
    Var1  Var2  Var3  Var4  Var5  Var6 combo 
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> 
 1     1     1     1     1     1     1 111111
 2     1     1     1     1     1     5 111115
 3     1     1     1     1     5     5 111155
 4     1     1     1     5     5     5 111555
 5     1     1     5     5     5     5 115555
 6     1     1     1     1     5     7 111157
 7     1     1     1     5     5     7 111557
 8     1     1     5     5     5     7 115557
 9     1     1     1     5     7     7 111577
10     1     1     5     5     7     7 115577
11     1     1     5     7     7     7 115777

Created on 2024-05-01 with reprex v2.1.0

@ Адриано Мелло: большое спасибо за ответ! можно ли изменить условие «по возрастанию» и сделать так, чтобы общий порядок чисел был 1,7,5?

stats_noob 01.05.2024 06:39

@stats_noob, как будет выглядеть порядок чисел? Например, «171 >> 571 >> 777 >> 715 >> 515» будет правильным?

Adriano Mello 01.05.2024 12:44

Например. 117555, 111755 и т. д.

stats_noob 01.05.2024 14:15

@stats_noob: Это нарушает предположения, изложенные в вопросе; это недействительные результаты. Если вы отклоняетесь от вопроса, вы имеете в виду просто заменить все 5 на 7 и 7 на 5?

Ben Voigt 03.05.2024 00:15

Код ниже применяет условия, которые вы закодировали в своем вопросе, и дает не все результаты NA.

library(dplyr)

numbers <- c(1, 5, 7)
all_combinations <- data.frame(expand.grid(rep(list(numbers), 6)))

all_combinations %>%
  mutate(
    starts_with_1 = ifelse(Var1 == 1, "yes", "no"),
    numbers_ascending = apply(., 1, function(x) all(diff(as.numeric(x)) >= 0)),
    numbers_ascending = ifelse(numbers_ascending , "yes", "no"),
    at_least_two_ones = apply(., 1, function(x) sum(x == 1) >= 2),
    at_least_two_ones = ifelse(at_least_two_ones, "yes", "no")
  ) %>%
  filter(
    starts_with_1 == "yes",
    numbers_ascending == "yes",
    at_least_two_ones == "yes"
  )

Я не уверен, что определенные вами логические условия дают требуемые результаты, поскольку в выводе ниже вы можете видеть, что есть случай, когда за 1 следует 7. Возможно, есть и другие случаи, которые не соответствуют правилам. ты определил.

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

   Var1 Var2 Var3 Var4 Var5 Var6 starts_with_1 numbers_ascending at_least_two_ones
1     1    1    1    1    1    1           yes               yes               yes
2     1    1    1    1    1    5           yes               yes               yes
3     1    1    1    1    5    5           yes               yes               yes
4     1    1    1    5    5    5           yes               yes               yes
5     1    1    5    5    5    5           yes               yes               yes
6     1    1    1    1    1    7           yes               yes               yes
7     1    1    1    1    5    7           yes               yes               yes
8     1    1    1    5    5    7           yes               yes               yes
9     1    1    5    5    5    7           yes               yes               yes
10    1    1    1    1    7    7           yes               yes               yes
11    1    1    1    5    7    7           yes               yes               yes
12    1    1    5    5    7    7           yes               yes               yes
13    1    1    1    7    7    7           yes               yes               yes
14    1    1    5    7    7    7           yes               yes               yes
15    1    1    7    7    7    7           yes               yes               yes

Вы также можете упростить реализацию следующим образом:

all_combinations %>%
  mutate(
    numbers_ascending = apply(., 1, function(x) all(diff(as.numeric(x)) >= 0)),
    at_least_two_ones = apply(., 1, function(x) sum(x == 1) >= 2)
  ) %>%
  filter(
    Var1 == 1,
    numbers_ascending,
    at_least_two_ones
  )

@datawookie: большое спасибо за ответ! можно ли изменить условие «по возрастанию» и сделать так, чтобы общий порядок чисел был 1,7,5?

stats_noob 01.05.2024 06:39

Да точно. Но это похоже на отдельный вопрос, и я бы посоветовал вам создать еще один вопрос по SO. Вы можете сослаться на это для контекста.

datawookie 01.05.2024 10:58

ваша 6-я строка не соответствует требованию, поскольку за 1 должен следовать либо 1, либо 5, а не 7

ThomasIsCoding 01.05.2024 23:01

Мое решение реализует логику, указанную в вопросе. Вопрос, как я понял, был не в логике, а в том, почему исходная реализация генерировала кучу NA.

datawookie 02.05.2024 05:34

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

fn <- function(numbers, n_cols = 6, n_at_start) {
  v <- seq_along(numbers)
  
  # Generate grid and convert to matrix
  m <- as.matrix(expand.grid(rep(list(v), n_cols)))
  
  # Colwise differences
  dm <- m[, -1] - m[, -n_cols]
  
  # Filter
  m <- m[rowSums(m == 1) >= n_at_start  &   # Row starts with n 1s
         rowSums(dm == 0 | dm == 1) == n_cols - 1, ] # Number followed by itself or next value in sequence
  
  m[] <- numbers[m]
  m

}

fn(c(1,5,7), n_cols = 6, n_at_start = 2)

      Var1 Var2 Var3 Var4 Var5 Var6
 [1,]    1    1    1    1    1    1
 [2,]    1    1    1    1    1    5
 [3,]    1    1    1    1    5    5
 [4,]    1    1    1    5    5    5
 [5,]    1    1    5    5    5    5
 [6,]    1    1    1    1    5    7
 [7,]    1    1    1    5    5    7
 [8,]    1    1    5    5    5    7
 [9,]    1    1    1    5    7    7
[10,]    1    1    5    5    7    7
[11,]    1    1    5    7    7    7

1)expand.grid Индексы чисел должны быть неубывающими, и поскольку должно быть не менее n1 единиц, каждое возможное решение из n чисел должно начинаться с n1 единиц. Остается n-n1 индексов, поэтому сформируйте сетку g, а затем используйте apply, чтобы получить логический вектор, указывающий, какие строки g сохранить, и подмножество g по нему. Наконец преобразуйте индексы в значения x. Этот последний шаг позволяет располагать x в любом порядке.

filter_rows <- function(g, x) {
  ok  <- function(z) all(diff(z) %in% 0:1)
  out <- g[apply(g, 1, ok), ]
  replace(out, TRUE, lapply(out, \(i) x[i]))
}

f <- function(x = c(1,5,7), n=6, n1=2) {
  do.call(expand.grid, rep(list(1, seq_along(x)), c(n1, n-n1))) |>
    filter_rows(x)
}

# test runs
f() # as per question 
f(n1 = 3) # 3 ones
f(c(1, 7, 5)) # change order

2) gtools В gtools есть функция, combinations которая может вычислять комбинации с дубликатами. Функция filter_rows взята из (1).

library(gtools)

f2 <- function(x = c(1,5,7), n=6, n1=2) {
  data.frame(as.list(rep(1, n1)), 
    combinations(length(x), n-n1, repeats.allowed = TRUE)) |>
  filter_rows(x)
}

# test runs
f2() # as per question 
f2(n1 = 3) # 3 ones
f2(c(1, 7, 5)) # change order

3) RcppAlgos Это похоже на (2), за исключением того, что здесь используется combGeneral из RcppAlgos. Функция filter_rows взята из (1).

library(RcppAlgos)

f3 <- function(x = c(1,5,7), n=6, n1=2) {
  data.frame(as.list(rep(1, n1)), 
    comboGeneral(length(x), n-n1, repetition = TRUE)) |>
    filter_rows(x)
}

# test runs
f3() # as per question 
f3(n1 = 3) # 3 ones
f3(c(1, 7, 5)) # change o

Обновлять

  • Добавлено (2) и (3)
  • Зафиксированный. Добавили одно условие, которое изначально я пропустил.
  • вынесенный код, общий для (1), (2) и (3)

Поскольку требуются две единицы, а за единицей может следовать только 1 или 5, первые два столбца должны быть равны одной. Ниже я даю три варианта. Первый требует некоторой фильтрации; другие этого не делают.

В первом варианте используется RcppAlgos::comboGeneral. Результаты по умолчанию будут упорядочены. Единственное условие, которое не будет удовлетворено, — это то, что за 1 не следует 7, поэтому мы фильтруем это условие.

library(RcppAlgos)

v <- c(1, 5, 7)
x <- cbind(1, 1, comboGeneral(v, 4, TRUE))
x[rowSums(x[,-6] == v[1] & x[,-1] == v[3]) == 0,]
#>       [,1] [,2] [,3] [,4] [,5] [,6]
#>  [1,]    1    1    1    1    1    1
#>  [2,]    1    1    1    1    1    5
#>  [3,]    1    1    1    1    5    5
#>  [4,]    1    1    1    1    5    7
#>  [5,]    1    1    1    5    5    5
#>  [6,]    1    1    1    5    5    7
#>  [7,]    1    1    1    5    7    7
#>  [8,]    1    1    5    5    5    5
#>  [9,]    1    1    5    5    5    7
#> [10,]    1    1    5    5    7    7
#> [11,]    1    1    5    7    7    7

Второй вариант меняет расположение перехода 1 -> 5 или 5 -> 7 с помощью аргумента freqs функции RcppAlgos::permuteGeneral. Переходов может быть до двух, а непереходов (исключая первые два столбца) — до четырех.

library(matrixStats)

cbind(1, 1, matrix(v[rowCumsums(permuteGeneral(0:1, 4, TRUE, c(4, 2))) + 1L], ncol = 4))
#>       [,1] [,2] [,3] [,4] [,5] [,6]
#>  [1,]    1    1    1    1    1    1
#>  [2,]    1    1    1    1    1    5
#>  [3,]    1    1    1    1    5    5
#>  [4,]    1    1    1    1    5    7
#>  [5,]    1    1    1    5    5    5
#>  [6,]    1    1    1    5    5    7
#>  [7,]    1    1    1    5    7    7
#>  [8,]    1    1    5    5    5    5
#>  [9,]    1    1    5    5    5    7
#> [10,]    1    1    5    5    7    7
#> [11,]    1    1    5    7    7    7

В третьем варианте используется compositionsGeneral и наблюдение о том, что количество повторений каждого элемента в последних 5 столбцах представляет собой 3-композицию из 5 (с допустимыми нулями):

x <- t(compositionsGeneral(0:5, 3, repetition = TRUE)[,3:1])
cbind(1, matrix(rep(rep(v, ncol(x)), x), ncol(x), 5, 1))
#>       [,1] [,2] [,3] [,4] [,5] [,6]
#>  [1,]    1    1    1    1    1    1
#>  [2,]    1    1    1    1    1    5
#>  [3,]    1    1    1    1    5    5
#>  [4,]    1    1    1    5    5    5
#>  [5,]    1    1    5    5    5    5
#>  [6,]    1    1    1    1    5    7
#>  [7,]    1    1    1    5    5    7
#>  [8,]    1    1    5    5    5    7
#>  [9,]    1    1    1    5    7    7
#> [10,]    1    1    5    5    7    7
#> [11,]    1    1    5    7    7    7

Идея

Поскольку у вас есть требование иметь как минимум две 1, а 1 — это самая маленькая цифра, с которой вы хотите начать. В случае, если у вас всегда есть как минимум два 1 в начале вашей последовательности, например, 11xxxx.

Для следующих четырех заполнителей x вы можете последовательно заполнить его допустимым числом (в зависимости от его прецедентного значения, например, за 1 следует 1 или 5, а за 5 следует 5 или 7 и т. д.) и итеративно обновлять последовательность, пока все четыре x не будут заполнены.


Код (с рекурсией)

Я бы сказал, что expand.grid здесь не лучший выбор, поскольку включает слишком много ненужных комбинаций. Вы можете создать рекурсивную функцию для получения желаемых комбинаций, например:

f <- function(n, v = c(1, 5, 7)) {
    if (n == 2) {
        return(list(c(1, 1)))
    }
    unlist(
        lapply(
            Recall(n - 1),
            \(x)
            Map(
                c,
                list(x),
                unlist(list(v[-3], v[-1], v[3])[match(tail(x, 1), v)])
            )
        ), FALSE
    )
}

который дает

> f(6)
[[1]]
[1] 1 1 1 1 1 1

[[2]]
[1] 1 1 1 1 1 5

[[3]]
[1] 1 1 1 1 5 5

[[4]]
[1] 1 1 1 1 5 7

[[5]]
[1] 1 1 1 5 5 5

[[6]]
[1] 1 1 1 5 5 7

[[7]]
[1] 1 1 1 5 7 7

[[8]]
[1] 1 1 5 5 5 5

[[9]]
[1] 1 1 5 5 5 7

[[10]]
[1] 1 1 5 5 7 7

[[11]]
[1] 1 1 5 7 7 7

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

Преобразование макета ограничений XML в Jetpack Compose — стиль цепочки, смещение
Почему эта ограничительная функция намного медленнее, чем аналогичная, и как мне увеличить скорость в Scipy Optimize?
Как принудительно сгруппировать уроки для одной и той же серии учеников в одном временном интервале и в одном и том же таймфолде/оптапланнере комнаты?
Целые числа в случайном порядке с ограничениями в R?
Размещение ввода-вывода — невозможная ошибка в Vivado
Timefold Solver по умолчанию применяет ограничения на основе приоритета/порядка ограничений (из ConstraintProvider)?
Как получить последовательные пары уроков (в зависимости от их временного интервала) в Timefold?
Почему концептуальное ограничение C++20 не работает должным образом?
OptaPlanner — используйте groupBy для избежаниеOvertime, но только на ресурсе, создающем сверхурочную работу
Как динамически выбирать, какие ограничения следует применять к задаче оптимизации на основе входных данных внешнего интерфейса в Timefold Spring Boot?