本文目錄一覽:
- 1、如何用 Python 實現一個圖數據庫(Graph Database)?
- 2、用python求1! 2! 3! 4! 5!的程序
- 3、萬字教你如何用 Python 實現線性規劃
- 4、用python求1到100所有奇數的和
- 5、使用python編程,實現對文件夾中所有txt文件中的某一列數據都加1?
- 6、用python編寫一個程序,輸出你的姓名和學號
如何用 Python 實現一個圖數據庫(Graph Database)?
本文章是 重寫 500 Lines or Less 系列的其中一篇,目標是重寫 500 Lines or Less 系列的原有項目:Dagoba: an in-memory graph database。
Dagoba 是作者設計用來展示如何從零開始自己實現一個圖數據庫( Graph Database )。該名字似乎來源於作者喜歡的一個樂隊,另一個原因是它的前綴 DAG 也正好是有向無環圖 ( Directed Acyclic Graph ) 的縮寫。本文也沿用了該名稱。
圖是一種常見的數據結構,它將信息描述為若干獨立的節點( vertex ,為了和下文的邊更加對稱,本文中稱為 node ),以及把節點關聯起來的邊( edge )。我們熟悉的鏈表以及多種樹結構可以看作是符合特定規則的圖。圖在路徑選擇、推薦算法以及神經網絡等方面都是重要的核心數據結構。
既然圖的用途如此廣泛,一個重要的問題就是如何存儲它。如果在傳統的關係數據庫中存儲圖,很自然的做法就是為節點和邊各自創建一張表,並用外鍵把它們關聯起來。這樣的話,要查找某人所有的子女,就可以寫下類似下面的查詢:
還好,不算太複雜。但是如果要查找孫輩呢?那恐怕就要使用子查詢或者 CTE(Common Table Expression) 等特殊構造了。再往下想,曾孫輩又該怎麼查詢?孫媳婦呢?
這樣我們會意識到,SQL 作為查詢語言,它只是對二維數據表這種結構而設計的,用它去查詢圖的話非常笨拙,很快會變得極其複雜,也難以擴展。針對圖而言,我們希望有一種更為自然和直觀的查詢語法,類似這樣:
為了高效地存儲和查詢圖這種數據結構,圖數據庫( Graph Database )應運而生。因為和傳統的關係型數據庫存在極大的差異,所以它屬於新型數據庫也就是 NoSql 的一個分支(其他分支包括文檔數據庫、列數據庫等)。圖數據庫的主要代表包括 Neo4J 等。本文介紹的 Dagoba 則是具備圖數據庫核心功能、主要用於教學和演示的一個簡單的圖數據庫。
原文代碼是使用 JavaScript 編寫的,在定義調用接口時大量使用了原型( prototype )這種特有的語言構造。對於其他主流語言的用戶來說,原型的用法多少顯得有些彆扭和不自然。
考慮到本系列其他數據庫示例大多是用 Python 實現的,本文也按照傳統,用 Python 重寫了原文的代碼。同樣延續之前的慣例,為了讓讀者更好地理解程序是如何逐步完善的,我們用迭代式的方法完成程序的各個組成部分。
原文在 500lines 系列的 Github 倉庫中只包含了實現代碼,並未包含測試。按照代碼注釋說明,測試程序位於作者的另一個代碼庫中,不過和 500lines 版本的實現似乎略有不同。
本文實現的代碼參考了原作者的測試內容,但跳過了北歐神話這個例子——我承認確實不熟悉這些神祇之間的親緣關係,相信中文背景的讀者們多數也未必了解,雖然作者很喜歡這個例子,想了想還是不要徒增困惑吧。因此本文在編寫測試用例時只參考了原文關於家族親屬的例子,放棄了神話相關的部分,儘管會減少一些趣味性,相信對於入門級的代碼來說這樣也夠用了。
本文實現程序位於代碼庫的 dagoba 目錄下。按照本系列程序的同意規則,要想直接執行各個已完成的步驟,讀者可以在根目錄下的 main.py 找到相應的代碼位置,取消注釋並運行即可。
本程序的所有步驟只需要 Python3 ,測試則使用內置的 unittest , 不需要額外的第三方庫。原則上 Python3.6 以上版本應該都可運行,但我只在 Python3.8.3 環境下完整測試過。
本文實現的程序從最簡單的案例開始,通過每個步驟逐步擴展,最終形成一個完整的程序。這些步驟包括:
接下來依次介紹各個步驟。
回想一下,圖數據庫就是一些點( node )和邊( edge )的集合。現在我們要做出的一個重大決策是如何對節點/邊進行建模。對於邊來說,必須指定它的關聯關係,也就是從哪個節點指向哪個節點。大多數情況下邊是有方向的——父子關係不指明方向可是要亂套的!
考慮到擴展性及通用性問題,我們可以把數據保存為字典( dict ),這樣可以方便地添加用戶需要的任何數據。某些數據是為數據庫內部管理而保留的,為了明確區分,可以這樣約定:以下劃線開頭的特殊字段由數據庫內部維護,類似於私有成員,用戶不應該自己去修改它們。這也是 Python 社區普遍遵循的約定。
此外,節點和邊存在互相引用的關係。目前我們知道邊會引用到兩端的節點,後面還會看到,為了提高效率,節點也會引用到邊。如果僅僅在內存中維護它們的關係,那麼使用指針訪問是很直觀的,但數據庫必須考慮到序列化到磁盤的問題,這時指針就不再好用了。
為此,最好按照數據庫的一般要求,為每個節點維護一個主鍵( _id ),用主鍵來描述它們之間的關聯關係。
我們第一步要把數據庫的模型建立起來。為了測試目的,我們使用一個最簡單的數據庫模型,它只包含兩個節點和一條邊,如下所示:
按照 TDD 的原則,首先編寫測試:
與原文一樣,我們把數據庫管理接口命名為 Dagoba 。目前,能夠想到的最簡單的測試是確認節點和邊是否已經添加到數據庫中:
assert_item 是一個輔助方法,用於檢查字典是否包含預期的字段。相信大家都能想到該如何實現,這裡就不再列出了,讀者可參考 Github 上的完整源碼。
現在,測試是失敗的。用最簡單的辦法實現數據庫:
需要注意的是,不管添加節點還是查詢,程序都使用了拷貝後的數據副本,而不是直接使用原始數據。為什麼要這樣做?因為字典是可變的,用戶可以在任何時候修改其中的內容,如果數據庫不知道數據已經變化,就很容易發生難以追蹤的一致性問題,最糟糕的情況下會使得數據內容徹底混亂。
拷貝數據可以避免上述問題,代價則是需要佔用更多內存和處理時間。對於數據庫來說,通常查詢次數要遠遠多於修改,所以這個代價是可以接受的。
現在測試應該正常通過了。為了讓它更加完善,我們可以再測試一些邊緣情況,看看數據庫能否正確處理異常數據,比如:
例如,如果用戶嘗試添加重複主鍵,我們預期應拋出 ValueError 異常。因此編寫測試如下:
為了滿足以上測試,代碼需要稍作修改。特別是按照 id 查找主鍵是個常用操作,通過遍歷的方法效率太低了,最好是能夠通過主鍵直接訪問。因此在數據庫中再增加一個字典:
完整代碼請參考 Github 倉庫。
在上個步驟,我們在初始化數據庫時為節點明確指定了主鍵。按照數據庫設計的一般原則,主鍵最好是不具有業務含義的代理主鍵( Surrogate key ),用戶不應該關心它具體的值是什麼,因此讓數據庫去管理主鍵通常是更為合理的。當然,在部分場景下——比如導入外部數據——明確指定主鍵仍然是有用的。
為了同時支持這些要求,我們這樣約定:字段 _id 表示節點的主鍵,如果用戶指定了該字段,則使用用戶設置的值(當然,用戶有責任保證它們不會重複);否則,由數據庫自動為它分配一個主鍵。
如果主鍵是數據庫生成的,事先無法預知它的值是什麼,而邊( edge )必須指定它所指向的節點,因此必須在主鍵生成後才能添加。由於這個原因,在動態生成主鍵的情況下,數據庫的初始化會略微複雜一些。還是先寫一個測試:
為支持此功能,我們在數據庫中添加一個內部字段 _next_id 用於生成主鍵,並讓 add_node 方法返回新生成的主鍵:
接下來,再確認一下邊是否可以正常訪問:
運行測試,一切正常。這個步驟很輕鬆地完成了,不過兩個測試( DbModelTest 和 PrimaryKeyTest )出現了一些重複代碼,比如 get_item 。我們可以把這些公用代碼提取出來。由於 get_item 內部調用了 TestCase.assertXXX 等方法,看起來應該使用繼承,但從 TestCase 派生基類容易引起一些潛在的問題,所以我轉而使用另一個技巧 Mixin :
實現數據庫模型之後,接下來就要考慮如何查詢它了。
在設計查詢時要考慮幾個問題。對於圖的訪問來說,幾乎總是由某個節點(或符合條件的某一類節點)開始,從與它相鄰的邊跳轉到其他節點,依次類推。所以鏈式調用對查詢來說是一種很自然的風格。舉例來說,要知道 Tom 的孫子養了幾隻貓,可以使用類似這樣的查詢:
可以想象,以上每個方法都應該返回符合條件的節點集合。這種實現是很直觀的,不過存在一個潛在的問題:很多時候用戶只需要一小部分結果,如果它總是不計代價地給我們一個巨大的集合,會造成極大的浪費。比如以下查詢:
為了避免不必要的浪費,我們需要另外一種機制,也就是通常所稱的“懶式查詢”或“延遲查詢”。它的基本思想是,當我們調用查詢方法時,它只是把查詢條件記錄下來,而並不立即返回結果,直到明確調用某些方法時才真正去查詢數據庫。
如果讀者比較熟悉流行的 Python ORM,比如 SqlAlchemy 或者 Django ORM 的話,會知道它們幾乎都是懶式查詢的,要調用 list(result) 或者 result[0:10] 這樣的方法才能得到具體的查詢結果。
在 Dagoba 中把觸發查詢的方法定義為 run 。也就是說,以下查詢執行到 run 時才真正去查找數據:
和懶式查詢( Lazy Query )相對應的,直接返回結果的方法一般稱作主動查詢( Eager Query )。主動查詢和懶式查詢的內在查找邏輯基本上是相同的,區別只在於觸發機制不同。由於主動查詢實現起來更加簡單,出錯也更容易排查,因此我們先從主動查詢開始實現。
還是從測試開始。前面測試所用的簡單數據庫數據太少,難以滿足查詢要求,所以這一步先來創建一個更複雜的數據模型:
此關係的複雜之處之一在於反向關聯:如果 A 是 B 的哥哥,那麼 B 就是 A 的弟弟/妹妹,為了查詢到他們彼此之間的關係,正向關聯和反向關聯都需要存在,因此在初始化數據庫時需要定義的邊數量會很多。
當然,父子之間也存在反向關聯的問題,為了讓問題稍微簡化一些,我們目前只需要向下(子孫輩)查找,可以稍微減少一些關聯數量。
因此,我們定義數據模型如下。為了減少重複工作,我們通過 _backward 字段定義反向關聯,而數據庫內部為了查詢方便,需要把它維護成兩條邊:
然後,測試一個最簡單的查詢,比如查找某人的所有孫輩:
這裡 outcome/income 分別表示從某個節點出發、或到達它的節點集合。在原作者的代碼中把上述方法稱為 out/in 。當然這樣看起來更加簡潔,可惜的是 in 在 Python 中是個關鍵字,無法作為函數名。我也考慮過加個下劃線比如 out_.in_ 這種形式,但看起來也有點怪異,權衡之後還是使用了稍微啰嗦一點的名稱。
現在我們可以開始定義查詢接口了。在前面已經說過,我們計劃分別實現兩種查詢,包括主動查詢( Eager Query )以及延遲查詢( Lazy Query )。
它們的內在查詢邏輯是相通的,看起來似乎可以使用繼承。不過遵循 YAGNI 原則,目前先不這樣做,而是只定義兩個新類,在滿足測試的基礎上不斷擴展。以後我們會看到,與繼承相比,把共同的邏輯放到數據庫本身其實是更為合理的。
接下來實現訪問節點的方法。由於 EagerQuery 調用查詢方法會立即返回結果,我們把結果記錄在 _result 內部字段中。雖然 node 方法只返回單個結果,但考慮到其他查詢方法幾乎都是返回集合,為統一起見,讓它也返回集合,這樣可以避免同時支持集合與單結果的分支處理,讓代碼更加簡潔、不容易出錯。此外,如果查詢對象不存在的話,我們只返回空集合,並不視為一個錯誤。
查詢輸入/輸出節點的方法實現類似這樣:
查找節點的核心邏輯在數據庫本身定義:
以上使用了內部定義的一些輔助查詢方法。用類似的邏輯再定義 income ,它們的實現都很簡單,讀者可以直接參考源碼,此處不再贅述。
在此步驟的最後,我們再實現一個優化。當多次調用查詢方法後,結果可能會返回重複的數據,很多時候這是不必要的。就像關係數據庫通常支持 unique/distinct 一樣,我們也希望 Dagoba 能夠過濾重複的數據。
假設我們要查詢某人所有孩子的祖父,顯然不管有多少孩子,他們的祖父應該是同一個人。因此編寫測試如下:
現在來實現 unique 。我們只要按照主鍵把重複數據去掉即可:
在上個步驟,初始化數據庫指定了雙向關聯,但並未測試它們。因為我們還沒有編寫代碼去支持它們,現在增加一個測試,它應該是失敗的:
運行測試,的確失敗了。我們看看要如何支持它。回想一下,當從邊查找節點時,使用的是以下方法:
這裡也有一個潛在的問題:調用 self.edges 意味着遍歷所有邊,當數據庫內容較多時,這是巨大的浪費。為了提高性能,我們可以把與節點相關的邊記錄在節點本身,這樣要查找邊只要看節點本身即可。在初始化時定義出入邊的集合:
在添加邊時,我們要同時把它們對應的關係同時更新到節點,此外還要維護反向關聯。這涉及對字典內容的部分複製,先編寫一個輔助方法:
然後,將添加邊的實現修改如下:
這裡的代碼同時添加正向關聯和反向關聯。有的朋友可能會注意到代碼略有重複,是的,但是重複僅出現在該函數內部,本着“三則重構”的原則,暫時不去提取代碼。
實現之後,前面的測試就可以正常通過了。
在這個步驟中,我們來實現延遲查詢( Lazy Query )。
延遲查詢的要求是,當調用查詢方法時並不立即執行,而是推遲到調用特定方法,比如 run 時才執行整個查詢,返回結果。
延遲查詢的實現要比主動查詢複雜一些。為了實現延遲查詢,查詢方法的實現不能直接返回結果,而是記錄要執行的動作以及傳入的參數,到調用 run 時再依次執行前面記錄下來的內容。
如果你去看作者的實現,會發現他是用一個數據結構記錄執行操作和參數,此外還有一部分邏輯用來分派對每種結構要執行的動作。這樣當然是可行的,但數據處理和分派部分的實現會比較複雜,也容易出錯。
本文的實現則選擇了另外一種不同的方法:使用 Python 的內部函數機制,把一連串查詢變換成一組函數,每個函數取上個函數的執行結果作為輸入,最後一個函數的輸出就是整個查詢的結果。由於內部函數同時也是閉包,儘管每個查詢的參數形式各不相同,但是它們都可以被閉包“捕獲”而成為內部變量,所以這些內部函數可以採用統一的形式,無需再針對每種查詢設計額外的數據結構,因而執行過程得到了很大程度的簡化。
首先還是來編寫測試。 LazyQueryTest 和 EagerQueryTest 測試用例幾乎是完全相同的(是的,兩種查詢只在於內部實現機制不同,它們的調用接口幾乎是完全一致的)。
因此我們可以把 EagerQueryTest 的測試原樣不變拷貝到 LazyQueryTest 中。當然拷貝粘貼不是個好注意,對於比較冗長而固定的初始化部分,我們可以把它提取出來作為兩個測試共享的公共函數。讀者可參考代碼中的 step04_lazy_query/tests/test_lazy_query.py 部分。
程序把查詢函數的串行執行稱為管道( pipeline ),用一個變量來記錄它:
然後依次實現各個調用接口。每種接口的實現都是類似的:用內部函數執行真正的查詢邏輯,再把這個函數添加到 pipeline 調用鏈中。比如 node 的實現類似下面:
其他接口的實現也與此類似。最後, run 函數負責執行所有查詢,返回最終結果;
完成上述實現後執行測試,確保我們的實現是正確的。
在前面我們說過,延遲查詢與主動查詢相比,最大的優勢是對於許多查詢可以按需要訪問,不需要每個步驟都返回完整結果,從而提高性能,節約查詢時間。比如說,對於下面的查詢:
以上查詢的意思是從孫輩中找到一個符合條件的節點即可。對該查詢而言,主動查詢會在調用 outcome(‘son’) 時就遍歷所有節點,哪怕最後一步只需要第一個結果。而延遲查詢為了提高效率,應在找到符合條件的結果後立即停止。
目前我們尚未實現 take 方法。老規矩,先添加測試:
主動查詢的 take 實現比較簡單,我們只要從結果中返回前 n 條記錄:
延遲查詢的實現要複雜一些。為了避免不必要的查找,返回結果不應該是完整的列表( list ),而應該是個按需返回的可迭代對象,我們用內置函數 next 來依次返回前 n 個結果:
寫完後運行測試,確保它們是正確的。
從外部接口看,主動查詢和延遲查詢幾乎是完全相同的,所以用單純的數據測試很難確認後者的效率一定比前者高,用訪問時間來測試也並不可靠。為了測試效率,我們引入一個節點訪問次數的概念,如果延遲查詢效率更高的話,那麼它應該比主動查詢訪問節點的次數更少。
為此,編寫如下測試:
我們為 Dagoba 類添加一個成員來記錄總的節點訪問次數,以及兩個輔助方法,分別用於獲取和重置訪問次數:
然後瀏覽代碼,查找修改點。增加計數主要在從邊查找節點的時候,因此修改部分如下:
此外還有 income/outcome 方法,修改都很簡單,這裡就不再列出。
實現後再次運行測試。測試通過,表明延遲查詢確實在效率上優於主動查詢。
不像關係數據庫的結構那樣固定,圖的形式可以千變萬化,查詢機制也必須足夠靈活。從原理上講,所有查詢無非是從某個節點出發按照特定方向搜索,因此用 node/income/outcome 這三個方法幾乎可以組合出任意所需的查詢。
但對於複雜查詢,寫出的代碼有時會顯得較為瑣碎和冗長,對於特定領域來說,往往存在更為簡潔的名稱,例如:母親的兄弟可簡稱為舅舅。對於這些場景,如果能夠類似 DSL (領域特定語言)那樣允許用戶根據專業要求自行擴展,從而簡化查詢,方便閱讀,無疑會更為友好。
如果讀者去看原作者的實現,會發現他是用一種特殊語法 addAlias 來定義自己想要的查詢,調用方法時再進行查詢以確定要執行的內容,其接口和內部實現都是相當複雜的。
而我希望有更簡單的方法來實現這一點。所幸 Python 是一種高度動態的語言,允許在運行時向類中增加新的成員,因此做到這一點可能比預想的還要簡單。
為了驗證這一點,編寫測試如下:
無需 Dagoba 的實現做任何改動,測試就可以通過了!其實我們要做的就是動態添加一個自定義的成員函數,按照 Python 對象機制的要求,成員函數的第一個成員應該是名為 self 的參數,但這裡已經是在 UnitTest 的內部,為了和測試類本身的 self 相區分,新函數的參數增加了一個下劃線。
此外,函數應返回其所屬的對象,這是為了鏈式調用所要求的。我們看到,動態語言的靈活性使得添加新語法變得非常簡單。
到此,一個初具規模的圖數據庫就形成了。
和原文相比,本文還缺少一些內容,比如如何將數據庫序列化到磁盤。不過相信讀者都看到了,我們的數據庫內部結構基本上是簡單的原生數據結構(列表+字典),因此序列化無論用 pickle 或是 JSON 之類方法都應該是相當簡單的。有興趣的讀者可以自行完成它們。
我們的圖數據庫實現為了提高查詢性能,在節點內部存儲了邊的指針(或者說引用)。這樣做的好處是,無論數據庫有多大,從一個節點到相鄰節點的訪問是常數時間,因此數據訪問的效率非常高。
但一個潛在的問題是,如果數據庫規模非常大,已經無法整個放在內存中,或者出於安全性等原因要實現分布式訪問的話,那麼指針就無法使用了,必須要考慮其他機制來解決這個問題。分布式數據庫無論採用何種數據模型都是一個棘手的問題,在本文中我們沒有涉及。有興趣的讀者也可以考慮 500lines 系列中關於分布式和集群算法的其他一些文章。
本文的實現和系列中其他數據庫類似,採用 Python 作為實現語言,而原作者使用的是 JavaScript ,這應該和作者的背景有關。我相信對於大多數開發者來說, Python 的對象機制比 JavaScript 基於原型的語法應該是更容易閱讀和理解的。
當然,原作者的版本比本文版本在實現上其實是更為完善的,靈活性也更好。如果想要更為優雅的實現,我們可以考慮使用 Python 元編程,那樣會更接近於作者的實現,但也會讓程序的複雜性大為增加。如果讀者有興趣,不妨對照着去讀讀原作者的版本。
用python求1! 2! 3! 4! 5!的程序
以下提供兩種方法 供參考,第一種方式為自己構造求階乘的函數,第二種則直接使用了Python標準庫,代碼如下:
一、
自己構造階乘函數
from functools import reduce
def factorial(n):
l = range(1,n+1)
result = reduce(lambda x,y:x*y,l)
return result
for i in range(1,6): print(‘{}! = {}’.format(i, factorial(i)))
二、
Python標準庫
from math import factorial
for i in range(1,6):
print(“{}! = {}”.format(i,factorial(i)))
兩段程序輸出一樣,如下:
萬字教你如何用 Python 實現線性規劃
想象一下,您有一個線性方程組和不等式系統。這樣的系統通常有許多可能的解決方案。線性規劃是一組數學和計算工具,可讓您找到該系統的特定解,該解對應於某些其他線性函數的最大值或最小值。
混合整數線性規劃是 線性規劃 的擴展。它處理至少一個變量採用離散整數而不是連續值的問題。儘管乍一看混合整數問題與連續變量問題相似,但它們在靈活性和精度方面具有顯着優勢。
整數變量對於正確表示自然用整數表示的數量很重要,例如生產的飛機數量或服務的客戶數量。
一種特別重要的整數變量是 二進制變量 。它只能取 零 或 一 的值,在做出是或否的決定時很有用,例如是否應該建造工廠或者是否應該打開或關閉機器。您還可以使用它們來模擬邏輯約束。
線性規劃是一種基本的優化技術,已在科學和數學密集型領域使用了數十年。它精確、相對快速,適用於一系列實際應用。
混合整數線性規劃允許您克服線性規劃的許多限制。您可以使用分段線性函數近似非線性函數、使用半連續變量、模型邏輯約束等。它是一種計算密集型工具,但計算機硬件和軟件的進步使其每天都更加適用。
通常,當人們試圖制定和解決優化問題時,第一個問題是他們是否可以應用線性規劃或混合整數線性規劃。
以下文章說明了線性規劃和混合整數線性規劃的一些用例:
隨着計算機能力的增強、算法的改進以及更多用戶友好的軟件解決方案的出現,線性規劃,尤其是混合整數線性規劃的重要性隨着時間的推移而增加。
解決線性規劃問題的基本方法稱為,它有多種變體。另一種流行的方法是。
混合整數線性規劃問題可以通過更複雜且計算量更大的方法來解決,例如,它在幕後使用線性規劃。這種方法的一些變體是,它涉及使用 切割平面 ,以及。
有幾種適用於線性規劃和混合整數線性規劃的合適且眾所周知的 Python 工具。其中一些是開源的,而另一些是專有的。您是否需要免費或付費工具取決於問題的規模和複雜性,以及對速度和靈活性的需求。
值得一提的是,幾乎所有廣泛使用的線性規劃和混合整數線性規劃庫都是以 Fortran 或 C 或 C++ 原生和編寫的。這是因為線性規劃需要對(通常很大)矩陣進行計算密集型工作。此類庫稱為求解器。Python 工具只是求解器的包裝器。
Python 適合圍繞本機庫構建包裝器,因為它可以很好地與 C/C++ 配合使用。對於本教程,您不需要任何 C/C++(或 Fortran),但如果您想了解有關此酷功能的更多信息,請查看以下資源:
基本上,當您定義和求解模型時,您使用 Python 函數或方法調用低級庫,該庫執行實際優化工作並將解決方案返回給您的 Python 對象。
幾個免費的 Python 庫專門用於與線性或混合整數線性規劃求解器交互:
在本教程中,您將使用SciPy和PuLP來定義和解決線性規劃問題。
在本節中,您將看到線性規劃問題的兩個示例:
您將在下一節中使用 Python 來解決這兩個問題。
考慮以下線性規劃問題:
你需要找到X和Ÿ使得紅色,藍色和黃色的不平等,以及不平等X 0和ÿ 0,是滿意的。同時,您的解決方案必須對應於z的最大可能值。
您需要找到的自變量(在本例中為 x 和 y )稱為 決策變量 。要最大化或最小化的決策變量的函數(在本例中為 z) 稱為 目標函數 、 成本函數 或僅稱為 目標 。您需要滿足的 不等式 稱為 不等式約束 。您還可以在稱為 等式約束 的約束中使用方程。
這是您如何可視化問題的方法:
紅線代表的功能2 X + Ý = 20,和它上面的紅色區域示出了紅色不等式不滿足。同樣,藍線是函數 4 x + 5 y = 10,藍色區域被禁止,因為它違反了藍色不等式。黃線是 x + 2 y = 2,其下方的黃色區域是黃色不等式無效的地方。
如果您忽略紅色、藍色和黃色區域,則僅保留灰色區域。灰色區域的每個點都滿足所有約束,是問題的潛在解決方案。該區域稱為 可行域 ,其點為 可行解 。在這種情況下,有無數可行的解決方案。
您想最大化z。對應於最大z的可行解是 最優解 。如果您嘗試最小化目標函數,那麼最佳解決方案將對應於其可行的最小值。
請注意,z是線性的。你可以把它想象成一個三維空間中的平面。這就是為什麼最優解必須在可行區域的 頂點 或角上的原因。在這種情況下,最佳解決方案是紅線和藍線相交的點,稍後您將看到。
有時,可行區域的整個邊緣,甚至整個區域,都可以對應相同的z值。在這種情況下,您有許多最佳解決方案。
您現在已準備好使用綠色顯示的附加等式約束來擴展問題:
方程式 x + 5 y = 15,以綠色書寫,是新的。這是一個等式約束。您可以通過向上一張圖像添加相應的綠線來將其可視化:
現在的解決方案必須滿足綠色等式,因此可行區域不再是整個灰色區域。它是綠線從與藍線的交點到與紅線的交點穿過灰色區域的部分。後一點是解決方案。
如果插入x的所有值都必須是整數的要求,那麼就會得到一個混合整數線性規劃問題,可行解的集合又會發生變化:
您不再有綠線,只有沿線的x值為整數的點。可行解是灰色背景上的綠點,此時最優解離紅線最近。
這三個例子說明了 可行的線性規劃問題 ,因為它們具有有界可行區域和有限解。
如果沒有解,線性規劃問題是 不可行的 。當沒有解決方案可以同時滿足所有約束時,通常會發生這種情況。
例如,考慮如果添加約束x + y 1會發生什麼。那麼至少有一個決策變量(x或y)必須是負數。這與給定的約束x 0 和y 0相衝突。這樣的系統沒有可行的解決方案,因此稱為不可行的。
另一個示例是添加與綠線平行的第二個等式約束。這兩行沒有共同點,因此不會有滿足這兩個約束的解決方案。
一個線性規劃問題是 無界的 ,如果它的可行區域是無界,將溶液不是有限。這意味着您的變量中至少有一個不受約束,可以達到正無窮大或負無窮大,從而使目標也無限大。
例如,假設您採用上面的初始問題並刪除紅色和黃色約束。從問題中刪除約束稱為 放鬆 問題。在這種情況下,x和y不會在正側有界。您可以將它們增加到正無窮大,從而產生無限大的z值。
在前面的部分中,您研究了一個與任何實際應用程序無關的抽象線性規劃問題。在本小節中,您將找到與製造業資源分配相關的更具體和實用的優化問題。
假設一家工廠生產四種不同的產品,第一種產品的日產量為x ₁,第二種產品的產量為x 2,依此類推。目標是確定每種產品的利潤最大化日產量,同時牢記以下條件:
數學模型可以這樣定義:
目標函數(利潤)在條件 1 中定義。人力約束遵循條件 2。對原材料 A 和 B 的約束可以從條件 3 和條件 4 中通過對每種產品的原材料需求求和得出。
最後,產品數量不能為負,因此所有決策變量必須大於或等於零。
與前面的示例不同,您無法方便地將其可視化,因為它有四個決策變量。但是,無論問題的維度如何,原理都是相同的。
在本教程中,您將使用兩個Python 包來解決上述線性規劃問題:
SciPy 設置起來很簡單。安裝後,您將擁有開始所需的一切。它的子包 scipy.optimize 可用於線性和非線性優化。
PuLP 允許您選擇求解器並以更自然的方式表述問題。PuLP 使用的默認求解器是COIN-OR Branch and Cut Solver (CBC)。它連接到用於線性鬆弛的COIN-OR 線性規劃求解器 (CLP)和用於切割生成的COIN-OR 切割生成器庫 (CGL)。
另一個偉大的開源求解器是GNU 線性規劃工具包 (GLPK)。一些著名且非常強大的商業和專有解決方案是Gurobi、CPLEX和XPRESS。
除了在定義問題時提供靈活性和運行各種求解器的能力外,PuLP 使用起來不如 Pyomo 或 CVXOPT 等替代方案複雜,後者需要更多的時間和精力來掌握。
要學習本教程,您需要安裝 SciPy 和 PuLP。下面的示例使用 SciPy 1.4.1 版和 PuLP 2.1 版。
您可以使用pip以下方法安裝兩者:
您可能需要運行pulptest或sudo pulptest啟用 PuLP 的默認求解器,尤其是在您使用 Linux 或 Mac 時:
或者,您可以下載、安裝和使用 GLPK。它是免費和開源的,適用於 Windows、MacOS 和 Linux。在本教程的後面部分,您將看到如何將 GLPK(除了 CBC)與 PuLP 一起使用。
在 Windows 上,您可以下載檔案並運行安裝文件。
在 MacOS 上,您可以使用 Homebrew:
在 Debian 和 Ubuntu 上,使用apt來安裝glpk和glpk-utils:
在Fedora,使用dnf具有glpk-utils:
您可能還會發現conda對安裝 GLPK 很有用:
安裝完成後,可以查看GLPK的版本:
有關詳細信息,請參閱 GLPK 關於使用Windows 可執行文件和Linux 軟件包進行安裝的教程。
在本節中,您將學習如何使用 SciPy優化和求根庫進行線性規劃。
要使用 SciPy 定義和解決優化問題,您需要導入scipy.optimize.linprog():
現在您已經linprog()導入,您可以開始優化。
讓我們首先解決上面的線性規劃問題:
linprog()僅解決最小化(而非最大化)問題,並且不允許具有大於或等於符號 ( ) 的不等式約束。要解決這些問題,您需要在開始優化之前修改您的問題:
引入這些更改後,您將獲得一個新系統:
該系統與原始系統等效,並且將具有相同的解決方案。應用這些更改的唯一原因是克服 SciPy 與問題表述相關的局限性。
下一步是定義輸入值:
您將上述系統中的值放入適當的列表、元組或NumPy 數組中:
注意:請注意行和列的順序!
約束左側和右側的行順序必須相同。每一行代表一個約束。
來自目標函數和約束左側的係數的順序必須匹配。每列對應一個決策變量。
下一步是以與係數相同的順序定義每個變量的界限。在這種情況下,它們都在零和正無窮大之間:
此語句是多餘的,因為linprog()默認情況下採用這些邊界(零到正無窮大)。
註:相反的float(“inf”),你可以使用math.inf,numpy.inf或scipy.inf。
最後,是時候優化和解決您感興趣的問題了。你可以這樣做linprog():
參數c是指來自目標函數的係數。A_ub和b_ub分別與不等式約束左邊和右邊的係數有關。同樣,A_eq並b_eq參考等式約束。您可以使用bounds提供決策變量的下限和上限。
您可以使用該參數method來定義要使用的線性規劃方法。有以下三種選擇:
linprog() 返回具有以下屬性的數據結構:
您可以分別訪問這些值:
這就是您獲得優化結果的方式。您還可以以圖形方式顯示它們:
如前所述,線性規劃問題的最優解位於可行區域的頂點。在這種情況下,可行區域只是藍線和紅線之間的綠線部分。最優解是代表綠線和紅線交點的綠色方塊。
如果要排除相等(綠色)約束,只需刪除參數A_eq並b_eq從linprog()調用中刪除:
解決方案與前一種情況不同。你可以在圖表上看到:
在這個例子中,最優解是紅色和藍色約束相交的可行(灰色)區域的紫色頂點。其他頂點,如黃色頂點,具有更高的目標函數值。
您可以使用 SciPy 來解決前面部分所述的資源分配問題:
和前面的例子一樣,你需要從上面的問題中提取必要的向量和矩陣,將它們作為參數傳遞給.linprog(),然後得到結果:
結果告訴您最大利潤是1900並且對應於x ₁ = 5 和x ₃ = 45。在給定條件下生產第二和第四個產品是沒有利潤的。您可以在這裡得出幾個有趣的結論:
opt.statusis0和opt.successis True,說明優化問題成功求解,最優可行解。
SciPy 的線性規劃功能主要用於較小的問題。對於更大和更複雜的問題,您可能會發現其他庫更適合,原因如下:
幸運的是,Python 生態系統為線性編程提供了幾種替代解決方案,這些解決方案對於更大的問題非常有用。其中之一是 PuLP,您將在下一節中看到它的實際應用。
PuLP 具有比 SciPy 更方便的線性編程 API。您不必在數學上修改您的問題或使用向量和矩陣。一切都更乾淨,更不容易出錯。
像往常一樣,您首先導入您需要的內容:
現在您已經導入了 PuLP,您可以解決您的問題。
您現在將使用 PuLP 解決此系統:
第一步是初始化一個實例LpProblem來表示你的模型:
您可以使用該sense參數來選擇是執行最小化(LpMinimize或1,這是默認值)還是最大化(LpMaximize或-1)。這個選擇會影響你的問題的結果。
一旦有了模型,就可以將決策變量定義為LpVariable類的實例:
您需要提供下限,lowBound=0因為默認值為負無窮大。該參數upBound定義了上限,但您可以在此處省略它,因為它默認為正無窮大。
可選參數cat定義決策變量的類別。如果您使用的是連續變量,則可以使用默認值”Continuous”。
您可以使用變量x和y創建表示線性表達式和約束的其他 PuLP 對象:
當您將決策變量與標量相乘或構建多個決策變量的線性組合時,您會得到一個pulp.LpAffineExpression代表線性表達式的實例。
注意:您可以增加或減少變量或表達式,你可以乘他們常數,因為紙漿類實現一些Python的特殊方法,即模擬數字類型一樣__add__(),__sub__()和__mul__()。這些方法用於像定製運營商的行為+,-和*。
類似地,您可以將線性表達式、變量和標量與運算符 ==、=以獲取表示模型線性約束的紙漿.LpConstraint實例。
註:也有可能與豐富的比較方法來構建的約束.__eq__(),.__le__()以及.__ge__()定義了運營商的行為==,=。
考慮到這一點,下一步是創建約束和目標函數並將它們分配給您的模型。您不需要創建列表或矩陣。只需編寫 Python 表達式並使用+=運算符將它們附加到模型中:
在上面的代碼中,您定義了包含約束及其名稱的元組。LpProblem允許您通過將約束指定為元組來向模型添加約束。第一個元素是一個LpConstraint實例。第二個元素是該約束的可讀名稱。
設置目標函數非常相似:
或者,您可以使用更短的符號:
現在您已經添加了目標函數並定義了模型。
注意:您可以使用運算符將 約束或目標附加到模型中,+=因為它的類LpProblem實現了特殊方法.__iadd__(),該方法用於指定 的行為+=。
對於較大的問題,lpSum()與列表或其他序列一起使用通常比重複+運算符更方便。例如,您可以使用以下語句將目標函數添加到模型中:
它產生與前一條語句相同的結果。
您現在可以看到此模型的完整定義:
模型的字符串表示包含所有相關數據:變量、約束、目標及其名稱。
注意:字符串表示是通過定義特殊方法構建的.__repr__()。有關 的更多詳細信息.__repr__(),請查看Pythonic OOP 字符串轉換:__repr__vs__str__ .
最後,您已準備好解決問題。你可以通過調用.solve()你的模型對象來做到這一點。如果要使用默認求解器 (CBC),則不需要傳遞任何參數:
.solve()調用底層求解器,修改model對象,並返回解決方案的整數狀態,1如果找到了最優解。有關其餘狀態代碼,請參閱LpStatus[]。
你可以得到優化結果作為 的屬性model。該函數value()和相應的方法.value()返回屬性的實際值:
model.objective持有目標函數model.constraints的值,包含鬆弛變量的值,以及對象x和y具有決策變量的最優值。model.variables()返回一個包含決策變量的列表:
如您所見,此列表包含使用 的構造函數創建的確切對象LpVariable。
結果與您使用 SciPy 獲得的結果大致相同。
注意:注意這個方法.solve()——它會改變對象的狀態,x並且y!
您可以通過調用查看使用了哪個求解器.solver:
輸出通知您求解器是 CBC。您沒有指定求解器,因此 PuLP 調用了默認求解器。
如果要運行不同的求解器,則可以將其指定為 的參數.solve()。例如,如果您想使用 GLPK 並且已經安裝了它,那麼您可以solver=GLPK(msg=False)在最後一行使用。請記住,您還需要導入它:
現在你已經導入了 GLPK,你可以在裡面使用它.solve():
該msg參數用於顯示來自求解器的信息。msg=False禁用顯示此信息。如果要包含信息,則只需省略msg或設置msg=True。
您的模型已定義並求解,因此您可以按照與前一種情況相同的方式檢查結果:
使用 GLPK 得到的結果與使用 SciPy 和 CBC 得到的結果幾乎相同。
一起來看看這次用的是哪個求解器:
正如您在上面用突出顯示的語句定義的那樣model.solve(solver=GLPK(msg=False)),求解器是 GLPK。
您還可以使用 PuLP 來解決混合整數線性規劃問題。要定義整數或二進制變量,只需傳遞cat=”Integer”或cat=”Binary”到LpVariable。其他一切都保持不變:
在本例中,您有一個整數變量並獲得與之前不同的結果:
Nowx是一個整數,如模型中所指定。(從技術上講,它保存一個小數點後為零的浮點值。)這一事實改變了整個解決方案。讓我們在圖表上展示這一點:
如您所見,最佳解決方案是灰色背景上最右邊的綠點。這是兩者的最大價值的可行的解決方案x和y,給它的最大目標函數值。
GLPK 也能夠解決此類問題。
現在你可以使用 PuLP 來解決上面的資源分配問題:
定義和解決問題的方法與前面的示例相同:
在這種情況下,您使用字典 x來存儲所有決策變量。這種方法很方便,因為字典可以將決策變量的名稱或索引存儲為鍵,將相應的LpVariable對象存儲為值。列表或元組的LpVariable實例可以是有用的。
上面的代碼產生以下結果:
如您所見,該解決方案與使用 SciPy 獲得的解決方案一致。最有利可圖的解決方案是每天生產5.0第一件產品和45.0第三件產品。
讓我們把這個問題變得更複雜和有趣。假設由於機器問題,工廠無法同時生產第一種和第三種產品。在這種情況下,最有利可圖的解決方案是什麼?
現在您有另一個邏輯約束:如果x ₁ 為正數,則x ₃ 必須為零,反之亦然。這是二元決策變量非常有用的地方。您將使用兩個二元決策變量y ₁ 和y ₃,它們將表示是否生成了第一個或第三個產品:
除了突出顯示的行之外,代碼與前面的示例非常相似。以下是差異:
這是解決方案:
事實證明,最佳方法是排除第一種產品而只生產第三種產品。
就像有許多資源可以幫助您學習線性規劃和混合整數線性規劃一樣,還有許多具有 Python 包裝器的求解器可用。這是部分列表:
其中一些庫,如 Gurobi,包括他們自己的 Python 包裝器。其他人使用外部包裝器。例如,您看到可以使用 PuLP 訪問 CBC 和 GLPK。
您現在知道什麼是線性規劃以及如何使用 Python 解決線性規劃問題。您還了解到 Python 線性編程庫只是本機求解器的包裝器。當求解器完成其工作時,包裝器返回解決方案狀態、決策變量值、鬆弛變量、目標函數等。
用python求1到100所有奇數的和
第一種
def Sum():
num=0
x=1
while x=100:
if x%2==1:#判斷為奇數時相加
num+=x
x+=1
print(‘1—100奇數的和為:’,num)
if __name__==”__main__”:
Sum()
第二種
def Sum():
x=1
arr=[]#定義一個數組用來存儲奇數
while x=100:
if x%2==1:
arr.append(x)#如果為奇數便把它存入數組中
x+=1
print(‘1—100奇數的和為:’,sum(arr))
#最後用python的sum函數直接把列表相加
if __name__==”__main__”:
Sum()
希望可以幫助到你
使用python編程,實現對文件夾中所有txt文件中的某一列數據都加1?
import os
path = r’C:\Users\shinelon\Desktop\新建文件夾’ # 替換你的文件夾
path_result = path+”\結果”
listdir = os.listdir(path)
try:
os.mkdir(path_result)
except FileExistsError:
pass
except:
print(‘已經改寫,若重改請刪除結果文件夾’)
for f_name in listdir:
path_filename = path+”\\”+f_name
print(path_filename)
with open(path_filename) as txt:
for i in txt.readlines():
a = i.split(‘,’)
b = a[2].split(‘.’)
c = str(int(b[0])+1) +’.’+ b[1]
d = a[0] + ‘,’ + a[1] + ‘,’ + c
with open(path_result+’\\’+f_name,’a’) as txt_result:
txt_result.write(d)
os.startfile(path_result)
用python編寫一個程序,輸出你的姓名和學號
用python編寫一個程序,輸出你的姓名和學號的方法。
如下參考:
1.輸入命令提示符,輸入python並按Enter進入python交互模式。
2.輸入name=input()後按回車,如下所示。
3.輸入名字“mymy”,然後回車。此時,變量將“name”回答為“mymy”。
4.輸入名稱後返回,可以看到變量名的內容是:mymy。
5.輸入打印(姓名)並回車。輸出變量名是mymy。
6.輸入print(‘hello’,name),然後回車。輸出是:hellomymy。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/238774.html