Выберите строки матрицы, которые являются перестановками данного вектора

У меня есть матрица X:

     one two three four
 [1,]  1   3   2   4
 [2,]  2   0   1   5
 [3,]  3   2   1   4
 [4,]  4   9   11  19
 [5,]  4   3   2   1

Я хочу получить новую матрицу Y, которая содержит только строки, являющиеся перестановками «1», «2», «3», «4». То есть:

     one two three four
 [1,]  1   3   2   4
 [3,]  3   2   1   4
 [5,]  4   3   2   1

Какую функцию или команду следует использовать?

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
0
535
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ваша примерная матрица и целевой вектор:

X <- structure(c(1, 2, 3, 4, 4, 3, 0, 2, 9, 3, 2, 1, 1, 11, 2, 4, 5, 4, 19, 1),
               dim = 5:4)
v <- 1:4

Но давайте создадим более сложный вариант (спасибо пользователю Харре):

X <- rbind(X, 1, c(1, 2, 1, 2))

Полностью векторизованный подход (с использованием пакета matrixStats)

rk <- matrix(match(X, v, nomatch = 0L), nrow(X), ncol(X))
ct <- matrixStats::rowTabulates(rk, values = 1:length(v))
zo <- matrixStats::rowCounts(ct, value = 0L)

## all rows that are permutations of 'v'
X[zo == 0L, ]
#     [,1] [,2] [,3] [,4]
#[1,]    1    3    2    4
#[2,]    3    2    1    4
#[3,]    4    3    2    1

## remove rows that are permutations of 'v'
X[zo > 0L, ]

Другой полностью векторизованный метод (база R)

Это математическое решение. Для нелинейной и асимметричной весовой функции ш(х) следующая взвешенная сумма:

1 х ш(1) + 2 х ш(2) + 3 х ш(3) + 4 х ш(4)

является уникальной оценкой или идентификатором и не зависит от перестановок. Так, например, следующее дает одно и то же значение:

2 х ш(2) + 1 х ш(1) + 3 х ш(3) + 4 х ш(4)

Но все остальное даст разные значения, например:

1 х ш(1) + 3 х ш(1) + 3 х ш(3) + 4 х ш(4)

0 х ш(0) + 3 х ш(1) + 0 х ш(0) + 4 х ш(4)

Вот реализация с использованием косинусных весов. Это работает, даже если X и v являются числами или символами с плавающей запятой.

## method = "tab" for tabulation method
## method = "cos" for cosine weights method
FindPerm <- function (X, v, method) {
  ## number of elements
  n <- length(v)
  if (ncol(X) != n) stop("inconformable 'X' and 'v'!")
  if (anyDuplicated(v)) stop("'v' should not contain duplicated values!")
  ## convert everything to integers 0, 1, 2, ..., n
  Xi <- matrix(match(X, v, nomatch = 0L), nrow(X), ncol(X))
  vi <- 1:n
  ## branches
  if (method == "tab") {
    ## row-wise tabulating
    rtab <- matrixStats::rowTabulates(Xi, values = vi)
    ## the i-th value is TRUE if X[i, ] is a permutation of v
    matrixStats::rowCounts(rtab, value = 0L) == 0L
  } else if (method == "cos") {
    ## evaluate cosine weights for Xi and vi
    w <- pi / (n + 1)
    cos.Xi <- cos(w * Xi)
    cos.vi <- cos(w * vi)
    ## weighted sum for Xi
    wtsum.Xi <- rowSums(Xi * cos.Xi)
    ## weighted sum for vi
    wtsum.vi <- sum(vi * cos.vi)
    ## the i-th value is TRUE if X[i, ] is a permutation of v
    wtsum.Xi == wtsum.vi
  } else {
    stop("unknown method!")
  }
}
X[FindPerm(X, v, "cos"), ]
#     [,1] [,2] [,3] [,4]
#[1,]    1    3    2    4
#[2,]    3    2    1    4
#[3,]    4    3    2    1

Ориентир

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

## a benchmark function, relying on package "microbenchmark"
## nr: number of matrix rows
## nc: number of elements in 'v'
bm <- function (nr, nc) {
  X <- matrix(sample.int(nc + 1L, nr * nc, replace = TRUE), nr)
  v <- 1:nc
  microbenchmark::microbenchmark("tab" = FindPerm(X, v, "tab"),
                                 "cos" = FindPerm(X, v, "cos"),
                                 check = "identical")
}

bm(2e+4, 4)
#Unit: milliseconds
# expr      min       lq     mean   median       uq      max
#  tab 4.302674 4.324236 4.536260 4.336955 4.359814 7.039699 
#  cos 4.846893 4.872361 5.163209 4.882942 4.901288 7.837580

bm(2e+4, 20)
#Unit: milliseconds
# expr      min       lq     mean   median       uq       max
#  tab 30.63438 30.70217 32.73508 30.77588 33.08046 135.64322
#  cos 21.16669 21.26161 22.28298 21.37563 23.60574  26.31775 

Это отличный ответ. Я пытался понять математику, но это слишком сложно для меня. Тем не менее, нет ли опечатки в вашей формуле "взвешенной суммы"? Должно ли 3 x w(1) быть либо 3 x w(3), либо 1 x w(1)? Спасибо!

WhatIf 01.08.2022 06:24

@WhatIf Хороший улов! Исправим в следующей версии вместе с другим обновлением.

Zheyuan Li 01.08.2022 19:37

Поскольку cos(pi/2) оценивается как 0, это не работает для v с нечетной длиной. Я предлагаю разделить w на 2.

jblood94 04.08.2022 18:48

@jblood94 Думаю, тогда sin() подойдет. Он никогда не равен 0 в диапазоне.

Zheyuan Li 04.08.2022 18:52
mat <- rbind(
    c(1, 3, 2, 4),
    c(2, 0, 1, 5),
    c(3, 2, 1, 4)
)

ok <- apply(mat, 1L, function(x) setequal(x, c(1, 2, 3, 4)))
mat[ok, ]

Другой вариант — использовать функцию Filter.

t(Filter(\(x) all((x %in% 1:4) & length(unique(x)) == 4) ,
data.frame(t(X))))

Просто для удовольствия

Кто может дать мне 4 различных цифры, сумма которых равна 6?

  • есть только {0,1,2,3}

тогда мы можем использовать модуль 4, используя %%

X[apply(X , 1 , \(x) sum(unique(x %% 4)) == 6 & length(unique(x)) == 4) , ]

ИЛИ ЖЕ

с использованием чистого for loop

ans <- data.frame(matrix(NA , ncol = ncol(X)))
r <- 1
for(i in 1:nrow(X)){
    if (all((X[i,] %in% 1:4) & length(unique(X[i,])) == 4)){
        ans[r,] <- X[i,]
        r <- r + 1
    }
}

ans <- as.matrix(ans)
  • выход
  X1 X2 X3 X4
1  1  3  2  4
2  3  2  1  4
3  4  3  2  1

  • данные
X <- matrix(c(1, 2, 3, 4, 4, 3, 0, 2, 9, 3, 2, 1, 1, 11, 2, 4, 
              5, 4, 19, 1) , ncol = 4)

Для удовольствия от tidyverse-решений, даже если я думаю, что мы предпочли бы работать с матрицами напрямую. Однако мы могли бы использовать rowwise() и c_across():

С операциями над множествами (вдохновленными @Stéphane Laurent):

library(dplyr)

mat %>% 
  as_tibble() |>
  rowwise() |>
    filter(setequal(c_across(), c(1, 2, 3, 4))) |>
  ungroup() |>
  as.matrix()

Или без операций множества:

library(dplyr)

mat %>% 
  as_tibble() |>
  rowwise() |>
  filter(1 %in% c_across(everything()) &
         2 %in% c_across(everything()) &
         3 %in% c_across(everything()) &
         4 %in% c_across(everything())
         ) |>
  ungroup() |>
  as.matrix()

Или вдохновленный @Mohamed Desouky:

mat %>% 
    as_tibble() |>
    rowwise() |>
      filter(all(c_across() %in% 1:4) & n_distinct(c_across()) == 4) |>
    ungroup() |>
    as.matrix()

И так далее..

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

Идея состоит в том, чтобы индексировать length(v)-мерный массив для маленьких v или индексировать v*sin(w*v), используя результаты match вместо вычисления X*sin(W*X), когда v велико:

library(RcppAlgos)

# simplified version of Zheyuan Li's function
f1 <- function(X, v) {
  n <- length(v)
  Xi <- matrix(match(X, v, nomatch = 0L), nrow(X), ncol(X))
  vi <- 1:n
  w <- pi/(n + 1)
  rowSums(Xi*sin(Xi*w)) == sum(vi*sin(vi*w))
}

f2 <- function(X, v) {
  n <- length(v)
  
  if (n < 6) {
    # index an n-dimensional array
    m <- array(FALSE, rep(n + 1L, n))
    m[permuteGeneral(n)] <- TRUE
    X[] <- match(X, v, nomatch = length(v) + 1L)
    m[X]
  } else {
    nn <- 1:n
    u <- c(nn*sin(pi*nn/(n + 1L)), 0)
    X[] <- u[match(X, v, nomatch = n + 1L)]
    rowSums(X) == sum(u)
  }
}

set.seed(123)
# using Zheyuan Li's test dataset
nr <- 2000; nc <- 4
X <- matrix(sample.int(nc + 1L, nr * nc, replace = TRUE), nr)
v <- 1:nc

microbenchmark::microbenchmark(f1 = f1(X, v),
                               f2 = f2(X, v),
                               check = "identical")
#> Unit: microseconds
#>  expr   min     lq    mean median     uq    max neval
#>    f1 344.4 367.25 438.932 374.05 386.75 5960.6   100
#>    f2  81.9  85.00 163.332  88.90  98.50 6924.4   100

# Zheyuan Li's larger test dataset
set.seed(123)
nr <- 2000; nc <- 20
X <- matrix(sample.int(nc + 1L, nr * nc, replace = TRUE), nr)
v <- 1:nc

microbenchmark::microbenchmark(f1 = f1(X, v),
                               f2 = f2(X, v),
                               check = "identical")
#> Unit: microseconds
#>  expr    min      lq     mean  median     uq    max neval
#>    f1 1569.2 1575.45 1653.510 1601.30 1683.6 3972.6   100
#>    f2  355.2  359.90  431.705  366.85  408.6 2253.8   100

Оригинальный ответ отредактирован для использования X + exp(1/X) (см. комментарии).

Это должно работать с положительными целыми числами:

Y <- X[rowSums(X + exp(1/X)) == sum(1:4 + exp(1/(1:4))),]

Сравнение с решением apply:

f1 <- function(x) x[apply(x, 1L, function(x) setequal(x, 1:4)),]
f2 <- function(x) x[rowSums(x + exp(1/x)) == sum(1:4 + exp(1/(1:4))),]

X <- matrix(sample(10, 4e5, TRUE), 1e5)
microbenchmark::microbenchmark(f1 = f1(X),
                               f2 = f2(X),
                               times = 10,
                               check = "equal")
#> Unit: milliseconds
#>  expr      min       lq      mean    median       uq      max neval
#>    f1 448.2680 450.8778 468.55179 461.62620 472.0022 542.0455    10
#>    f2  28.5362  28.6889  31.50941  29.44845  30.2693  50.4402    10

Уточнение: x + sqrt(1/x) будет работать для перестановок 1:4. Он будет работать для X до 8 столбцов и поиска перестановок 1:8. x + exp(1/x) будет работать для еще большего количества столбцов (я проверил до 13), если перестановки имеют значение 1:ncol(X).

jblood94 31.07.2022 04:14

Если перестановки не из 1:ncol(X) -- скажем, какой-нибудь другой вектор v, просто используйте match(X, v).

jblood94 31.07.2022 04:22

Хороший трюк. Теперь я думаю, что это лучшее, что мы можем получить от кодирования р.

Zheyuan Li 04.08.2022 18:10

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

jblood94 05.08.2022 20:38
Ответ принят как подходящий

Мы можем попробовать это

> mat[colSums(mapply(`%in%`, list(1:4), asplit(mat, 1))) == ncol(mat), ]
     [,1] [,2] [,3] [,4]
[1,]    1    3    2    4
[2,]    3    2    1    4
[3,]    4    3    2    1

Этот вопрос ГОРЯЧИЙ. Я учусь, поэтому я воспринимаю это как хорошую возможность учиться. Мне действительно сложно придумывать новые решения, но я обнаружил, что здесь не хватает двух вещей:

  1. нет серьезной проверки этих ответов;
  2. нет эталона для всех из них.

Я хотел бы преобразовать каждый ответ в функцию, которая возвращает вектор TRUE/FALSE для пометки строк. Я также хочу, чтобы эта функция работала с любой матрицей и любым вектором.

  • Ответ Стефана Лорана, Ответ Чжэюань Ли и Ответ ThomasIsCoding требуют минимальной адаптации.

  • Ответ Мохамеда Десуки также легко адаптировать, вынеся функцию, примененную в Filter(), и apply() по строкам матрицы.

  • ответ jblood94 является сложной задачей. Было отмечено, что для других матриц и векторов необходимо преобразование с использованием match. Я не знаю, как правильно, но я увидел match в ответе Чжэюань Ли, поэтому позаимствовал эту часть.

  • Ответ ТарДжея ужасен (извините, не воспринимайте это как оскорбление). Кажется, ни один из них не работает. Я не вижу никакого сравнения между строками матрицы и векторами в базовом решении R. Для других кодов tidyverse я не знаю, что такое df_matrix. Я попросил TarJae пересмотреть ответ.

  • ответ Харре использует tidyverse и не возвращает TRUE/FALSE. Поэтому я должен исключить его из теста (извините).

Вот функции для теста.

S.Laurent <- function (X, v) apply(X, 1L, function(x) setequal(x, v))

Z.Li <- function (X, v, method) {
  ## number of elements
  n <- length(v)
  if (ncol(X) != n) stop("inconformable 'X' and 'v'!")
  if (anyDuplicated(v)) stop("'v' should not contain duplicated values!")
  ## convert everything to integers 0, 1, 2, ..., n
  Xi <- matrix(match(X, v, nomatch = 0L), nrow(X), ncol(X))
  vi <- 1:n
  ## branches
  if (method == "tab") {
    ## row-wise tabulating
    rtab <- matrixStats::rowTabulates(Xi, values = vi)
    ## the i-th value is TRUE if X[i, ] is a permutation of v
    matrixStats::rowCounts(rtab, value = 0L) == 0L
  } else if (method == "cos") {
    ## evaluate cosine weights for Xi and vi
    w <- pi / (n + 1)
    cos.Xi <- cos(w * Xi)
    cos.vi <- cos(w * vi)
    ## weighted sum for Xi
    wtsum.Xi <- rowSums(Xi * cos.Xi)
    ## weighted sum for vi
    wtsum.vi <- sum(vi * cos.vi)
    ## the i-th value is TRUE if X[i, ] is a permutation of v
    wtsum.Xi == wtsum.vi
  } else {
    stop("unknown method!")
  }
}

Thomas <- function (X, v) colSums(mapply(`%in%`, list(v), asplit(X, 1))) == ncol(X)

M.Desouky <- function (X, v) apply(X, 1, function (x) all((x %in% v) & length(unique(x)) == length(v)))

jblood94 <- function (X, v) {
  Xi <- matrix(match(X, v, nomatch = 0L), nrow(X), ncol(X))
  vi <- 1:length(v)
  rowSums(Xi + exp(1/Xi)) == sum(vi + exp(1/vi))
}

Для эталона я следил за настройкой в ​​​​ответе Чжэюань Ли.

library(matrixStats)
library(microbenchmark); library(ggplot2)

nr <- 2000; nc <- 4
X <- matrix(sample.int(nc + 1L, nr * nc, replace = TRUE), nr)
v <- 1:nc
microbenchmark("S.Laurent" = S.Laurent(X, v),
               "Z.Li.tab" = Z.Li(X, v, "tab"),
               "Z.Li.cos" = Z.Li(X, v, "cos"),
               "Thomas" = Thomas(X, v),
               "M.Desouky" = M.Desouky(X, v),
               "jblood94" = jblood94(X, v),
               check = "identical") -> bm1
autoplot(bm1)

"bm1.png"

nr <- 2000; nc <- 20
X <- matrix(sample.int(nc + 1L, nr * nc, replace = TRUE), nr)
v <- 1:nc
microbenchmark("S.Laurent" = S.Laurent(X, v),
               "Z.Li.tab" = Z.Li(X, v, "tab"),
               "Z.Li.cos" = Z.Li(X, v, "cos"),
               "Thomas" = Thomas(X, v),
               "M.Desouky" = M.Desouky(X, v),
               "jblood94" = jblood94(X, v),
               check = "identical") -> bm2
autoplot(bm2)

"bm2.png"

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

Вывод: Метод "cos" Чжэюань Ли является победителем.

Спасибо за комплексный тест! См. мой обновленный ответ для улучшения улучшения Чжэюань Ли по сравнению с моим первоначальным ответом.

jblood94 04.08.2022 17:46

Библиотека algorithm в C++ предлагает функцию под названием std::is_permutation, которая делает свое дело.

Функция рабочей лошадки ниже использует Rcpp и довольно проста.

#include <Rcpp.h>

// [[Rcpp::export]]
SEXP perm_idx_cpp(Rcpp::IntegerMatrix mat, const std::vector<int> &v) {
  
    const int nRows = mat.nrow();
    const int nCols = mat.ncol();
    
    std::vector<int> test(nCols);
    Rcpp::LogicalVector res(nRows);
    
    for (int i = 0; i < nRows; ++i) {
        for (int j = 0; j < nCols; ++j) {
            test[j] = mat(i, j);
        }
        
        res[i] = std::is_permutation(
            test.begin(), test.end(), v.begin()
        );
    }
    
    return res;
}

И, назвав это R, мы имеем (примечание. Мы используем match для получения целочисленных индексов, как в ответе @Zheyuan Li, что, кстати, абсолютно блестяще!):

get_perm_idx <- function(X, v) {
    Xi <- matrix(match(X, v, nomatch = 0L), ncol = ncol(X))
    perm_idx_cpp(Xi, seq_along(v))
}

Это также очень эффективно. Вот простой бенчмарк:

nr <- 2e4
nc <- 20
X <- matrix(sample.int(nc + 1L, nr * nc, replace = TRUE), nr)
v <- 1:nc
microbenchmark("tab" = FindPerm(X, v, "tab"),
               "cos" = FindPerm(X, v, "cos"),
               "is_perm_cpp" = get_perm_idx(X, v),
               check = "identical")
Unit: milliseconds
        expr       min        lq     mean    median        uq       max neval
         tab 33.641345 36.479660 39.00994 37.402306 39.560015  54.88057   100
         cos  9.496309 12.887493 15.30122 13.306302 14.053643 132.24079   100
 is_perm_cpp  3.232093  4.819553  6.08687  4.993367  5.248818  19.56919   100

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

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