require 'Combinations'
context "Simple Combinations" do
specify "degenerate cases" do
Combinations.get(0,0).should.equal []
Combinations.get(1,0).should.equal []
Combinations.get(0,1).should.equal []
end
specify "nC1" do
Combinations.get(1,1).should.equal [[0]]
Combinations.get(2,1).should.equal [[0],[1]]
end
specify "nCn" do
Combinations.get(2,2).should.equal [[0,1]]
Combinations.get(3,3).should.equal [[0,1,2]]
end
specify "nCr" do
Combinations.get(3,2).should.equal [[0,1],[0,2],[1,2]]
Combinations.get(4,3).should.equal [[0,1,2],[0,1,3],[0,2,3],[1,2,3]]
Combinations.get(5,3).should.equal [
[0,1,2],[0,1,3],[0,1,4],[0,2,3],[0,2,4],[0,3,4],
[1,2,3],[1,2,4],[1,3,4],
[2,3,4]
]
end
end
讓函數choose(n, k)來找出來自集合(0...n)的k個元素的所有不同組合。那麼
choose(3, 0) == [[]]
choose(3, 1) == [[0], [1], [2]]
choose(3, 2) == [[0,1], [0,2], [1,2]]
等等。我們也有
choose(3, 4) == []
因爲沒辦法從僅僅三個元素中找出4種元素的不同組合。
所以,讓我們給choose(n, k)來寫一個遞歸的定義;基本的情況是
choose(0, 0) == [[]]
choose(0, k) == [] if k > 0
這覆蓋了所有n == 0 時的情況。現在讓我們看看n==3,
choose(3, 1) == [[0], [1], [2]]
choose(3, 2) == [[0,1], [0,2], [1,2]]
那當n==4時會怎樣呢?
choose(4, 2) == [[0,1], [0,2], [1,2], [0,3], [1,3], [2,3]]
酷!看起來前面一半和choose(3,2)一樣
choose(4, 2) == choose(3, 2) + [[0,3], [1,3], [2,3]]
剩下的元素與choose(3,1)再加上新元素3是一樣的
這就說明這是一個普遍的規則。從一組(n+1)個元素元素中選出不同的k個元素的所有組合的方式是:
- 從一組n個元素中選出k個元素的所有組合,再加上
- k-1箇舊元素的所有組合加上一個新的元素
assert_equal [[]], choose(3, 0)
assert_equal [], choose(0, 3)
end
# choose(1,1) == choose(0, 1) + append_all(choose(0, 0), 0)
# == [] + append_all([[]], 0)
# == [[0]]
assert_equal [[0]], choose(1, 1)
assert_equal [[0,1], [0,2], [1,2]], choose(3, 2)
assert_equal [[0,1], [0,2], [1,2], [0,3], [1,3], [2,3]], choose(4, 2)
assert_equal [[0,1,2,3]], choose(4, 4)
end
return [[]] if n == 0 && k == 0
return [] if n == 0 && k > 0
return [[]] if n > 0 && k == 0
new_element = n-1
choose(n-1, k) + append_all(choose(n-1, k-1), new_element)
end
lists.map { |l| l << element }
end
既然我們遞歸調用k-1,我們必須增加一段代碼去定義當k==0時的情況。這段代碼當然是精簡的。它也是清晰的,只要你明白了遞歸定義是如何
require 'Combinations'
Combinations.get args[:for_n_items], args[:sets_of]
end
specify "degenerate cases" do
get_combinations(:sets_of => 0, :for_n_items => 0).should.equal []
get_combinations(:sets_of => 0, :for_n_items => 1).should.equal []
get_combinations(:sets_of => 1, :for_n_items => 0).should.equal []
end
specify "nC1" do
get_combinations(:sets_of => 1, :for_n_items => 1).should.equal [[0]]
get_combinations(:sets_of => 1, :for_n_items => 2).should.equal [[0],[1]]
end
specify "nCn" do
get_combinations(:sets_of => 2, :for_n_items => 2).should.equal [[0,1]]
get_combinations(:sets_of => 3, :for_n_items => 3).should.equal [[0,1,2]]
end
specify "nCr" do
get_combinations(:sets_of => 2, :for_n_items => 3).should.equal [[0,1],[0,2],[1,2]]
get_combinations(:sets_of => 3, :for_n_items => 4).should.equal [[0,1,2],[0,1,3],[0,2,3],[1,2,3]]
get_combinations(:sets_of => 3, :for_n_items => 5).should.equal [
[0,1,2],[0,1,3],[0,1,4],[0,2,3],[0,2,4],[0,3,4],
[1,2,3],[1,2,4],[1,3,4],
[2,3,4]
]
end
Robert C. Martin的英文blog網址: http://www.butunclebob.com/ArticleS.UncleBob)
譯者注:Robert C. Martin是Object Mentor公司總裁,面向對象設計、模式、UML、敏捷方法學和極限編程領域內的資深顧問。他不僅是Jolt獲獎圖書《敏捷軟件開發:原則、模式與實踐》(中文版)(《敏捷軟件開發》(英文影印版))的作者,還是暢銷書Designing Object-Oriented C++ Applications Using the Booch Method的作者。Martin是Pattern Languages of Program Design 3和More C++ Gems的主編,並與James Newkirk合著了XP in Practice。他是國際程序員大會上著名的發言人,並在C++ Report雜誌擔任過4年的編輯。