Marxist Philosophy of Science
Mathematics · Computing · Method
Sets and Combinatorics in R
A computational exploration of Cartesian products, power sets, permutations, and combinations
JOSÉ MAURICIO GÓMEZ JULIÁN
Originally published on April 4, 2021 · Revised and expanded edition
Abstract
This article brings together definitions, counting arguments, and reproducible experiments in R to study constructions on finite sets. It examines Cartesian products and power sets, ordered selections with and without repetition, combinations, and multiset permutations. The discussion distinguishes mathematical objects from their computational representations, explains the role of order and multiplicity, clarifies the relationship between power sets and σ-algebras, and separates the calculation of cardinalities from exhaustive enumeration.
Keywords: finite sets; combinatorics; R; RStudio; Cartesian product; power set; σ-algebra; permutations; combinations; reproducibility.
Counting means establishing which differences make two objects distinct. Before running a function or applying a formula, we need to decide whether order matters, whether an element may appear more than once, and whether we are constructing subsets, sequences, or collections with multiplicities. R provides an accessible laboratory for making these decisions visible and comparing their consequences with results we can inspect.
The empirical approach proposed here consists of constructing finite examples, comparing representations, and checking identities in specific cases. This activity has exploratory and educational value and helps check implementations. An identity that holds for every size also requires a general argument: running a program for several values does not, by itself, turn an observed regularity into a theorem.
1. Mathematical objects and their representation in R
A set is a collection whose defining feature is membership: writing an element more than once or changing the order of presentation does not create a different set. Thus, {a, b, a} and {b, a} represent the same set. By contrast, the sequences (a, b) and (b, a) are different, and the sequence (a, a) has two positions even though it uses only one distinct symbol.
A multiset records how often each type of element appears, without distinguishing positions. For example, {a, a, b} and {a, b, a} describe the same multiset, which differs from {a, b, b}. Here, braces are used informally to display multiplicities; the context will make clear whether they denote an ordinary set or a multiset.
| Object | Order | Multiplicity | Example |
|---|---|---|---|
| Set | Does not distinguish positions | Membership only | {a, b} = {b, a, a} |
| Sequence or tuple | Distinguishes positions | May include repetitions | (a, b) ≠ (b, a) |
| Multiset | Does not distinguish positions | Preserves quantities | {a, a, b} ≠ {a, b, b} |
An R vector has positions and can contain repeated values; it is therefore not automatically a mathematical set. In this article, we represent sets using finite numeric vectors or character vectors with no duplicates or missing values. The function unique() removes repeated values; setequal() compares membership while ignoring order and duplicates, whereas identical() compares R objects strictly. R’s set operations are documented in [1].
x <- c("a", "b", "a")
A <- unique(x)
A
# [1] "a" "b"
setequal(x, c("b", "a"))
# [1] TRUE
identical(c("a", "b"), c("b", "a"))
# [1] FALSE1.1. Basic operations and verifiable laws
The union A ∪ B contains elements belonging to at least one of the sets; the intersection A ∩ B retains their common elements; and the difference A ∖ B retains elements belonging to A but not to B. Taking a complement requires a specified universe Ω: Ac = Ω ∖ A. Without an explicit universe, the phrase “everything that is not in A” does not determine a particular set.
S <- c("a", "b")
T <- c("b", "c")
Omega <- c("a", "b", "c", "d")
union(S, T) # "a" "b" "c"
intersect(S, T) # "b"
setdiff(S, T) # "a"
setdiff(T, S) # "c"
setdiff(Omega, S) # "c" "d"
all(S %in% union(S, T)) # TRUE: S is a subset of S union TFor finite sets, the identity |S ∪ T| = |S| + |T| − |S ∩ T| corrects the double counting of shared elements. De Morgan’s laws describe how complements interact with unions and intersections: (S ∪ T)c = Sc ∩ Tc and (S ∩ T)c = Sc ∪ Tc. These laws follow by examining the membership of an arbitrary element; the script also checks them in this example.
2. Preparing the environment and reading conventions
R is the language and computing environment; RStudio is a development interface for working with R. The code can also be run in the R console or with Rscript. The basic examples use R and its standard packages; additional enumerations use gtools, and rje::powerSet() is retained as an alternative way to construct the power set [7].
# Run once if not already installed:
# install.packages(c("gtools", "rje"))
A <- c("a", "b", "c") # Small, inspectable example
B <- c(1, 2, 3)
reference_set <- 1:10 # Reference numerical example
n <- length(reference_set)
k <- 3 # Selection length, where applicableOutputs preceded by # are comments indicating expected results, not additional instructions. The following sections contain teaching examples; the complete script assembles the helper functions and checks in executable order. Package names are made explicit with package::function() to identify where each operation comes from.
The visual representations and tables are built from text and accessible HTML. This allows readers to select symbols, zoom in, and consult formulas without relying on screenshots.
3. Cartesian products: constructing pairs and tuples
Given two sets A and B, their Cartesian product A × B is the set of all ordered pairs (a, b) such that a belongs to A and b belongs to B. If both sets are finite, each element of A can be paired with every element of B:
|A × B| = |A| · |B|
Vertical bars denote cardinality.
A Cartesian product preserves coordinate positions. In general, A × B and B × A are different sets, even though they have the same cardinality. If both factors are the same set, pairs such as (a, a) may occur: sharing the same source set does not require the coordinates to be distinct.
| A / B | 1 | 2 | 3 |
|---|---|---|---|
| a | (a, 1) | (a, 2) | (a, 3) |
| b | (b, 1) | (b, 2) | (b, 3) |
| c | (c, 1) | (c, 2) | (c, 3) |
In R, expand.grid() produces one row for each combination of coordinates. In the function’s documentation, “combination” refers to a configuration of variable values, not an unordered selection in the combinatorial sense used in later sections. We specify stringsAsFactors = FALSE to keep text as text, and omit auxiliary attributes with KEEP.OUT.ATTRS = FALSE [2].
product <- expand.grid(
A = A, B = B,
KEEP.OUT.ATTRS = FALSE,
stringsAsFactors = FALSE
)
nrow(product)
# [1] 9
product_10 <- expand.grid(
x = reference_set, y = reference_set,
KEEP.OUT.ATTRS = FALSE,
stringsAsFactors = FALSE
)
nrow(product_10)
# [1] 100For k coordinates chosen from A, we write Ak. If |A| = n, then |Ak| = nk. For k = 0, we adopt A0 = {()}, the set containing the empty tuple. This convention keeps the cardinality equal to 1 even when A is empty.
4. Power sets: all subsets of a set
The power set of A, written 𝒫(A), contains all its subsets, including ∅ and A itself. When constructing a subset, each element of A has two possibilities: it is either included or excluded. For n distinct elements, the multiplication principle gives:
|𝒫(A)| = 2n
For A = {a, b, c}, the eight subsets can be grouped by size. This arrangement by levels makes the distribution 1, 3, 3, 1 visible; a complete Hasse diagram would also include the covering relations between adjacent levels.
We can construct it using standard R by enumerating combinations of every size. The code uses indices to prevent combn() from interpreting a numeric vector of length one as a population size.
A <- c("a", "b", "c")
PA <- list(A[0]) # A list that CONTAINS the empty subset
for (k in seq_len(length(A))) {
indices <- utils::combn(seq_along(A), k, simplify = FALSE)
PA <- c(PA, lapply(indices, function(i) A[i]))
}
length(PA)
# [1] 8
# Alternative using the package from the original version:
# PA <- rje::powerSet(A)
# P10 <- rje::powerSet(1:10)
# length(P10) # 10244.1. Why the power set is a σ-algebra
A σ-algebra ℱ on a universe Ω is a family of subsets that contains Ω and is closed under complements relative to Ω and under countable unions. These conditions imply that it also contains ∅. The power set 𝒫(Ω) satisfies these conditions because it contains every possible subset of Ω [6].
However, “power set” and “σ-algebra” are not synonyms. On Ω = {a, b, c}, both {∅, Ω} and {∅, {a}, {b, c}, Ω} are σ-algebras; neither equals the eight-element family 𝒫(Ω). The first distinguishes only the impossible event from the certain event; the second distinguishes the block {a} from the block {b, c}.
On a finite universe, every family of subsets is finite. Consequently, checking closure under binary unions, together with the other axioms, establishes closure under countable unions: only finitely many distinct subsets can occur in such a union. The script provides a checker for this finite setting. This procedure cannot be extended to infinite universes without further justification.
4.2. The connection with combinations
The subsets of A are partitioned by size. There is exactly one subset of size zero, n subsets of size one, and, in general, a binomial number of subsets of size k. Therefore:
This counts every subset once, including the empty set and the full set.
The correspondence between a subset and its vector of membership indicators also establishes a bijection between 𝒫(A) and {0, 1}n. This is a structural connection with binary sequences, not merely a numerical coincidence.
5. Ordered selections without repetition
We choose k distinct elements from n available elements and assign each one a position. There are n possibilities for the first position, n − 1 for the second, and so on. For 0 ≤ k ≤ n:
These constructions are also called variations, arrangements, or partial permutations without repetition, depending on the terminological tradition. When k = n, we obtain full permutations: P(n, n) = n!. If k > n, there are no possible selections without repetition, and for k = 0 there is a unique empty selection.
With A = {a, b, c} and k = 2, we obtain six sequences: (a, b), (a, c), (b, a), (b, c), (c, a), and (c, b). The same pair of symbols can appear in two different orders.
n <- 10
k <- 3
prod(n - seq_len(k) + 1)
# [1] 720
# Small enumeration; requires gtools:
# p2 <- gtools::permutations(3, 2, v = A)
# nrow(p2) # 6The falling product avoids calculating two large factorials separately, although it can also overflow for large sizes. The general function in the script handles k = 0 first, avoiding misleading descending ranges.
5.1. Summing over different lengths
Counting all nonempty sequences of lengths 1 through n means summing P(n, k), rather than calculating a single permutation count. For n = 10, the total is 9,864,100; including the empty tuple raises it to 9,864,101. Lengths distinguish objects: a sequence with two positions is never the same as a sequence with three.
n <- 10
counts <- vapply(seq_len(n), function(k) {
prod(n - seq_len(k) + 1)
}, numeric(1))
sum(counts)
# [1] 98641006. Ordered selections with unrestricted repetition
If a symbol may reappear in every position, there are always n options, and the available set does not shrink. Sequences of length k form the Cartesian product Ak:
For A = {a, b, c} and k = 2, the nine results are (a, a), (a, b), (a, c), (b, a), (b, b), (b, c), (c, a), (c, b), and (c, c). Compared with the previous section, the pairs containing identical symbols have been added.
n <- 10
k <- 3
n^k
# [1] 1000
# With unrestricted repetition, explicitly enabled:
# r2 <- gtools::permutations(
# 3, 2, v = A, repeats.allowed = TRUE
# )
# nrow(r2) # 9
# No external packages needed for the two-position case:
pairs <- expand.grid(x = A, y = A,
KEEP.OUT.ATTRS = FALSE,
stringsAsFactors = FALSE)
nrow(pairs)
# [1] 9For n = 10 and k = 10, the count is 10,000,000,000. Here, it is best to calculate the cardinality and stop: exhaustive generation is not a reasonable example for an ordinary computer. The script’s memory safeguard prevents this enumeration.
7. Combinations without repetition: choosing subsets
We now choose k distinct elements without distinguishing order. Every subset of size k gives rise to k! ordered sequences. Dividing the preceding count by that number gives:
Factorial domain: 0 ≤ k ≤ n. By combinatorial convention, C(n, k) = 0 when k > n.
With A = {a, b, c} and k = 2, there are three subsets: {a, b}, {a, c}, and {b, c}. A program’s writing {a, b} in the order of the input vector does not give the subset positions.
choose(10, 3)
# [1] 120
A <- c("a", "b", "c")
indices <- utils::combn(seq_along(A), 2, simplify = FALSE)
subsets <- lapply(indices, function(i) A[i])
length(subsets)
# [1] 3
# Another representation: each column is a combination.
utils::combn(A, 2)
# [,1] [,2] [,3]
# [1,] "a" "a" "b"
# [2,] "b" "c" "c"choose() calculates cardinalities; combn() generates selections. By default, the latter arranges results in columns, whereas gtools::combinations() arranges them in rows. Confusing these orientations leads to counting the length of a selection instead of the number of selections [3].
For a ten-element set, summing combinations of sizes 1 through 10 gives 1,023 nonempty subsets. Adding the empty set yields the 1,024 elements of its power set. The identity C(n, k) = C(n, n − k) also follows from the bijection that assigns each subset its complement.
8. Combinations with repetition: choosing multiplicities
There are n available types; we choose a total of k units and allow types to repeat, without distinguishing positions. An outcome can be described by nonnegative integer multiplicities x₁, …, xₙ satisfying:
For n ≥ 1, the stars and bars method represents the k units as stars and separates the n categories with n − 1 bars. Choosing the positions of the stars among k + n − 1 places is equivalent to choosing the multiplicities:
With A = {a, b, c} and k = 2, the six selections are {a, a}, {a, b}, {a, c}, {b, b}, {b, c}, and {c, c}. In particular, {a, b} and {b, a} represent the same outcome. For n = 10 and k = 3, the count is 220.
n <- 10
k <- 3
choose(n + k - 1, k)
# [1] 220
# Small enumeration; requires gtools:
# m2 <- gtools::combinations(
# 3, 2, v = A, repeats.allowed = TRUE
# )
# nrow(m2) # 6As an additional visual example, with the four symbols {1, 2, 3, 4} and size 2, the selections are 11, 12, 13, 14, 22, 23, 24, 33, 34, and 44. These are ten combinations with repetition; there would be sixteen ordered sequences because they would distinguish, for example, 12 from 21. A tree enumerating only those ten branches belongs in this section.
9. APPLE: permuting a fixed multiset
The word APPLE has five positions but four types of letters: A, P, L, and E. The letter P occurs twice. Swapping those two Ps does not change the visible word. Its distinct rearrangements therefore number neither 5! nor the unrestricted five-letter sequences drawn from four symbols.
When n objects have fixed multiplicities m₁, …, mᵣ summing to n, each visible sequence is counted m₁! ··· mᵣ! times if we artificially distinguish every object. Removing that distinction gives:
NAPPLE = 5!1! · 2! · 1! · 1! = 60
letters_in_word <- strsplit("APPLE", "", fixed = TRUE)[[1]]
multiplicities <- as.numeric(table(letters_in_word))
factorial(length(letters_in_word)) / prod(factorial(multiplicities))
# [1] 60The distinction has two parts: combinations with repetition choose the multiplicities; multiset permutations take the multiplicities as fixed and count the possible orders. The script checks the APPLE example through a small enumeration: it permutes the five positions and removes duplicate words. This strategy is educational; it is inefficient for large multisets because it generates duplicates before discarding them.
The correct formula depends on which distinctions are preserved by the object we want to count.
10. Comparison tables and totals by length
The following formulas answer different questions. For n = 10 and k = 3, comparing them shows how much the count changes when we alter the importance of order or the possibility of repetition.
| Construction | Order | Repetition | Cardinality | Result |
|---|---|---|---|---|
| Ordered, without repetition | Matters | No | n! / (n − k)! | 720 |
| Ordered, with unrestricted repetition | Matters | Yes | nk | 1,000 |
| Combinations without repetition | Does not matter | No | C(n, k) | 120 |
| Combinations with repetition | Does not matter | Yes | C(n + k − 1, k) | 220 |
The two formulas without repetition require k ≤ n to use their factorial expressions. The formula for combinations with repetition applies directly for n ≥ 1. All models include one empty selection when k = 0.
| Object | Count | Enumerate in small examples |
|---|---|---|
| Product A × B | length(A) * length(B) | expand.grid() |
| Power set | 2^length(A) | The script’s power_set(); rje::powerSet() |
| Ordered, without repetition | count_ordered(n, k) | gtools::permutations() |
| Ordered, with repetition | n^k | gtools::permutations(..., repeats.allowed = TRUE) |
| Combinations without repetition | choose(n, k) | utils::combn() |
| Combinations with repetition | choose(n + k - 1, k) | gtools::combinations(..., repeats.allowed = TRUE) |
10.1. The eleven sizes for n = 10
The k = 0 row is essential for identities that include the empty object. The table presents counts without generating the corresponding collections.
| k | Ordered without repetition | Ordered with repetition | Combinations without repetition | Combinations with repetition |
|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 1 |
| 1 | 10 | 10 | 10 | 10 |
| 2 | 90 | 100 | 45 | 55 |
| 3 | 720 | 1,000 | 120 | 220 |
| 4 | 5,040 | 10,000 | 210 | 715 |
| 5 | 30,240 | 100,000 | 252 | 2,002 |
| 6 | 151,200 | 1,000,000 | 210 | 5,005 |
| 7 | 604,800 | 10,000,000 | 120 | 11,440 |
| 8 | 1,814,400 | 100,000,000 | 45 | 24,310 |
| 9 | 3,628,800 | 1,000,000,000 | 10 | 48,620 |
| 10 | 3,628,800 | 10,000,000,000 | 1 | 92,378 |
10.2. Nonempty totals: k from 1 through 10
| Family | Sum | Total |
|---|---|---|
| Ordered, without repetition | ∑ P(10, k) | 9,864,100 |
| Ordered, with unrestricted repetition | ∑ 10k | 11,111,111,110 |
| Combinations without repetition | 210 − 1 | 1,023 |
| Combinations with repetition | C(20, 10) − 1 | 184,755 |
The last result follows from the identity ∑k=0m C(n + k − 1, k) = C(n + m, m), for n ≥ 1. One justification adds an auxiliary category that absorbs the units needed to reach m: choosing up to m units from n types is equivalent to distributing exactly m units among n + 1 types. We subtract one from the final count to exclude the empty selection.
With unrestricted repetition and at least one available symbol, allowing k to grow without a bound produces infinitely many sequences and infinitely many finite multisets. The range of sizes must therefore be stated whenever a “total” is presented.
n <- 10
ks <- 0:n
counts <- data.frame(
k = ks,
ordered_without = vapply(ks, function(k) {
if (k == 0) 1 else prod(n - seq_len(k) + 1)
}, numeric(1)),
ordered_with = n^ks,
combinations_without = choose(n, ks),
combinations_with = choose(n + ks - 1, ks)
)
colSums(counts[-1, -1])
# ordered_without ordered_with
# 9864100 11111111110
# combinations_without combinations_with
# 1023 18475511. Scalability, limits, and numerical precision
Knowing a cardinality does not require materializing every object. A hypothetical matrix with 1010 rows, ten columns, and eight bytes per cell would require approximately 800 decimal GB for the data alone, before accounting for auxiliary structures and temporary copies. This estimate applies to that specific representation; actual consumption depends on the data type and algorithm.
The script therefore sets teaching limits of 100,000 results and 1,000,000 estimated cells before enumeration. These limits do not guarantee that every admitted computation will be fast or use little memory; they prevent accidental execution of the largest examples. For larger tasks, consider lazy generation, partial traversal, or specialized algorithms.
11.1. Overflow and approximate arithmetic
R’s double numbers do not represent every arbitrarily large integer exactly. Rounding can occur even when the result is not infinite. A quotient of factorials does not avoid this problem either: intermediate factorials can overflow even when the final result is representable.
choose() calculates binomial coefficients, and lchoose() obtains their logarithms without first forming the full quantity; lfactorial() serves an analogous purpose for factorials [5]. To estimate an order of magnitude:
# Base-10 logarithm of C(2000, 1000):
log10_binomial <- lchoose(2000, 1000) / log(10)
log10_binomial
# Approximately 600.3114
# Logarithm of P(n, k), within the domain 0 <= k <= n:
n <- 1000
k <- 500
log_P <- lfactorial(n) - lfactorial(n - k)Working with logarithms reduces numerical range problems; it does not turn a calculation into exact integer arithmetic. Nor should an enormous logarithm automatically be exponentiated, since overflow may reappear. If every digit of a large integer is needed, an appropriate arbitrary-precision tool must be selected and validated for that purpose.
11.2. What the checks establish
The script compares counts with small enumerations, checks summation identities, and examines boundary cases. It also avoids confusing an empty list with a list containing the empty selection. These tests detect implementation errors, inconsistent conventions, and regressions. Explanations based on bijections and counting principles justify the results beyond the finite grid that is checked.
12. Mathematical interpretation and applications
The preceding constructions are related by specific functions and correspondences. Forgetting the order of a sequence of length k without repetition produces a subset of size k, and every subset has k! preimages. If repetitions are allowed, forgetting order produces a multiset; in this case, the number of sequences associated with each multiset depends on its multiplicities.
This difference explains why dividing nk by k! does not, in general, give the number of combinations with repetition. For A = {a, b} and k = 2, the sequences aa and bb each have a single visible order, whereas ab and ba are grouped into the same multiset. The groups do not all have the same size.
12.1. Probability: enumeration does not specify a distribution
Suppose we make two independent draws with replacement from a mechanism that produces a and b with probability 1/2 each. The sequences aa, ab, ba, and bb each have probability 1/4. After forgetting order, the three multisets {a, a}, {a, b}, and {b, b} have probabilities 1/4, 1/2, and 1/4. Choosing uniformly among these three multisets therefore does not reproduce the same experiment.
The formula “favorable outcomes / possible outcomes” requires the counted outcomes to be equally probable. Defining the sample space, the selection mechanism, and the distribution is an additional step beyond enumerating objects.
12.2. Experimental design and data science
- Cartesian product: constructing every configuration in a parameter grid or every combination of factor levels. If the factors have different sizes, multiply their cardinalities.
- Power set: describing possible selections of variables. A set of n variables has 2n subsets, including the model with no selected predictors.
- Ordered selections without repetition: assigning distinct positions to different people, with one candidate per position and no reuse.
- Combinations without repetition: selecting committees or subsets of observations when selection order is not part of the outcome.
- Combinations with repetition: distributing indistinguishable units among identified categories, with nonnegative quantities and no upper bounds.
The assumptions must be reviewed for each application. Incompatible factor combinations, limited inventories, unequal probabilities, or additional constraints change the problem. A table of formulas guides the choice; the definition of the object and mechanism determines which formula is appropriate.
12.3. Epistemological scope
A computational representation selects which information to retain. Converting a sequence into a set removes order and multiplicity; converting it into a multiset removes order but preserves frequencies. These transformations show that a mathematical abstraction is more than a change of name: it changes the distinctions organizing the problem and, with them, the counting results.
Computational experimentation becomes more informative when that selection is explicit. Defining, representing, deducing, executing, and comparing are complementary stages of reproducible inquiry into finite objects.
13. Complete reproducible script
The following program brings the article together. Copy its contents into a text file named sets_combinatorics.R, save it as UTF-8, and run it with source("sets_combinatorics.R", encoding = "UTF-8") or Rscript sets_combinatorics.R. External packages are used only if installed; the program reports when it skips those blocks.
The counting functions validate nonnegative integer sizes. The enumerators represent each selection as a vector inside a list, including empty cases. The size safeguard runs before calling the enumerators. The representation domain is restricted to finite numeric vectors and character vectors without missing values; sets of more complex objects require an appropriate equality relation.
Open the complete script · functions, examples, and checks
# SETS AND COMBINATORICS IN R
# JOSÉ MAURICIO GÓMEZ JULIÁN
# Article script. Save as sets_combinatorics.R (UTF-8).
# The basic examples require no external packages.
# install.packages(c("gtools", "rje")) # Only if the optional blocks are wanted.
# 1. Validation and counting functions -------------------------------
nonnegative_integer <- function(x, name) {
if (!is.numeric(x) || is.complex(x) || length(x) != 1L || is.na(x) ||
!is.finite(x) || x < 0 || x != floor(x)) {
stop(name, " must be a finite nonnegative integer.")
}
invisible(TRUE)
}
validate_nk <- function(n, k) {
nonnegative_integer(n, "n")
nonnegative_integer(k, "k")
}
normalize_set <- function(x) {
if ((!is.numeric(x) && !is.character(x)) || is.complex(x) ||
!is.null(dim(x)) || anyNA(x) ||
(is.numeric(x) && any(!is.finite(x)))) {
stop("Use a finite numeric or character vector, with no NA values or dimensions.")
}
unique(x)
}
count_ordered <- function(n, k, repetition = FALSE) {
validate_nk(n, k)
if (!is.logical(repetition) || length(repetition) != 1L ||
is.na(repetition)) stop("repetition must be TRUE or FALSE.")
if (k == 0) return(1)
if (repetition) return(n^k)
if (k > n) return(0)
# Falling product: avoids forming two factorials separately.
prod(n - seq_len(k) + 1)
}
count_combinations <- function(n, k, repetition = FALSE) {
validate_nk(n, k)
if (!is.logical(repetition) || length(repetition) != 1L ||
is.na(repetition)) stop("repetition must be TRUE or FALSE.")
if (k == 0) return(1)
if (n == 0) return(0)
if (repetition) return(choose(n + k - 1, k))
if (k > n) return(0)
choose(n, k)
}
limit_enumeration <- function(rows, columns,
max_rows = 1e5,
max_cells = 1e6) {
if (!is.finite(rows) || rows > max_rows ||
rows * max(1, columns) > max_cells) {
stop("Enumeration stopped by the teaching limit; calculate only the count.")
}
invisible(TRUE)
}
# All enumerations return lists of vectors.
# For k=0, return list(x[0]): a list containing an empty vector.
enumerate_ordered <- function(x, k, repetition = FALSE) {
x <- normalize_set(x)
n <- length(x)
total <- count_ordered(n, k, repetition)
limit_enumeration(total, k)
if (k == 0) return(list(x[0]))
if (total == 0) return(list())
if (!requireNamespace("gtools", quietly = TRUE)) {
stop("This enumerator requires gtools: install.packages('gtools').")
}
m <- gtools::permutations(n, k, v = x,
repeats.allowed = repetition)
lapply(seq_len(nrow(m)), function(i) unname(m[i, ]))
}
enumerate_combinations <- function(x, k, repetition = FALSE) {
x <- normalize_set(x)
n <- length(x)
total <- count_combinations(n, k, repetition)
limit_enumeration(total, k)
if (k == 0) return(list(x[0]))
if (total == 0) return(list())
if (!repetition) {
# Using indices prevents combn(5, 1) from interpreting 5 as 1:5.
indices <- utils::combn(seq_len(n), k, simplify = FALSE)
return(lapply(indices, function(i) x[i]))
}
if (!requireNamespace("gtools", quietly = TRUE)) {
stop("This enumerator requires gtools: install.packages('gtools').")
}
m <- gtools::combinations(n, k, v = x, repeats.allowed = TRUE)
lapply(seq_len(nrow(m)), function(i) unname(m[i, ]))
}
power_set <- function(x) {
x <- normalize_set(x)
n <- length(x)
limit_enumeration(2^n, n)
result <- list(x[0])
for (k in seq_len(n)) {
result <- c(result, enumerate_combinations(x, k))
}
result
}
format_family <- function(F) {
vapply(F, function(x) {
if (length(x) == 0) "{}" else paste0("{", paste(x, collapse = ", "), "}")
}, character(1))
}
# 2. Sets and Cartesian products -------------------------------
A <- c("a", "b", "c")
B <- c(1, 2, 3)
reference_set <- 1:10
n <- length(reference_set)
stopifnot(
setequal(c("a", "b", "a"), c("b", "a")),
!identical(c("a", "b"), c("b", "a")),
identical(normalize_set(c(1, 1, 2)), c(1, 2))
)
product <- expand.grid(A = A, B = B,
KEEP.OUT.ATTRS = FALSE, stringsAsFactors = FALSE)
stopifnot(nrow(product) == length(A) * length(B))
product_10 <- expand.grid(x = reference_set, y = reference_set,
KEEP.OUT.ATTRS = FALSE, stringsAsFactors = FALSE)
stopifnot(nrow(product_10) == 100)
print(product)
# Set operations and laws within an explicit universe.
S <- c("a", "b")
T <- c("b", "c")
Omega <- c("a", "b", "c", "d")
stopifnot(
length(union(S, T)) == length(S) + length(T) - length(intersect(S, T)),
setequal(setdiff(Omega, union(S, T)),
intersect(setdiff(Omega, S), setdiff(Omega, T))),
setequal(setdiff(Omega, intersect(S, T)),
union(setdiff(Omega, S), setdiff(Omega, T)))
)
# 3. Power set and finite sigma-algebra ----------------------
PA <- power_set(A)
P10 <- power_set(reference_set)
stopifnot(length(PA) == 8, length(P10) == 1024,
sum(lengths(PA) == 0) == 1)
print(format_family(PA))
contains_set <- function(x, F) {
any(vapply(F, function(y) setequal(x, y), logical(1)))
}
is_finite_sigma_algebra <- function(F, omega) {
if (!is.list(F)) return(FALSE)
if (!all(vapply(F, function(x) all(x %in% omega), logical(1)))) {
return(FALSE)
}
if (!contains_set(omega[0], F) || !contains_set(omega, F)) return(FALSE)
for (x in F) {
if (!contains_set(setdiff(omega, x), F)) return(FALSE)
for (y in F) if (!contains_set(union(x, y), F)) return(FALSE)
}
TRUE
}
F_min <- list(A[0], A)
F_intermediate <- list(A[0], "a", c("b", "c"), A)
stopifnot(is_finite_sigma_algebra(F_min, A),
is_finite_sigma_algebra(F_intermediate, A),
is_finite_sigma_algebra(PA, A),
!is_finite_sigma_algebra(list(A[0], "a", A), A))
# On a finite universe, closure under binary unions suffices here:
# any countable union involves only finitely many distinct subsets.
# 4. Small sequence enumeration using base R -----------------------
pairs <- expand.grid(x = A, y = A,
KEEP.OUT.ATTRS = FALSE, stringsAsFactors = FALSE)
without_repetition <- pairs[pairs$x != pairs$y, , drop = FALSE]
stopifnot(nrow(pairs) == 9, nrow(without_repetition) == 6,
length(enumerate_combinations(A, 2)) == 3)
# 5. Count table for n=10, k=0,...,10 ------------------------
ks <- 0:n
counts <- data.frame(
k = ks,
ordered_without = vapply(ks, function(k) count_ordered(n, k), numeric(1)),
ordered_with = vapply(ks, function(k) count_ordered(n, k, TRUE), numeric(1)),
combinations_without = vapply(ks, function(k) count_combinations(n, k), numeric(1)),
combinations_with = vapply(ks, function(k) count_combinations(n, k, TRUE), numeric(1))
)
print(counts, row.names = FALSE)
stopifnot(sum(counts$combinations_without) == 2^n,
sum(counts$combinations_without[-1]) == 1023,
sum(counts$ordered_without[-1]) == 9864100,
sum(counts$ordered_with[-1]) == 11111111110,
sum(counts$combinations_with[-1]) == 184755)
# Lists for all lengths are illustrated with n=3.
# Millions of sequences for n=10 are not enumerated.
combinations_by_k <- lapply(0:length(A), function(k) {
enumerate_combinations(A, k)
})
stopifnot(sum(lengths(combinations_by_k)) == 8)
# 6. APPLE: permutations of a multiset -----------------------
letters_in_word <- strsplit("APPLE", "", fixed = TRUE)[[1]]
multiplicities <- as.numeric(table(letters_in_word))
apple_count <- factorial(length(letters_in_word)) /
prod(factorial(multiplicities))
stopifnot(apple_count == 60)
# 7. Boundary cases and memory safeguards ------------------------
stopifnot(count_ordered(0, 0) == 1,
count_ordered(0, 0, TRUE) == 1,
count_ordered(0, 2, TRUE) == 0,
count_ordered(2, 3) == 0,
count_combinations(0, 0, TRUE) == 1,
count_combinations(0, 2, TRUE) == 0,
length(power_set(numeric(0))) == 1,
identical(enumerate_combinations(5, 1), list(5)),
length(enumerate_combinations(A, 0)) == 1)
expected_failure <- tryCatch({
limit_enumeration(10^10, 10)
FALSE
}, error = function(e) TRUE)
stopifnot(expected_failure)
# Large counts: logarithmic scales do not provide exact arithmetic.
log10_binomial <- lchoose(2000, 1000) / log(10)
print(log10_binomial)
# 8. Optional block: gtools ---------------------------------------
if (requireNamespace("gtools", quietly = TRUE)) {
p2 <- enumerate_ordered(A, 2)
r2 <- enumerate_ordered(A, 2, TRUE)
c2 <- enumerate_combinations(A, 2)
m2 <- enumerate_combinations(A, 2, TRUE)
stopifnot(length(p2) == 6, length(r2) == 9,
length(c2) == 3, length(m2) == 6)
# Compare generated objects with formulas on a small grid.
for (nn in 1:4) {
xx <- letters[seq_len(nn)]
for (kk in 0:4) {
for (rr in c(FALSE, TRUE)) {
stopifnot(
length(enumerate_ordered(xx, kk, rr)) ==
count_ordered(nn, kk, rr),
length(enumerate_combinations(xx, kk, rr)) ==
count_combinations(nn, kk, rr)
)
}
}
}
ordered_by_k <- lapply(0:length(A), function(k) {
enumerate_ordered(A, k)
})
stopifnot(sum(lengths(ordered_by_k)) == 16)
# APPLE: distinguish positions first; then remove duplicate words.
positions <- gtools::permutations(length(letters_in_word), length(letters_in_word))
words <- apply(positions, 1, function(i) paste0(letters_in_word[i], collapse = ""))
apple_words <- unique(words)
stopifnot(length(apple_words) == apple_count)
print(head(apple_words))
} else {
message("gtools not installed: its examples were skipped; base counts were computed.")
}
# 9. Optional block: rje ------------------------------------------
if (requireNamespace("rje", quietly = TRUE)) {
PA_rje <- rje::powerSet(A)
stopifnot(length(PA_rje) == 8,
all(vapply(PA_rje, function(x) contains_set(x, PA), logical(1))))
} else {
message("rje not installed: the base-R power-set construction was used.")
}
cat("Checks completed without errors.\n")
sessionInfo()
The output of sessionInfo() records the R version, platform, and packages attached or loaded through a namespace. Keep it alongside the results. The absence of gtools or rje does not count as a successful check of its respective block: those examples are skipped with an explicit message.
14. References and documentation
The following references support the functions used and the conceptual clarification concerning σ-algebras. The counting arguments are developed in the article through multiplication principles, bijections, and explanations of how outcomes are identified.
- R Core Team. Set Operations. Official documentation for the base package. Union, intersection, difference, and set comparison operations. Read the documentation.
- R Core Team. Create a Data Frame from All Combinations of Factor Variables. Documentation for
expand.grid(). Read the documentation. - R Core Team. Generate All Combinations of n Elements, Taken m at a Time. Documentation for
utils::combn(). Read the documentation. - gtools package documentation. Enumerate the Combinations or Permutations of the Elements of a Vector. Reference for
combinations(),permutations(), and their arguments. Read the CRAN documentation. - R Core Team. Special Functions of Mathematics. Documentation for
choose(),lchoose(),factorial(), andlfactorial(). Read the documentation. - Kehle, Christoph (2025). Introduction to Functional Analysis. Lecture notes for 18.102, Massachusetts Institute of Technology, Section 3.1, Definition 3.1 and example, pp. 25–26. Definition of a σ-algebra and the power-set example with counting measure. Read the lecture notes.
- rje package documentation. Miscellaneous Useful Functions for Statistics. Reference index,
powerSetentry; also available from R withhelp("powerSet", package = "rje"). Read the package index.


Leave a Comment/Deja un Comentario