Q: How to avoid multiple for loops in this optimization problem I am trying to find the combination of columns "a" and "b" in table "brand_1" and "brand_2" that
maximize my objective function "obj." How can I accomplish this without writing 4 for-loops?
Here is a simple code to illustrate my question. I also need to generalize it so that it works for any number of tables (not just "brand_1" and "brand_2") with each table will always have two columns ("a" and "b"). Thanks so much!
brand_1 <- data.frame(a = c(1, 3),  b = c(5, 2))
brand_2 <- data.frame(a = c(5, 4),  b = c(2, 1))

num_row <- nrow(brand_1) # where nrow(brand_1) = nrow(brand_2)

all_a1 <- vector()
all_b1 <- vector()
all_a2 <- vector()
all_b2 <- vector()
all_obj <- vector()

for (a1 in 1:num_row) {
  for (b1 in 1:num_row) {
    for (a2 in 1:num_row) {
      for (b2 in 1:num_row) {
        obj <- brand_1[a1, "a"] + brand_1[b1, "b"] + brand_2[a2, "a"] + brand_2[b2, "b"]
        all_a1  <- c(all_a1, brand_1[a1, "a"])
        all_b1  <- c(all_b1, brand_1[b1, "b"])
        all_a2  <- c(all_a2, brand_2[a2, "a"])
        all_b2  <- c(all_b2, brand_2[b2, "b"])
        
        all_obj <- c(all_obj, obj)
      }
    }
  }
}

out <- data.frame(all_a1, all_b1, all_a2, all_b2, all_obj)

 Here line 9 shows the solution that maximizes "obj".

A: We can use expand.grid
out <- expand.grid(c(brand_1, brand_2))
names(out) <- make.unique(names(out))
out$all_obj <- rowSums(out)

-output
out
#   a b a.1 b.1 all_obj
#1  1 5   5   2      13
#2  3 5   5   2      15
#3  1 2   5   2      10
#4  3 2   5   2      12
#5  1 5   4   2      12
#6  3 5   4   2      14
#7  1 2   4   2       9
#8  3 2   4   2      11
#9  1 5   5   1      12
#10 3 5   5   1      14
#11 1 2   5   1       9
#12 3 2   5   1      11
#13 1 5   4   1      11
#14 3 5   4   1      13
#15 1 2   4   1       8
#16 3 2   4   1      10     


Or with complete
library(dplyr)
library(tidyr)
bind_cols(brand_1, brand_2)  %>%
    complete(!!! rlang::syms(names(.))) %>% 
    mutate(all_obj = rowSums(.))


A: For a general solution with any number of dataframes we can use ls to get all the objects with "brand" in it, get them in list using mget, rename the column names based on there object name and use crossing to generate their all possible combinations. Finally, you can use rowSums to sum the rows.
library(tidyverse)

mget(ls(pattern = 'brand')) %>%
  imap_dfc(~setNames(., paste0(names(.x), sub('.*_', '', .y)))) %>%
  do.call(crossing, .) %>%
  mutate(all_obj = rowSums(.))

#      a1    b1    a2    b2 all_obj
#   <dbl> <dbl> <dbl> <dbl>   <dbl>
# 1     1     2     4     1       8
# 2     1     2     4     2       9
# 3     1     2     5     1       9
# 4     1     2     5     2      10
# 5     1     5     4     1      11
# 6     1     5     4     2      12
# 7     1     5     5     1      12
# 8     1     5     5     2      13
# 9     3     2     4     1      10
#10     3     2     4     2      11
#11     3     2     5     1      11
#12     3     2     5     2      12
#13     3     5     4     1      13
#14     3     5     4     2      14
#15     3     5     5     1      14
#16     3     5     5     2      15

