Reputation: 644
My question is about getting rid of a for loop while retaining the functionality of the code.
I have a matrix of pairwise orderings of elements A_1, A_2, ... A_N
. Each ordering is represented as a row of a matrix. The code below shows an example.
# Matrix representing the relations
# A1 < A2, A1 < A5, A2 < A4
(mat <- matrix(c(1, 2, 1, 5, 2, 4), ncol = 2, byrow = TRUE))
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 1 5
#> [3,] 2 4
I want this whole matrix as a set of ordered pairs. The reason is that I later need to generate the transitive closure of these relations. I have been using the sets
package and created the function below.
create_sets <- function(mat){
# Empty set
my_set <- sets::set()
# For loop for adding pair elements to the set, one at a time
for(i in seq(from = 1, to = nrow(mat), by = 1)){
my_set <- sets::set_union(my_set,
sets::pair(mat[[i, 1]], mat[[i, 2]]))
}
return(my_set)
}
create_sets(mat)
#> {(1, 2), (1, 5), (2, 4)}
This function works well, but I believe the for loop is unnecessary, and am not capable of replacing it. For the particular example matrix above with exactly three rows, I could instead have used to following code:
my_set2 <- sets::set(
sets::pair(mat[[1, 1]], mat[[1, 2]]),
sets::pair(mat[[2, 1]], mat[[2, 2]]),
sets::pair(mat[[3, 1]], mat[[3, 2]])
)
my_set2
#> {(1, 2), (1, 5), (2, 4)}
The reason why this works, is that sets::set
takes any number of pairs.
args(sets::set)
#> function (...)
#> NULL
However, the matrix mat
will have an arbitrary number of rows, and I want the function to be able to handle all possible cases. This is why I have not been able to get rid of the for loop.
My question is hence: Given a matrix mat
in which each row represents an ordered pair, is there some generic way of passing the pairs in each row as separate arguments to sets::set
, without looping?
Upvotes: 2
Views: 718
Reputation: 42544
The OP has asked
[...] is there some generic way of passing the pairs in each row as separate arguments to
sets::set
, without looping?
Yes, the do.call()
function is probably what you are looking for. From help(do.call)
:
do.call
constructs and executes a function call from a name or a function and a list of arguments to be passed to it.
So, OP's create_sets()
function can be replaced by
do.call(sets::set, apply(mat, 1, function(x) sets::pair(x[1], x[2])))
{(1, 2), (1, 5), (2, 4)}
The second argument to do.call()
requires a list. This is created by
apply(mat, 1, function(x) sets::pair(x[1], x[2]))
which returns the list
[[1]] (1, 2) [[2]] (1, 5) [[3]] (2, 4)
apply(mat, 1, FUN)
is a kind of implied for
loop which loops over the rows of a matrix mat
and takes the vector of row values as argument when calling function FUN
.
as.tuple()
instead of pair()
The pair()
function requires exactly two arguments. This is why we were forced to define an anonymous function function(x) sets::pair(x[1], x[2])
.
The as.tuple()
function coerces the elements of an object into elements of a set. So, the code can be even more simplified :
do.call(sets::set, apply(mat, 1, sets::as.tuple))
{(1, 2), (1, 5), (2, 4)}
Here, as.tuple()
takes the whole vector of row values and coerces it to a set.
Upvotes: 1
Reputation: 1648
for loops aren't always the end of the world, this doesn't look too bad if your matrices aren't enormous.
Write a function that combines the row things (there is a shorter way to do this, but this makes your task explicit)
f <- function(x) {
sets::pair(x[1], x[2])
}
Reduce(sets::set_union, lapply(split(mat, 1:nrow(mat)), f))
## {(1, 2), (1, 5), (2, 4)}
The Reduce
does the same thing as the for loop (repeatedly apply set_union
), and the lapply
turns the matrix into a list of pairs (also like a for loop would)
Upvotes: 1