Øystein S
Øystein S

Reputation: 644

How to pass an arbitrary number of arguments to R function without for loop?

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

Answers (2)

Uwe
Uwe

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.

Edit: 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

twedl
twedl

Reputation: 1648

Option 1: do nothing

for loops aren't always the end of the world, this doesn't look too bad if your matrices aren't enormous.

Option 2: the split, apply, combine way (by way of a new function)

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

Related Questions