R 套件筆記 | 在有條件的情況下使用 lpSolve 對樣品進行分組

更新於 發佈於 閱讀時間約 11 分鐘

最近遇到了一個思考有點久的問題,如何將數值不同的樣品進行分組,同時每組的總和不能超越固定的上限值,且某些樣品不能被分在同一組。雖然規則並不困難,但是人工進行分組卻頗花時間,是不是有可能自動化處理這個步驟?如果有可能的話想要產出多個不同的分組方法,最後由人工選擇最佳的分組方式。

沒有處理過有條件的分組的我不知如何開始進行,一開始有思考過用硬幹的方式,將樣品依照數值進行排序,從最大的樣品開始丟入組別,如果放不下就創一個新組別丟入,這樣的方式只會得到一組解,且可能發生最後一個組別總和特別少的情形,形成一種浪費。更糟糕的是加上各種條件之後容易有邏輯混亂的的問題,寫一堆 if else 讓人昏頭,一想到就覺得很可怕。

lpSolve 套件

用線性規劃 (Linear Programming, LP) 的方式思考這個問題會簡單許多,線性規劃在目標函數 (objective) 和限制條件 (constraint) 皆為線性下進行最佳化求解。 R 套件 lpSolve 便是用於解決線性規劃和整數線性規劃的問題,以下是最簡單線性組合求解問題:

  1. 目標函數,求其最大解
    x + 9 y+ z
  2. 限制條件
    x + 2y + 3z ≤ 9
    3x + 2y + 2z ≤ 15
#install.packages("lpSolve")
library(lpSolve)

#目標函數​
f.obj <- c(1, 9, 1)
#限制條件​矩陣
f.con <- matrix (c(1, 2, 3, 3, 2, 2), nrow=2, byrow=TRUE)
f.dir <- c("<=", "<=")
f.rhs <- c(9, 15)

#限制條件下求目標函數最大解​
lp ("max", f.obj, f.con, f.dir, f.rhs)
## Not run: Success: the objective function is 40.5
lp ("max", f.obj, f.con, f.dir, f.rhs)$solution
## Not run: [1] 0.0 4.5 0.0

限制條件矩陣可以選擇用 "const.mat" 或是 "dense.const" 參數輸入,使用 "const.mat" 較為直觀,但我認為 "dense.const" 在之後多條件式輸入上會比較方便一點,尤其是參數較多的時候比較不會亂掉。

# The same problem using the dense constraint approach:
#
f.con.d <- matrix (c(rep (1:2,each=3), rep (1:3, 2), t(f.con)), ncol=3)
lp ("max", f.obj, , f.dir, f.rhs, dense.const=f.con.d)
const.mat 和 dense.const 的差異

const.mat 和 dense.const 的差異

一般較常看到使用線性規劃解決生產資源分配已達到最佳化等問題,一開始沒有想到可以用這個套件解決我的問題。不過在看到 lp 函數範例中,處理 8 皇后問題給了我一些靈感。

八皇后問題

八皇后一個經典的問題,在 8×8 的西洋棋棋盤上放置 8 個皇后,使得任何一個皇后都無法直接吃掉其他的皇后。也就是說,任兩個皇后都不能處於同一條行、列或斜線上。此問題扣除旋轉和對稱之後,有 12 個獨立的解法。

在 lp 函式解法,會將棋盤上每個位置進行編號 1~64,再輸入已經寫好的限制條件,共 42 個條件包含:每個行或列都會有 1 個皇后,斜線會有小於 1 個皇后,再限制解為 binary,並設定輸出多個解 (只有 binary 可以設定多個解)。

chess.obj <- rep (1, 64)
#載入已寫好的限制條件 dense.const 矩陣
q8 <- make.q8 ()
chess.dir <- rep (c("=", "<"), c(16, 26))
chess.rhs <- rep (1, 42)
#輸出 3 個解​
chess = lp ('max', chess.obj, , chess.dir, chess.rhs, dense.const = q8,
all.bin=TRUE, num.bin.solns=3)
#拆開解(solution 最後會有一個多餘的 -1)​
res <- split(chess$solution[1:(3*64)], rep(1:3, each=64))
# 3 個矩陣解​
matrix(res$`1`,8)
matrix(res$`2`,8)
matrix(res$`3`,8)
八皇后問題限制條件

八皇后問題限制條件

限制條件下分配組別

回到分組的問題,如果把分組的結果當成棋盤,就可以用相同的邏輯進行。

首先計算所有樣品的總和,判斷分出的組數,決定矩陣的大小,之後就可以加入所有限制條件,這時候用 dense.const 輸入會比較容易一些,不同的類型的條件式各別產出之後再合併輸入 lp 函式。

分組限制條件

分組限制條件


library(lpSolve)

#每個樣品的量
weights <- c(100, 150, 20, 50, 80, 120, 10, 240, 85, 90)
#樣品種類,同種類不可同一組
type = c("A","B","C","D","E","A","B","F","G","H")
samples = length(weights)
#每個組總和上限
weight.max <- 300

#計算樣品總量 -> 組別數
groups=ceiling(sum(weights)/weight.max)

##矩陣大小
obj=rep (1, samples*groups)
length.obj=length(obj)

####限制條件####

#每組上限
group.const=matrix(c(rep(c(1:groups),each=samples),
c(1:length.obj),
rep(weights,groups)), ncol=3)
group.dir=rep("<=",groups)
group.rhs=rep(weight.max,groups)

#不可重複分配
#從第一行位置推算所有行位置
uni.const.i=seq(1,1+(groups-1)*samples,samples)
uni.const.2=c()
for(i in 1:samples){
uni.const.2=c(uni.const.2,uni.const.i+(i-1))
}
uni.const=matrix(c(rep(c((groups+1):(groups+samples)),each=groups),
uni.const.2,
rep(1,length.obj)),ncol=3)
uni.dir=rep("=",samples)
uni.rhs=rep(1,samples)

#相同種類不可分在同一組
type.dup=type[duplicated(type)]
#從第一列位置推算所有列位置
dup.const.1=c()
dup.const.2=c()
for(j in 1:length(type.dup)){
dup.const.i=which(type==type.dup[j])
for(i in 1:groups){
dup.const.2=c(dup.const.2,dup.const.i+(i-1)*samples)
dup.const.1=c(dup.const.1,
rep((groups+samples+(j-1)*groups+i),length(dup.const.i)))
}
}

dup.const=matrix(c(dup.const.1,
dup.const.2,
rep(1,length(dup.const.1))),ncol=3)
dup.dir=rep("<=",length(unique(dup.const.1)))
dup.rhs=rep(1,length(unique(dup.const.1)))

####合併篩選條件####
fin.const=rbind(group.const,uni.const,dup.const)
fin.dir <- c(group.dir,uni.dir,dup.dir)
fin.rhs <- c(group.rhs,uni.rhs,dup.rhs)

###設定候選組合
num.solns=3
x=lp ('max', objective.in = obj, const.dir = fin.dir, const.rhs = fin.rhs,
dense.const = fin.const,
all.bin=TRUE, num.bin.solns=num.solns)

#將結果分開
res <- split(x$solution[1:(num.solns*length.obj)], rep(1:num.solns, each=length.obj))

#每個組合和組總量
rbind(weights * matrix(res$`1`,samples),weights %*% matrix(res$`1`,samples))
rbind(weights * matrix(res$`2`,samples),weights %*% matrix(res$`2`,samples))
rbind(weights * matrix(res$`3`,samples),weights %*% matrix(res$`3`,samples))


最後輸出的分組結果,不過似乎因為限制較多,分組方法的差異有限,不過這樣還是比人工分組有效率許多。

分組結果

分組結果

留言
avatar-img
留言分享你的想法!
avatar-img
金吉的沙龍
7會員
41內容數
寫一些自己覺得有趣的事情
你可能也想看
Thumbnail
八皇后問題是一個經典的計算機科學問題,以前沒學好,最近剛好看到,於是重讀了一次,順手做個筆記幫助記憶,也分享給有需要或有興趣的讀者。內容枯燥,適合睡前閱讀。
Thumbnail
八皇后問題是一個經典的計算機科學問題,以前沒學好,最近剛好看到,於是重讀了一次,順手做個筆記幫助記憶,也分享給有需要或有興趣的讀者。內容枯燥,適合睡前閱讀。
Thumbnail
三種做法所花的時間都不同,請試著一步步優化,在只跑一次迴圈下完成吧!
Thumbnail
三種做法所花的時間都不同,請試著一步步優化,在只跑一次迴圈下完成吧!
Thumbnail
題目會給我們兩條已經從小到大排序好的串列,要求我們依照從小到大的順序,合併這兩條串列。
Thumbnail
題目會給我們兩條已經從小到大排序好的串列,要求我們依照從小到大的順序,合併這兩條串列。
Thumbnail
題目會給定我們一個陣列,要求我們重新安排順序,把等於零的數字搬到後面。 同時必須保持原本數字的前後相對次序。
Thumbnail
題目會給定我們一個陣列,要求我們重新安排順序,把等於零的數字搬到後面。 同時必須保持原本數字的前後相對次序。
Thumbnail
題目會給我們一個排序好的矩陣matrix ,和一個目標值 target 要求我們在矩陣中尋找target,如果存在,返回True。 如果target 不存在,返回False 題目要求必須在O( log (m*n) )對數時間內完成 。
Thumbnail
題目會給我們一個排序好的矩陣matrix ,和一個目標值 target 要求我們在矩陣中尋找target,如果存在,返回True。 如果target 不存在,返回False 題目要求必須在O( log (m*n) )對數時間內完成 。
Thumbnail
題目會給我們一個輸入陣列candidates,和一個目標值 target 問我們,從canditdates裡面重複挑選,可以湊出總和為target目標值的組合數有幾種? 在此,我們將使用找零錢II的DP模型和化簡的技巧來解題。
Thumbnail
題目會給我們一個輸入陣列candidates,和一個目標值 target 問我們,從canditdates裡面重複挑選,可以湊出總和為target目標值的組合數有幾種? 在此,我們將使用找零錢II的DP模型和化簡的技巧來解題。
Thumbnail
這題就是經典的考排序驗算法, 不管在教科書、上機考、面試白板題都是一個很基本又滿熱門的題目。 題目會給定一個輸入陣列,要求我們實作一個排序演算法,把陣列元素從小到大排好。
Thumbnail
這題就是經典的考排序驗算法, 不管在教科書、上機考、面試白板題都是一個很基本又滿熱門的題目。 題目會給定一個輸入陣列,要求我們實作一個排序演算法,把陣列元素從小到大排好。
Thumbnail
題目會給定我們一個陣列,還有一個整數值x 問我們每次從陣列頭部或尾部取一個整數,最少需要幾次才能使取出的整數總和為x?
Thumbnail
題目會給定我們一個陣列,還有一個整數值x 問我們每次從陣列頭部或尾部取一個整數,最少需要幾次才能使取出的整數總和為x?
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News