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
7會員
36內容數
寫一些自己覺得有趣的事情
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
你可能也想看
Google News 追蹤
Thumbnail
現代社會跟以前不同了,人人都有一支手機,只要打開就可以獲得各種資訊。過去想要辦卡或是開戶就要跑一趟銀行,然而如今科技快速發展之下,金融App無聲無息地進到你生活中。但同樣的,每一家銀行都有自己的App時,我們又該如何選擇呢?(本文係由國泰世華銀行邀約) 今天我會用不同角度帶大家看這款國泰世華CUB
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
題目敘述 Combination Sum II_Leetcode #40 給定一個整數陣列candidates 和 目標值target。 每個陣列元素只能選擇一次,請問有多少種組合方法,可以使得組合數總和 = target? 請把滿足組合數總和 = target的組合方法以陣列的形式返回答案
Thumbnail
河內塔的遊戲描述 有三個柱子A柱,B柱,C柱。 A柱上有 N 個 (N>1) 穿孔圓盤,盤的尺寸由下到上依次變小。 要求按下列規則透過合法移動,將所有圓盤移至 C 柱: 1. 每次只能移動頂端的一個圓盤; 2. 大圓盤不能疊在小圓盤上面。
Thumbnail
題目敘述 Solving Questions With Brainpower 給定一個測驗題陣列,每個欄位都是一個pair, 分別記錄測驗題做完可以得到的分數,和需要的冷卻時間 (也就是會有一段時間不能作答接下來的題目)。 請問在最佳的答題策略下,最多可以獲得多少分數?
Thumbnail
題目敘述 Combination Sum IV 給定一個輸入陣列nums,和目標值target,從nums裡面挑數字去湊出總和 = target,數字可以重複挑選。 請問有多少排列數可以湊出target? 註: 排列數的意思就是位置不同代表兩種不同的方法數。
Thumbnail
給定一個整數陣列hand代表手牌點數,和參數groupSize。請問能不能每groupSize牌一組,每一組都拼出順子? 如果可以,返回True。如果無解,返回False。演算法使用最小堆積或排序。關鍵知識點:從小到大掃描每張牌,檢查能不能組成牌組長度為groupSize的順子即可。
Thumbnail
給定兩個輸入整數陣列, 若在兩個陣列遇到相同的數字可以連成一線, 但是有規定連線不可和別的連線有交叉, 請問最多可以形成幾條連線? 解答中探討了演算法化簡的技巧和DP模型, 可以透過演算法化簡的技巧, 把這題映射到原本已經學會的Longest Common Subsequence的DP模型來解開。
Thumbnail
子集合生成是一道經典的組合類上機考和面試題目。本篇文章介紹多個不同的解決方案,以及相關演算法框架。主要目標是給定n個相異的元素,產生所有的子集合。
Thumbnail
題目敘述 輸入給定一個鏈結串列的head node。 要求我們進行化簡,只要某個節點的右手邊存在比較大的節點,就刪除掉。 例如 5->2->13->3 5的右手邊有13,所以5刪除掉。 2的右手邊有13,所以2刪除掉。 13的右手邊沒有更大的節點,所以13留著。 3的右手邊沒有更大
Thumbnail
題目敘述 題目會給我們一個參數k 和 目標值n。 請問我們從1~9內挑k個相異的數字,使得他們的總和為n 的組合數有多少? 挑選時,每個數字必須相異,而且每個數字只能選一次。 題目的原文敘述 測試範例 Example 1: Input: k = 3, n = 7 Output: [
Thumbnail
題目敘述 題目會給我們一個整數陣列,裡面包含各種正整數,每回合可以消去兩個相同的數字,或者消去三個相同的數字。問最少需要幾次消去,才能讓陣列為空? 如果無解,則返回-1 詳細的題目可在這裡看到 測試範例 Example 1: Input: nums = [2,3,3,2,2,4,2,3,
Thumbnail
現代社會跟以前不同了,人人都有一支手機,只要打開就可以獲得各種資訊。過去想要辦卡或是開戶就要跑一趟銀行,然而如今科技快速發展之下,金融App無聲無息地進到你生活中。但同樣的,每一家銀行都有自己的App時,我們又該如何選擇呢?(本文係由國泰世華銀行邀約) 今天我會用不同角度帶大家看這款國泰世華CUB
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
題目敘述 Combination Sum II_Leetcode #40 給定一個整數陣列candidates 和 目標值target。 每個陣列元素只能選擇一次,請問有多少種組合方法,可以使得組合數總和 = target? 請把滿足組合數總和 = target的組合方法以陣列的形式返回答案
Thumbnail
河內塔的遊戲描述 有三個柱子A柱,B柱,C柱。 A柱上有 N 個 (N>1) 穿孔圓盤,盤的尺寸由下到上依次變小。 要求按下列規則透過合法移動,將所有圓盤移至 C 柱: 1. 每次只能移動頂端的一個圓盤; 2. 大圓盤不能疊在小圓盤上面。
Thumbnail
題目敘述 Solving Questions With Brainpower 給定一個測驗題陣列,每個欄位都是一個pair, 分別記錄測驗題做完可以得到的分數,和需要的冷卻時間 (也就是會有一段時間不能作答接下來的題目)。 請問在最佳的答題策略下,最多可以獲得多少分數?
Thumbnail
題目敘述 Combination Sum IV 給定一個輸入陣列nums,和目標值target,從nums裡面挑數字去湊出總和 = target,數字可以重複挑選。 請問有多少排列數可以湊出target? 註: 排列數的意思就是位置不同代表兩種不同的方法數。
Thumbnail
給定一個整數陣列hand代表手牌點數,和參數groupSize。請問能不能每groupSize牌一組,每一組都拼出順子? 如果可以,返回True。如果無解,返回False。演算法使用最小堆積或排序。關鍵知識點:從小到大掃描每張牌,檢查能不能組成牌組長度為groupSize的順子即可。
Thumbnail
給定兩個輸入整數陣列, 若在兩個陣列遇到相同的數字可以連成一線, 但是有規定連線不可和別的連線有交叉, 請問最多可以形成幾條連線? 解答中探討了演算法化簡的技巧和DP模型, 可以透過演算法化簡的技巧, 把這題映射到原本已經學會的Longest Common Subsequence的DP模型來解開。
Thumbnail
子集合生成是一道經典的組合類上機考和面試題目。本篇文章介紹多個不同的解決方案,以及相關演算法框架。主要目標是給定n個相異的元素,產生所有的子集合。
Thumbnail
題目敘述 輸入給定一個鏈結串列的head node。 要求我們進行化簡,只要某個節點的右手邊存在比較大的節點,就刪除掉。 例如 5->2->13->3 5的右手邊有13,所以5刪除掉。 2的右手邊有13,所以2刪除掉。 13的右手邊沒有更大的節點,所以13留著。 3的右手邊沒有更大
Thumbnail
題目敘述 題目會給我們一個參數k 和 目標值n。 請問我們從1~9內挑k個相異的數字,使得他們的總和為n 的組合數有多少? 挑選時,每個數字必須相異,而且每個數字只能選一次。 題目的原文敘述 測試範例 Example 1: Input: k = 3, n = 7 Output: [
Thumbnail
題目敘述 題目會給我們一個整數陣列,裡面包含各種正整數,每回合可以消去兩個相同的數字,或者消去三個相同的數字。問最少需要幾次消去,才能讓陣列為空? 如果無解,則返回-1 詳細的題目可在這裡看到 測試範例 Example 1: Input: nums = [2,3,3,2,2,4,2,3,