【问题标题】:Create n by n matrix with unique values from 1:n使用 1:n 的唯一值创建 n × n 矩阵
【发布时间】:2018-03-09 22:10:49
【问题描述】:

我想在 R 中生成一个随机的 n × n 矩阵,其离散值范围为 1 到 n。棘手的部分是我希望每个值在行和列中都是唯一的。

例如,如果n=3 矩阵可能如下所示:

1 2 3 
2 3 1 
3 1 2 

或者它可能看起来像这样:

2 3 1 
1 2 3 
3 1 2 

有人知道如何生成这种矩阵吗?

【问题讨论】:

  • 我以为我有答案,但我错了。这些是排列,您可以使用 permute::allPerms(3) 获得 n = 3 的所有排列。然后,您想要的是满足列约束的可能排列的子集。我想。
  • 你不认为这是一个好问题吗?
  • 嗯?不,这是个好问题?

标签: r matrix


【解决方案1】:

您想要的称为Latin square。这是一个允许生成它们的函数(来自Cookbook for R;另请参见here 和一堆其他在线搜索结果):

latinsquare <- function(len, reps=1, seed=NA, returnstrings=FALSE) {

    # Save the old random seed and use the new one, if present
    if (!is.na(seed)) {
        if (exists(".Random.seed"))  { saved.seed <- .Random.seed }
        else                         { saved.seed <- NA }
        set.seed(seed)
    }

    # This matrix will contain all the individual squares
    allsq <- matrix(nrow=reps*len, ncol=len)

    # Store a string id of each square if requested
    if (returnstrings) {  squareid <- vector(mode = "character", length = reps) }

    # Get a random element from a vector (the built-in sample function annoyingly
    #   has different behavior if there's only one element in x)
    sample1 <- function(x) {
        if (length(x)==1) { return(x) }
        else              { return(sample(x,1)) }
    }

    # Generate each of n individual squares
    for (n in 1:reps) {

        # Generate an empty square
        sq <- matrix(nrow=len, ncol=len) 

        # If we fill the square sequentially from top left, some latin squares
        # are more probable than others.  So we have to do it random order,
        # all over the square.
        # The rough procedure is:
        # - randomly select a cell that is currently NA (call it the target cell)
        # - find all the NA cells sharing the same row or column as the target
        # - fill the target cell
        # - fill the other cells sharing the row/col
        # - If it ever is impossible to fill a cell because all the numbers
        #    are already used, then quit and start over with a new square.
        # In short, it picks a random empty cell, fills it, then fills in the 
        # other empty cells in the "cross" in random order. If we went totally randomly
        # (without the cross), the failure rate is much higher.
        while (any(is.na(sq))) {

            # Pick a random cell which is currently NA
            k <- sample1(which(is.na(sq)))

            i <- (k-1) %% len +1       # Get the row num
            j <- floor((k-1) / len) +1 # Get the col num

            # Find the other NA cells in the "cross" centered at i,j
            sqrow <- sq[i,]
            sqcol <- sq[,j]

            # A matrix of coordinates of all the NA cells in the cross
            openCell <-rbind( cbind(which(is.na(sqcol)), j),
                              cbind(i, which(is.na(sqrow))))
            # Randomize fill order
            openCell <- openCell[sample(nrow(openCell)),]

            # Put center cell at top of list, so that it gets filled first
            openCell <- rbind(c(i,j), openCell)
            # There will now be three entries for the center cell, so remove duplicated entries
            # Need to make sure it's a matrix -- otherwise, if there's just 
            # one row, it turns into a vector, which causes problems
            openCell <- matrix(openCell[!duplicated(openCell),], ncol=2)

            # Fill in the center of the cross, then the other open spaces in the cross
            for (c in 1:nrow(openCell)) {
                # The current cell to fill
                ci <- openCell[c,1]
                cj <- openCell[c,2]
                # Get the numbers that are unused in the "cross" centered on i,j
                freeNum <- which(!(1:len %in% c(sq[ci,], sq[,cj])))

                # Fill in this location on the square
                if (length(freeNum)>0) { sq[ci,cj] <- sample1(freeNum) }
                else  {
                    # Failed attempt - no available numbers
                    # Re-generate empty square
                    sq <- matrix(nrow=len, ncol=len)

                    # Break out of loop
                    break;
                }
            }
        }

        # Store the individual square into the matrix containing all squares
        allsqrows <- ((n-1)*len) + 1:len
        allsq[allsqrows,] <- sq

        # Store a string representation of the square if requested. Each unique
        # square has a unique string.
        if (returnstrings) { squareid[n] <- paste(sq, collapse="") }

    }

    # Restore the old random seed, if present
    if (!is.na(seed) && !is.na(saved.seed)) { .Random.seed <- saved.seed }

    if (returnstrings) { return(squareid) }
    else               { return(allsq) }
}

【讨论】:

    【解决方案2】:

    mats 是此类矩阵的列表。它使用r2dtable 生成N 随机n x n 矩阵,其元素选自0、1、...、n-1,其边距均由margin 给出。然后它过滤掉那些所有列都有一个 0:(n-1) 的列,并将每个矩阵加一以给出结果。返回的矩阵数量可能会有所不同,您必须生成大量矩阵N,以便在 n 变大时只获得一些矩阵。当我在mats 下方尝试 n

    set.seed(123)
    N <- 100 # no of tries
    n <- 3 # rows of matrix (= # cols)
    
    check <- function(x) all(apply(x, 2, sort) == seq_len(nrow(x))-1)
    margin <- sum(seq_len(n))-n
    margins <- rep(margin, n)
    L <- r2dtable(N, r = margins, c = margins)
    mats <- lapply(Filter(check, L), "+", 1)
    

    【讨论】:

      【解决方案3】:

      这是一个尝试:

      x <- c(1,2,3)
      out <- NULL
      for(i in 1:3){
        y <- c(x[1 + (i+0) %% 3], x[1 + (i+1) %% 3], x[1 + (i+2) %% 3])
        out <- rbind(out,y)
      }
      

      这给出了:

      > out
        [,1] [,2] [,3]
      y    2    3    1
      y    3    1    2
      y    1    2    3
      

      对于一般情况:

      n <- 4
      x <- 1:n
      out <- NULL
      for(i in 1:n){
        y <- x[1 + ((i+0:(n-1))%%n)]
        out <- rbind(out,y)
      }
      

      如果我没记错的话,这是预期的结果:

      > out
        [,1] [,2] [,3] [,4]
      y    2    3    4    1
      y    3    4    1    2
      y    4    1    2    3
      y    1    2    3    4
      

      更短:

      n < 4 
      x <- 1:n
      vapply(x, function(i) x[1 + ((i+0:(n-1))%%n)], numeric(n))
      

      【讨论】:

        【解决方案4】:

        这是一个版本,它为此类矩阵生成所有可能的行,然后一一获取,每次都将选择限制为有效选择:

        n <- 9
        allrows <- combinat::permn(n)
        
        takerows <- function(taken, all) {
          available <- rep(TRUE, length(all))
          for(i in 1:nrow(taken)) {
            available <- sapply(all, function(x) all((x-taken[i,])!=0)) & available
          }
          matrix(all[[which(available)[sample(sum(available), 1)]]], nrow=1)
        }
        
        magicMat <- takerows(matrix(rep(0, n), ncol=n), allrows)
        for(i in 1:(n-1)) {
          magicMat <- rbind(magicMat, takerows(magicMat, allrows))
        }
        
        
        > magicMat
              [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
         [1,]    5    3    1    4    2    8    6    7    9
         [2,]    9    8    6    2    1    3    7    4    5
         [3,]    4    5    7    8    9    2    3    6    1
         [4,]    3    9    2    1    6    7    5    8    4
         [5,]    1    6    5    3    8    4    2    9    7
         [6,]    7    2    4    9    3    5    8    1    6
         [7,]    6    4    8    5    7    1    9    3    2
         [8,]    8    1    9    7    5    6    4    2    3
         [9,]    2    7    3    6    4    9    1    5    8
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-20
          • 2018-06-22
          相关资源
          最近更新 更多