14

I want to generate a set of permutations of n balls in m bins. The following set of nested lists generates those permutations.

n <- 3
m <- 4
v <- rep(0,m)
for (i in n:0){
  for (j in (n-sum(i)):0){
    for (k in (n-sum(i,j)):0){
      for (l in (n - sum(i,j,k)):0){
        v <- c(i,j,k,l)
        print(v)
        if (sum(v) == n){ break }
      }
    }
  }
}

Which prints the solution:

[1] 3 0 0 0
[1] 2 1 0 0
[1] 2 0 1 0
[1] 2 0 0 1
[1] 1 2 0 0
[1] 1 1 1 0
[1] 1 1 0 1
[1] 1 0 2 0
[1] 1 0 1 1
[1] 1 0 0 2
[1] 0 3 0 0
[1] 0 2 1 0
[1] 0 2 0 1
[1] 0 1 2 0
[1] 0 1 1 1
[1] 0 1 0 2
[1] 0 0 3 0
[1] 0 0 2 1
[1] 0 0 1 2
[1] 0 0 0 3

The total number of permutations will be choose(n+m-1,m-1), and the order of the permutations does not matter to me. But I am having a hard time making this into a function that can take an arbitrary number of bins. (I won't spoil the well with my attempts, it is just jumble of nested loops though.) So if someone more saavy than me could translate the nested loops above into a function I would appreciate it.

Or if there is already a function available to conduct this type of permutation (or a different algorithm to follow) I would appreciate being told about it. I would prefer an approach that does not generate superfluous permutations (here ones that do not add up to n) and then discards them, but for small problems like this a solution that does that would be acceptable.

Andy W
  • 5,031
  • 3
  • 25
  • 51
  • 3
    One approach, certainly not the most efficient but better than the multiple nested for loops, would be: `x <- expand.grid(rep(list(0:n), m)); x[rowSums(x) == n,]` – talat Nov 21 '14 at 15:27
  • Thank you @beginneR! I was having difficulty using `expand.grid` how I wanted it, that example clears it up a bit for me though. – Andy W Nov 21 '14 at 15:30
  • 3
    Never **ever** reinvent an obvious wheel. There are tons of comb and perm -like tools in various packages. (e.g. Josh's answer) – Carl Witthoft Nov 21 '14 at 15:36
  • 4
    Not sure what the point of the downvote was. I certainly investigated several packages before I created this question, e.g. `combinations` and `permutations` plus base R functions. – Andy W Nov 21 '14 at 15:38
  • If you haven't seen it, you might find [this question and answer](http://stackoverflow.com/q/22569176/980833) useful as a general reference. It's not comprehensive, but does point to a number of useful packages for combinatorics in R. – Josh O'Brien Nov 21 '14 at 15:44

5 Answers5

18
library(partitions)
compositions(3,4)

# [1,] 3 2 1 0 2 1 0 1 0 0 2 1 0 1 0 0 1 0 0 0
# [2,] 0 1 2 3 0 1 2 0 1 0 0 1 2 0 1 0 0 1 0 0
# [3,] 0 0 0 0 1 1 1 2 2 3 0 0 0 1 1 2 0 0 1 0
# [4,] 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 3
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • Thank you, the `partitions` library never came up in my search attempts. Looking at the source I would have never come up with that solution either. – Andy W Nov 21 '14 at 15:53
2

The following gives a slightly different but equivalent answer by using a more general package iterpc

m = 4; n = 3
library(iterpc)
I = iterpc(m, n, replace=T)
getall(I)

The output is the bin numbers for the n balls.

     [,1] [,2] [,3]
 [1,]    1    1    1
 [2,]    1    1    2
....
....
[18,]    3    3    4
[19,]    3    4    4
[20,]    4    4    4

The first line means that the 3 balls are all from bin 1 while the last line means that the 3 balls are all from bin 4.

You can easily produce your desired result by counting numbers of 1, 2, 3 and 4's. And you can also make use of the iterator to generate the result sequentially.

count <- function(x){
    as.numeric(table(factor(x, levels=1:m)))
}
I = iterpc(m, n, replace=T)


> count(getnext(I))
[1] 3 0 0 0
> count(getnext(I))
[1] 2 1 0 0
> count(getnext(I))
[1] 2 0 1 0
> count(getnext(I))
[1] 2 0 0 1
Randy Lai
  • 3,084
  • 2
  • 22
  • 23
2

Using the excellent RccpAlgos package. From 2.6.0 there are several integer composition functions.

compositionsGeneral(v = 0:3, m = 4, repetition = TRUE, weak = TRUE)

      [,1] [,2] [,3] [,4]
 [1,]    0    0    0    3
 [2,]    0    0    3    0
 [3,]    0    3    0    0
 [4,]    3    0    0    0
 [5,]    0    0    1    2
 [6,]    0    0    2    1
 [7,]    0    1    0    2
 [8,]    0    1    2    0
 [9,]    0    2    0    1
[10,]    0    2    1    0
[11,]    1    0    0    2
[12,]    1    0    2    0
[13,]    1    2    0    0
[14,]    2    0    0    1
[15,]    2    0    1    0
[16,]    2    1    0    0
[17,]    0    1    1    1
[18,]    1    0    1    1
[19,]    1    1    0    1
[20,]    1    1    1    0

Pre-RcppAlgos 2.6.0 alternative

Henrik
  • 65,555
  • 14
  • 143
  • 159
  • And `2.6.0` is officially out! So now we can do: `compositionsGeneral(0:3, 4, repetition = TRUE, weak=TRUE)`. Thanks again for your input. – Joseph Wood Aug 19 '22 at 18:00
  • 1
    @Joseph Wood Very nice feature! Thanks for your great work and for letting me know. Updated answer. Cheers – Henrik Aug 19 '22 at 18:16
  • I just noticed, the output is in lexicographical order with `compositionsGeneral`. – Joseph Wood Aug 20 '22 at 01:19
1

This can be done pretty easily in R - I see a lot of complicated algorithms out there, but it can be done by shifting bars. We keep track of the locations of partitions (bars) rather than how many are in each bin. If there are 3 objects that makes 3 bar positions:

objects         O O O 
bar position   0 1 2 3 

For example, if we have N=3 and K=4 bins, we will have K-1=3 bars. Begin with all bars position 0

|||OOO

In other words, all objects are in bin 4. The algorithm goes like this:

  1. Find the right-most bar that is not in position N. Increment that bar position by 1, and also collect all bars to the right to that position.
  2. Record the partition
  3. Go to 1: Repeat until all bars are in position N

This will iterate through every partitioning of N objects into K bins.

  1. |||OOO starting permutation; bump bar 3
  2. ||O|OO bar 3 is in position 1; bump it
  3. ||OO|O bar 3 is in position 2; bump it
  4. ||OOO| bar 3 is in position 3. bar 2 is in position 0; bump it and reset bar 3
  5. |O||OO bar 3 is in position 1; bump it
  6. |O|O|O bar 3 is in position 2; bump it
  7. |O|OO| bar 3 is in position 3. bar 2 is in position 1; bump it and reset bar 3
  8. |OO||O

and so on.

N=3
K=4
k=K-1
bars=rep(0,k)
bars[k]=-1      #so bars should be a vector of k-1 0s and then a -1; 


while(bars[1]<N){    #if the first bar is at position N then we're done
  for(j in k:1){     #go backwards through bars
    if(bars[j]<N){   #the first bar that is not in the final position...
      bars[j:k]=bars[j]+1 ##bump it up by 1 and reset all subsequent bars
      break
    }
  }
  print(c(bars,N)-c(0,bars))  
  ##this just translates bar positions into bin counts
}
0

Here is a base R solution that returns a list.

get_list <- function(M, N) {
  # All permutations
  perms <- expand.grid(rep(list(0:N), M))
  
  # Keep those meeting constraints
  kept <- perms[rowSums(perms) == N,]
  
  # Return as list
  if (length(kept) > 1) {
    split(kept, seq(nrow(kept)))
  } else {
    as.list(kept)
  }
  
}

# Compute
get_list(N = 5, M = 1)
#> [[1]]
#> [1] 5
get_list(N = 2, M = 3)
#> $`1`
#>   Var1 Var2 Var3
#> 3    2    0    0
#> 
#> $`2`
#>   Var1 Var2 Var3
#> 5    1    1    0
#> 
#> $`3`
#>   Var1 Var2 Var3
#> 7    0    2    0
#> 
#> $`4`
#>    Var1 Var2 Var3
#> 11    1    0    1
#> 
#> $`5`
#>    Var1 Var2 Var3
#> 13    0    1    1
#> 
#> $`6`
#>    Var1 Var2 Var3
#> 19    0    0    2
socialscientist
  • 3,759
  • 5
  • 23
  • 58
  • 1
    Please note that a power set with `expand.grid` soon becomes very inefficient. See e.g. [all combinations of k numbers between 0 and n whose sum equals n, speed optimization](https://stackoverflow.com/a/23276284/1851712). Cheers – Henrik Aug 05 '22 at 16:03
  • Yes, the question, however, is not "What is the [fastest/least memory-intensive/whatever] solution" - thus the lack of benchmarks on all other solutions within. – socialscientist Aug 05 '22 at 16:09
  • 1
    Indeed, I know, that's why I phrased my comment carefully 'please note', for future visitors. See also [the first comment](https://stackoverflow.com/questions/27064675/generating-all-permutations-of-n-balls-in-m-bins/73252355?noredirect=1#comment42643200_27064675). Cheers :) – Henrik Aug 05 '22 at 16:12