Ketty
Ketty

Reputation: 851

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)

enter image description here Here line 9 shows the solution that maximizes "obj".

Upvotes: 1

Views: 56

Answers (2)

Ronak Shah
Ronak Shah

Reputation: 389265

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

Upvotes: 1

akrun
akrun

Reputation: 887881

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(.))

Upvotes: 2

Related Questions