iT邦幫忙

0

運費單位的寫法

php
  • 分享至 

  • xImage

各位好
運費單位設定為
A 10單位 150
B 5單位 100
C 2單位 60
這三種箱子

以幾個例子來說
單位12的話最划算的結果是A+C = 210
單位15的話最划算的結果是A+B = 250
單位6的話最划算的結果是B+C = 160

不知道程式怎樣寫可以得到這個結果

看更多先前的討論...收起先前的討論...
思考的解決方式蠻單純而直覺,
就像中學的雞兔同籠那樣地列舉的算法。

主要需列出所有可能符合條件的排法,
排出來後,再乘以價錢,就很容易比較哪種排法最低價;
所以關鍵是怎麼把各種排法列出來。

先不要一下就三元方程式來想,
而先以二元方程式的較小規模較好解決,
且把 = 改為是 >= 右邊值的最小值來考量:
以 5x + 2y >= 7 為例:
def maxx(a,b)
  int = b/a
  mod = b%a
  if mod.zero?
    return int
  else
    return int + 1
  end
end

def myrange(a,b)
  arr = []
  maxx = maxx(a,b)
  0.step(maxx,1) do |x|
    arr << x
  end
  return arr
end

def mycount(a,b,c)
  # ax + by = c
  puts "#{a}X + #{b}Y = #{c}"
  myrange = myrange(a,c)
  myrange.each do |i|
    #puts "X = #{i}"
    k = c - i * a
    mymax = maxx(b,k)
    if k > 0
      puts "#{a} x #{i} + #{b} x #{mymax} = #{c} ( #{a*i + b*mymax} )"
    else
      puts "#{a} x #{i} + #{b} x 0 = #{c} ( #{a*i} )"
    end
  end
end


子程序 maxx(a,b) 要算出像 a * x = b 裡的 x 最大值為何,
5 * x >= 7 這個 >= 符號不是很恰當,
不曉得該怎麼表示,而 x 可以等於 0,1,2 就是要算出那最大值為 2
7/5 的商為 1,餘數非 0 就把商數加 1 ,就是最大值;
餘數為 0,則商數就是最大值。
子程序 myrange(a,b) 就只是把 maxx 求到的最大值,
從 1 算到最大值成一陣列。

子程序 mycount(a,b,c) 就是先不考量 Y ,列出所有可能X的值,
然後以 X 逐項代入,這是第24行的迴圈第一層。
26 行 k 就是 7 減掉 5x 後,
來算 27 行的可以的最大值。
7 - 5x 若大於 0,就可用 maxx 來算 y 可以的最大值,
若等於或小於0 , y 值就一定是 0,不然會爆掉。
+--+--+--+
| 5| 2|  |
+--+--+--+
| 0| 4| 8|
+--+--+--+
| 1| 1| 7|
+--+--+--+
| 2| 0|10|
+--+--+--+

執行結果:
$ irb -r2Box
>> mycount(5,2,7)
5X + 2Y = 7
5 x 0 + 2 x 4 = 7 ( 8 )
5 x 1 + 2 x 1 = 7 ( 7 )
5 x 2 + 2 x 0 = 7 ( 10 )

有了這二元的解決的基礎,三元的解法,
就類似此法,再多個迴圈可列舉推算出來。

數學的動作,用程式語言寫出,還蠻鎖碎,
也不大容易做有效的說明。

這個不曉得 X 可以是 0,1,2 ,
該用數學的什麼符號來代表較適當?
這是不了解所在。
jamesjan iT邦高手 1 級 ‧ 2010-06-22 12:45:18 檢舉
chan15提到:
單位6的話最划算的結果是B+C = 160

以這個例子來說應該不是最划算吧!
超過單位 5 在單位10 以下的 應該都用 A 去計價
除非單位的包裝有更明確的限制

像如果我只有 3 單位,用 2 單位的計價 需要 120 元
我用 5 單位去計價才 100 元
fillano iT邦超人 1 級 ‧ 2010-06-22 13:40:42 檢舉
對耶...我算出來也是這樣,沒有去套第三個條件就沒發現(前兩個沒錯就沒檢查第三個)...
6 單位:
>> b=BoxPrice.new(10,5,2,6)
10X + 5Y + 2Z = 6
10 x 0 + 5 x 0 + 2 x 3 = 6 ( 6 )
10 x 0 + 5 x 1 + 2 x 1 = 6 ( 7 )
10 x 0 + 5 x 2 + 2 x 0 = 6 ( 10 )
10 x 1 + 5 x 0 + 2 x 0 = 6 ( 10 )
=> #<BoxPrice:0x9b064d4 @solutions=[[0, 0, 3], [0, 1, 1], [0, 2, 0], [1, 0, 0]], @prices=[[0, 0, 180], [0, 100, 60], [0, 200, 0], [150, 0, 0]], @total=[180, 160, 200, 150], @min=3, @best=[1, 0, 0]>
>> b.best
=> [1, 0, 0]
>> b.total
=> [180, 160, 200, 150]
>> b.prices[b.min]
=> [150, 0, 0]
>> b.solutions[b.min]
=> [1, 0, 0]
3單位:
>> b=BoxPrice.new(10,5,2,3)
10X + 5Y + 2Z = 3
10 x 0 + 5 x 0 + 2 x 2 = 3 ( 4 )
10 x 0 + 5 x 1 + 2 x 0 = 3 ( 5 )
=> #<BoxPrice:0x9a07d3c @solutions=[[0, 0, 2], [0, 1, 0]], @prices=[[0, 0, 120], [0, 100, 0]], @total=[120, 100], @min=1, @best=[0, 1, 0]>
>> b.best
=> [0, 1, 0]
>> b.total
=> [120, 100]
>> b.prices[b.min]
=> [0, 100, 0]
>> b.solutions[b.min]
=> [0, 1, 0]
實在看不下去了
他隨便一個問題
就浪費各位大大這麼多時間
這麼認真地回答

已經用 Ruby, Javascript 來解
我用Excel來做

笨方法如下

1.手動列出0..9,10..19,90..99的最佳解
2.分析出共同的模式
3.寫出計算公式

笨結果如下

假設 N = 10X + 5Y + 2Z
IF N = 0 THEN
   X = Y = Z = 0
ELSE
   I = N MOD 10
   J = N DIV 10
   SELECT CASE I
      0:
	     X = J : Y = 0 :Z = 0
      1,2:
	     X = J : Y = 0 :Z = 1
      3,4,5:
	     X = J : Y = 1 :Z = 0		 
      6,7,8,9:
         X = J + 1 : Y = 0 : Z = 0
   END SELECT
END IF


只有問題有意義時, 答案才有意義
1張15單位的單子, 運費單位是150+100=250

那如果是3張5單位的單子呢
答案應該是
3*100(5單位) = 300
還是
150(10單位)+100(5單位)= 250?

總之我想講的就是
大家不要再浪費時間啦爆氣
shunyuan iT邦研究生 1 級 ‧ 2010-06-22 17:09:12 檢舉
antijava提到:
大家不要再浪費時間啦


高手。驚

偶而換換口味,溫習一下以前學校學的功課,也不錯啊。開心
chan15 iT邦新手 2 級 ‧ 2010-06-22 20:33:02 檢舉
為何會有三張5單位的單子跑出來?
每次的購物單去計算加總的運費單位啊?
jamesjan iT邦高手 1 級 ‧ 2010-06-23 13:21:45 檢舉
chan15提到:
為何會有三張5單位的單子跑出來?

如同 antijava 大所講的,題目定義不清楚
所以很多情況是沒辦法界定的

例如:
我們同一天出同一家客戶,出貨單是 by 客戶訂單來拋單
出貨地點可能 By 客戶指定廠別來出貨
倉庫在包裝時就必須有併單或分單包裝的考量
除了包裝方式外,還必須考慮 Shippment Term,運輸方式等等

所以沒有界定清楚,答案就會發散...
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
12
fillano
iT邦超人 1 級 ‧ 2010-06-22 08:26:13
最佳解答

用遞迴來解(抱歉,我用javascript來做):

&lt;pre class="c" name="code">


&lt;script>
var packages = {
	a:{unit:10,cost:150},
	b:{unit:5,cost:100},
	c:{unit:2,cost:60}
};

var res = {};

function compute(p,u,a) {
	for(var i in p) {
		a.push(i);
		if(u>p[i].unit) {
			compute(p,u-p[i].unit,a);
		} else {
			var r = JSON.stringify(a);
			r = JSON.parse(r);
			var c = 0;
			for(var j in r) {
				c += p[r[j]].cost;
			}
			if(res.total) {
				if(res.total>c) {
					res = {combo:r,total:c};
				}
			} else {
				res = {combo:r,total:c};
			}
		}
		a.pop();
	}
}
compute(packages, 15, []);
alert(JSON.stringify(res));
&lt;/script>

不過這樣窮舉組合(我這樣已經會排除掉無意義的組合了)的計算次數會隨著單位快速增長,並不適合在需要時動態計算。另外,php做遞迴的效率很差(跟javascript比較的話),所以建議你還是用迴圈展開來做,我只是做好玩的。(其實可以調整一下,在遞迴的條件中加入成本的評估,超過目前最低成本這條路徑就不繼續算下去,這樣除非剛好碰到最差狀況,否則應該可以改善計算的效率,你可以自己試試看。這樣算的時候,packages陣列的順序應該也會影響效率喔...)

看更多先前的回應...收起先前的回應...
shunyuan iT邦研究生 1 級 ‧ 2010-06-22 12:11:09 檢舉

高手。

fillano iT邦超人 1 級 ‧ 2010-06-22 12:23:38 檢舉

加上評估以後跳過許多不需要的計算,速度會快很多:

&lt;pre class="c" name="code">

&lt;script>
var packages = {
	a:{unit:10,cost:150},
	b:{unit:5,cost:100},
	c:{unit:2,cost:60}
};

var res = {};

function compute(p,u,a) {
	for(var i in p) {
		a.push(i);
		var r = JSON.stringify(a);
		r = JSON.parse(r);
		var c = 0;
		for(var j in r) {
			c += p[r[j]].cost;
		}
		if (res.total && c>res.total) break;
		if(u>p[i].unit) {
			compute(p,u-p[i].unit,a);
		} else {
			if(res.total) {
				if(res.total>c) {
					res = {combo:r,total:c};
				}
			} else {
				res = {combo:r,total:c};
			}
		}
		a.pop();
	}
}
compute(packages, 71, []);
alert(JSON.stringify(res));
&lt;/script>
fillano iT邦超人 1 級 ‧ 2010-06-22 15:22:59 檢舉

其實這個解法有個問題,就是packages裡面元素的順序會影響到計算結果以及複雜度。我應該回來看一下dynamic programming跟圖還有路徑搜尋的演算法資料...

我的想法很直觀,就是把包裝的組合攤開成樹,在建立樹的時候就計算成本,當路徑的成本在某個節點上超過目前已計算過路徑的最低成本時,就跳過這個路徑。

當unit數量大時,用越大的包裝成本越低,所以先計算大的包裝的話,就會更快找到比較低的成本路徑,這樣就可以捨棄掉更多需要計算的路徑,讓計算效率更好。所以packages中包裝方法的順序對於這個計算的方法效率會有影響。不過其實我的方法還是有問題,如果改packages中包裝方法的順序,不是只有計算效率受影響,結果還會不正確,應該要再調整一下....等我有空吧。

fillano iT邦超人 1 級 ‧ 2010-06-22 15:28:33 檢舉

另外,遞迴結束的條件(不管捨棄)是在完成包裝時,所以這是深度優先的遍歷。

fillano iT邦超人 1 級 ‧ 2010-06-22 15:34:24 檢舉

稍微計算了一下呼叫compute函數的次數來檢驗計算效率。如果有41個單位的物品要包裝,會呼叫8次。但是如果我把:

&lt;pre class="c" name="code">        if (res.total && c>res.total) break;

這一行拿掉,會呼叫21,439次。

fillano iT邦超人 1 級 ‧ 2010-06-22 16:50:07 檢舉

正確版:

&lt;pre class="c" name="code">


&lt;script>
var packages = {
	c:{unit:2,cost:60},
	b:{unit:5,cost:100},
	a:{unit:10,cost:150}
};

var res = {};

function compute(p,u,a) {
	count++;
	for(var i in p) {
		a.push(i);
		var r = JSON.stringify(a);
		r = JSON.parse(r);
		var c = 0;
		for(var j in r) {
			c += p[r[j]].cost;
		}
		if (res.total && c>res.total) {
			a.pop();
			continue;
		}
		if(u>p[i].unit) {
			compute(p,u-p[i].unit,a);
		} else {
			if(res.total) {
				if(res.total>c) {
					res = {combo:r,total:c};
				}
			} else {
				res = {combo:r,total:c};
			}
		}
		a.pop();
	}
}
compute(packages, 41, []);
alert(JSON.stringify(res));
&lt;/script>

同樣用41個單位的物品來測試,像上面的程式會呼叫compute函數3,820次,把packages裡面的包裝方法順序反過來的話,會呼叫1,975次。

海綿大,問題答到這樣也只是自娛了,所以ok啦。

fillano iT邦超人 1 級 ‧ 2010-06-22 16:59:15 檢舉

另外,建議chan15大用程式跑出lookup table,然後直接代入得出結果就好,這個問題的解決方法時間複雜度大概都在O(N^2),即時算的效率應該不好,除非你確定單位數真的都不大。

fillano iT邦超人 1 級 ‧ 2010-06-22 17:07:21 檢舉

應該說O(N^2)以上...殘念,我的方法很爛...

chan15 iT邦新手 2 級 ‧ 2010-06-25 03:52:50 檢舉
&lt;pre class="c" name="code">
/*
 * 傳出陣列(各箱子的數量、金額)
 */
function get_boxnum($num) {
    /* 在此設定相關條件
     *  一定要將num值由大至小排。要不然會出錯
     */
    $boxset = Array('A' =>  Array('num'=>10,'money'=>150),'B' =>  Array('num'=>5,'money'=>100),'C' =>  Array('num'=>2,'money'=>60));

    $t_num = $num;
    $now_type = Array();
    $last_box = '';
    foreach($boxset AS $box => $data) {
        //計算打包
        $tmp_num = floor($t_num/$data['num']);
        $now_type[$box]['num'] = $tmp_num;
        $now_type[$box]['money'] = $tmp_num*$data['money'];

        //取得剩餘數量
        $t_num = $t_num%$data['num'];
        $last_box = $box;   //最後一個箱子編號
    }
    if($t_num > 0)
    {
        $now_type[$last_box]['num']++;  //如還有剩餘量,將最後一個箱子加1
        $now_type[$last_box]['money'] += $boxset[$last_box]['money'];
    }
    return $now_type;
}
chan15 iT邦新手 2 級 ‧ 2010-06-25 03:53:36 檢舉

網友提供的寫法,我覺得還不錯,提供給大家

&lt;pre class="c" name="code">$now_type = get_boxnum(6); //直接用買6個做測試

//這裏是顯示。
foreach($now_type AS $tt => $dd) {
    echo "使用".$dd['num']."個".$tt."箱子,共".$dd['money']."元\n";
    $allmoney +=$dd['money'];
}
echo "合計".$allmoney."元";
16
shunyuan
iT邦研究生 1 級 ‧ 2010-06-20 01:43:37

應該任何一種程式語言都可以。

這是一個資料結構的最短路徑的問題,你上網 Google 一下應該會有詳細的解說跟範例。

建議找一個 c 語言的例子,然後你再改寫成 php

不過也很可能待會有那位大大,直接就給你答案,也說不定。

14
pojen
iT邦研究生 5 級 ‧ 2010-06-20 20:01:27

據我所知最有效率的做法應該是 Dyanmic programming (抱歉不清楚中文譯名)

http://www.avatar.se/molbioinfo2001/dynprog/dynamic.html

14
逮丸逮丸
iT邦大師 1 級 ‧ 2010-06-21 20:01:14

我的解法一點也不高深,
只用到數學的四則運算,
較深一點的數學就疑惑 + 暈
所以會比較繁瑣些,
幾乎像是畫「九九乘法表」的結構做出來的。

直接把程式碼貼上,
雖然是 Ruby,但還蠻好辨識在寫什麼,
再自行用其他語言來改寫;
篇幅關係,部份推想過程的說明,稍後貼在 [討論](http://ithelp.ithome.com.tw/question/10045138?tab=opini<br />
on#ooa_hash) 中:

&lt;pre class="c" name="code">class BoxPrice
  attr_reader :solutions, :every_price, :every_total, :best
  def initialize(a,b,c,d)
    @solutions = mycount(a,b,c,d)
    @prices = every_price(@solutions)
    @total = every_total(@prices)
    @min = which_min(@total)
    @best = @solutions[@min]
  end

  def maxx(a,b)
    # 以 5 * x >= 7 為例算出x 最小的 >= b 的值
    # x = 0,1,2
    # get the x Max is 2
    # here a = 5, b = 7
    int = b/a
    mod = b%a
    if mod.zero?
      return int
    else
      return int + 1
    end
  end

  def myrange(a,b)
    # 5 * x >= 7 將可能的x 列舉成陣列
    # x = 0,1,2
    # 2 is by maxx(a,b)
    arr = []
    maxx = maxx(a,b)
    0.step(maxx,1) do |x|
      arr &lt;&lt; x
    end
    return arr
  end

  def mycount(a,b,c,d)
    # 排出x, y, z 的各可能組合
    # ax + by + cz = d
    puts "#{a}X + #{b}Y + #{c}Z = #{d}"
    boxes = []
    arange = myrange(a,d)
    arange.each do |i|
      #puts "X = #{i}"
      no_a_total = d - i * a
      brange = myrange(b, no_a_total)
        brange.each do |j|
          no_b_total = no_a_total - j*b
          crange = myrange(c, no_b_total)
            cmax = maxx(c, no_b_total)
            if cmax > 0
              puts "#{a} x #{i} + #{b} x #{j} + #{c} x #{cmax} = #{d} ( #{a*i + b*j + c*cmax} )"
              boxes &lt;&lt; [i, j, cmax]
            else
              puts "#{a} x #{i} + #{b} x #{j} + #{c} x 0 = #{d} ( #{a*i + b*j} )"
              boxes &lt;&lt; [i, j, 0]
            end
        end
    end
    return boxes
  end

  def every_price(arr)
    # 將各組合換算成價錢的小計
    return arr.collect{|gr| [ gr[0] * 150, gr[1] *100, gr[2] * 60]}
  end

  def every_total(arr)
    # 各組價錢的總計
    return arr.collect {|gr| sum = 0; gr.each{|x| sum+= x}; sum}
  end

  def which_min(arr)
    # 找出各組價錢的最小值
    return arr.index arr.min
  end

end

以下是執行的結果:

&lt;pre class="c" name="code">irb -r3Box
>> b=BoxPrice.new(10,5,2,12)
10X + 5Y + 2Z = 12
10 x 0 + 5 x 0 + 2 x 6 = 12 ( 12 )
10 x 0 + 5 x 1 + 2 x 4 = 12 ( 13 )
10 x 0 + 5 x 2 + 2 x 1 = 12 ( 12 )
10 x 0 + 5 x 3 + 2 x 0 = 12 ( 15 )
10 x 1 + 5 x 0 + 2 x 1 = 12 ( 12 )
10 x 1 + 5 x 1 + 2 x 0 = 12 ( 15 )
=> #&lt;BoxPrice:0x8cef570 @solutions=[[0, 0, 6], [0, 1, 4], [0, 2, 1], [0, 3, 0], [1, 0, 1], [1, 1, 0]], @prices=[[0, 0, 360], [0, 100, 240], [0, 200, 60], [0, 300, 0], [150, 0, 60], [150, 100, 0]], @total=[360, 340, 260, 300, 210, 250], @min=4, @best=[1, 0, 1]>
>> b.best
=> [1, 0, 1]

上述可用 b.solutions, b.prices, b.total 來分別列出
各組可能解,各組解的小計價格,各組解的總價。

shunyuan iT邦研究生 1 級 ‧ 2010-06-22 00:31:41 檢舉

高手!

把解法丟到網頁上:
http://alpha.tagbible.net/zhjp/bpform
填上就可顯示出各種解法,及指出最佳解。

我要發表回答

立即登入回答