裝滿的硬碟中是 1 多還 0 多?
import struct, os
filename, count_one, count_zero = "example.txt", 0, 0
for current_byte in list(open(filename,"rb").read()):
count_one += bin(struct.unpack("B",current_byte)[0]).count("1")
count_zero = os.path.getsize(filename) * 8 - count_one
print "One: " + str(count_one) + " times
Zero: " + str(count_zero) + " times
One/Zero: " + str(float(count_one)/float(count_zero))
我也寫了個程序來統計1和0出現的數量,Python版,6行實現。
一次全載入內存,空間換時間,畢竟固態硬碟再強,隨機讀寫性能也比不過內存,也省得分塊讀取了了。
程序GitHub地址:Python-Binary-Statistics/statistics.py at master · lincanbin/Python-Binary-Statistics · GitHub
以英文版聖經為例:
One: 13937005 times
Zero: 17550939 times
One/Zero: 0.794088851884
[Finished in 2.7s]
Python英文官方文檔5. Built-in Types:
One: 327325 times
Zero: 396891 times
One/Zero: 0.82472265685
[Finished in 0.2s]
美國國寶Justin Bieber知名歌曲《Baby》的歌詞
One: 7090 times
Zero: 9134 times
One/Zero: 0.776220713817
[Finished in 0.1s]
大家可以看到1與0的比值是一直在0.8附近波動的,這是因為英語字母是以ASCII碼在計算機中保存的,大寫字母的ASCII碼範圍是從01000001到01011010,小寫字母則是從01100001到01111010。ASCII碼都是以0開頭,並且英語文章中中字母並不是等概率出現。根據對大部分文章的統計,可以得到英文字母使用概率表。
英文字母使用頻率表:(%)
A 8.19 B 1.47 C 3.83 D 3.91 E 12.25 F 2.26 G 1.71
H 4.57 I 7.10 J 0.14 K 0.41 L 3.77 M 3.34 N 7.06
O 7.26 P 2.89 Q 0.09 R 6.85 S 6.36 T 9.41
U 2.58 V 1.09 W 1.59 X 0.21 Y 1.58 Z 0.08
可以看到概率是各不相同的,不過上表缺少了空格以及標點。用這些概率乘上對應 ASCII碼的 1與0的比 並累加就可以得到這個結果,應該是0.8附近(我其實沒算……從實際測試來看應該是這個數值)
然後是中文,UTF-8編碼24位表示一個漢字史詩巨作《斗破蒼穹》UTF-8版:
One: 70866992 times
Zero: 65263776 times
One/Zero: 1.0858549159
[Finished in 10.8s]
經常寫正則表達式的朋友肯定知道UTF-8中漢字對應的區塊是:
[u4e00-u9fa5]
換成二進位也就是100111000000000 - 1001111110100101。
第一個字是: 11100100 10111000 10000000 (一)
最後一個字是: 11101001 10111110 10100101 (龥)
漢字數量比較多,各個漢字的權重實際上影響不是特別大。因為UTF-8漢字編碼前4位必然是1110,並且漢字二進位編碼範圍如上所述。所以實際中,UTF-8漢語文字里 1與0的比 會略高於1,根據我自己的語料庫來看,在1.10左右。
GBK正則表達式匹配則是這樣寫的:
[x80-xFF]
但是這個範圍還包含了全形標點,同理,這裡同樣給出參考:
史詩巨作《斗破蒼穹》GBK版:
One: 50675085 times
Zero: 42257995 times
One/Zero: 1.19918337347
[Finished in 7.5s]
至於EXE,因為編譯出來的結果常常會有大片的、連續為零的冗餘段,實際有不少exe一零比會遠遠低於1,例如世界上最好的語言的解釋器的前端:
One: 200601 times
Zero: 409703 times
One/Zero: 0.489625411579
[Finished in 0.1s]
這是java.exe
One: 595247 times
Zero: 931601 times
One/Zero: 0.638950580774
[Finished in 0.2s]
EXE這個因程序而異,實際各個程序間區別很大。
剩下的例如JPG、MP3、RAR等等帶壓縮文件的1 0則是接近等概率分布。因為它們是壓縮文件,二元信源中信息熵:
圖像為倒U型,當P=0.5時取得信息熵H(X)最大。而信息熵越大,表示佔用二進位位越長,因此就可以表達更多符號。數據壓縮正是基於這一點,當這個文件趨近於無法再壓縮時,信息熵趨近於1。
因為目前的壓縮演算法都比較優秀了,所以這些帶壓縮文件的 1和0的比值 都是趨近於1的。
例如這個 Fate/Zero 的字幕文件的壓縮包:(壓縮一般來說還是需要比較大的文件才有明顯的效果,這個文件尺寸算是中等,可以作為參考)
One: 1820004 times
Zero: 1805588 times
One/Zero: 1.00798410269
[Finished in 0.5s]
上文中的php.exe壓縮後的php.rar:
One: 134836 times
Zero: 134892 times
One/Zero: 0.999584853068
[Finished in 0.2s]
但是我不想跑整個硬碟,太傷,跑壞了我價值連城的固態硬碟你們賠我?反正你的硬碟內容肯定跟我不一樣,我這裡大片大片One/Zero = 0.8的項目代碼,還有大片大片的 One/Zero ≈ 1.0的 MP3、小本子、片子。總體來說大部分文件10還是趨向於等概率分布,因為文件壓縮技術隨著計算機性能的提升已經得到了廣泛的推廣,但是實際比值多少還是跟硬碟文件比例有關,因人而異,要看你硬碟里主要文件類型是什麼。
如今的普通硬碟早就到了 TB 級別了,為了裝滿這麼大的硬碟只能用視頻數據了。
普通數據很難搞到那麼大,如果用垃圾數據或者某些數據不斷複製,則結果是不準確的。
那麼就看看視頻數據吧,我隨便找了三段長度不等的視頻做了下統計
One: 1035228194 Zero: 1045177454
One: 2579804653 Zero: 2578664979
One: 6665438504 Zero: 6664059608
第一段里0略多,後兩段 1 略多,初步結論就是基本差不多,具體哪個多看運氣。
數bit這事太燒CPU,我筆記本都發燙了,不想測試更多了。佔位,等我下班後,寫個程序把硬碟里常見的文件統計一下。
************************統計好了,數據如下******************************
說明:「exe:」表示的是文件類型,第一列數據為0的個數,第二列為1的個數,第三列為第一列除於第二列。
exe:
1149123 1054277 1.09
6571605 5552563 1.18
44737161 45092287 0.99
57703193 57037807 1.01
108412288 111505224 0.97
15932380 10150956 1.57
3111549 2053259 1.52
71287901 42600427 1.67
9922542 8222746 1.21
平均: 1.25
ZIP:
67167877 67228675 1.00
69622344 69775072 1.00
1742074 1775246 0.98
3345837 3419995 0.97
平均: 0.99
RAR:
40896 41632 0.98
44737161 45092287 0.99
7807134 8029922 0.97
平均: 0.98
txt:
5645417 4473391 1.26
3596 3708 0.97
6410 4926 1.30
8102 10706 0.76
237606 216754 1.10
平均: 1.08
DOC;
181037 23771 7.62
146301 21643 6.76
145512 22432 6.49
176270 24442 7.21
平均: 7.02
FLV:
208631914 205771694 1.01
236499013 233941339 1.01
157016899 155390749 1.01
平均: 1.01
WMV:
106388387 94036853 1.13
218159601 208024183 1.05
28957809 26673591 1.09
平均 : 1.09
mp4:
71351079 78779241 0.91
png:
83676 79188 1.06
90195 81989 1.10
平均: 1.08
jpg:
543701 608003 0.89
93463 91545 1.02
405362 430198 0.94
209486 198002 1.06
平均: 0.98
torrent:
1548459 1567445 0.98
373879 371241 1.01
平均: 1.00
總結:經過統計,樣本數據最不穩定的是exe文件。比例最大的是doc文件。
************************我是分割線*******************************
本程序使用的是以下代碼掃描出來的,因為程序的演算法是最笨的那種,速度非常慢,無法統計大文件,所以採集的樣本都很小。希望哪位大俠能優化一下演算法,我想去統計一下4G大小的文件。
************************demo.c*******************
/*
本程序使用lccwin32編譯,如果要用vc,請把long long 類型替換成int64 , 把「%lld」替換成"%l64d" 。
*/
#include &
#include &
#include &
//函數返回值
typedef enum bool{
OK,ERR
}BOOL;
//用來保存文件的統計數據
typedef struct _a_file{
FILE* fd;
long long zero;
long long one;
}AF;
//掃描一個位元組,返回1的個數
int scanChar( char ch ){
int i;
int count = 0;
for( i = 0 ; i &< 8 ; i ++ ){
if( ch 1 ){
count ++;
}
ch = ch &>&> 1;
}
return count;
}
//打開一個文件
BOOL openFile( AF* af , char* filename ){
af-&>fd = fopen( filename , "rb" );
if( af-&>fd == NULL ) {
perror( "open file");
return ERR;
}
af-&>zero = 0 ;
af-&>one = 0;
return OK;
}
//關閉一個文件,清空數據
void closeFile( AF* af ){
if( af-&>fd ){
fclose( af-&>fd ) ;
}
af-&>zero = 0;
af-&>one = 0;
}
//掃描一個文件,並統計0和1的數量
void scanFile( AF* af ) {
char temp;
int count ;
while( !feof( af-&>fd ) ){
fread( temp , sizeof( temp ) , 1 , af-&>fd ) ;
count = scanChar( temp ) ;
af-&>zero += 8 - count ;
af-&>one += count ;
}
}
//主函數
int main( int argc , char** argv ){
char* filename;
AF af;
if( argc != 2 ) {
printf( "輸入參數錯誤
" ) ;
return 1;
}
filename = argv[1];
if( openFile( af , filename ) == ERR ){
return 1;
}
scanFile( af ) ;
printf( "zero = %lld , one = %lld
" , af.zero , af.one ) ;
closeFile( af );
return 0;}
我的硬碟, 500G, 還有100G沒有用。
bit count: 4000862896128, 1 bit count: 1344538814857, total/1 bit count: 2.98
0大概是1的2倍,總共跑了 1:56:51.87,差點2小時。
#define _GNU_SOURCE
#include &
#include &
#include &
#include &
#include &
#include &
int count_one(char* buffer, int size) {
unsigned int count = 0;
for (int j = 0; j &< size; ++ j) {
char ch = buffer[j];
for(int i = 0 ; i &< 8 ; i ++ ){ // one char is 8 bits
if( ch 1 ) {
count ++;
}
ch = ch &>&> 1;
}
}
return count;
}
int main(int argc, char** argv) {
char *file = "/dev/sda";
// int fd = open(file, O_RDONLY | O_DIRECT, 0);
int fd = open(file, O_RDONLY, 0);
if (fd &< 0) {
perror(file);
exit(1);
}
const int length = 1048576; // 1M
char buffer[length];
ssize_t r = 0;
int i = 0;
unsigned long long total = 0;
unsigned long long one_count = 0;
while((r = read(fd, buffer, length)) &> 0 ) {
total += r;
one_count += count_one(buffer, r);
if (i % 200 == 0) {
printf("%dM, total: %lu, 1: %lu, 0/1: %.2f
", i, total * 8, one_count, (double)(total * 8 - one_count) / one_count);
}
i++;
}
printf("%dM, total: %lu, 1: %lu, 0/1: %.2f
", i, total * 8, one_count, (double)(total * 8 - one_count) / one_count);
return 0;
}
linux編譯並運行:
gcc -O3 /tmp/count.c -std=c99 sudo ./a.out
我認為0多。
因為
1、很多應用程序內有大量000000000000000000000000冗餘代碼段(記得dos時代不改變exe大小的病毒嗎)
2、英文文本(包括文本文件和資源內的英文文本)的每個位元組的最高位都是0(utf-8或者雙位元組編碼,unicode文本現在很少見了)
看來上面的答案很多都忽視了「用戶數據」和「寫入硬碟的數據」的區別,我來補充一點吧。
硬碟並不是將所有電腦送給他的數據都原封不動的存到磁軌上面的。這兩種信號之間有個專門的轉換器,叫Channel. Channel的供應商主要是 LSI( Avago子公司 ) 和 Mavell.
Channel的主要工作就是為了數據的穩定性和正確性。它會在每段數據間加上奇偶校驗碼之類的東西來輔助糾錯;會在長長的一串0中加入1來保證數據的穩定,也會在一串長長的1中加入0來增強穩定性。為什麼呢,因為隨著存儲密度的提高,過強的磁疇會導致周邊和它反向的弱磁疇翻轉,這樣就出錯了。
具體到如何糾錯和如何encoding,這就可以在研究生階段開設一個課程了...
---20150923補充--
這是本人在知乎上面的處女答,答完之後半年沒管,發現居然這麼多支持.而且還被大學的室友發現...受寵若驚,正好手頭有些材料,繼續補完.
上圖是鄙人自己畫的,Channel在機械硬碟裡面的作用大致流程圖,有錯誤請糾正.Channel encoding 做了些什麼:
- 添加同步信號.使用PLL鎖相環來控制因為馬達的輕微不穩定所導致的數據讀取失敗
- 調整信號.確保每個即將寫在磁碟上面的信號至少包含一個磁力翻轉.
- 防止出錯.局部響應最大擬然PRML, 名字這麼複雜,其實就是防止出現過長的磁疇.
Error Correction encoding 又做了些什麼:
- FM, frequency modulation. 在每一個"1"或者"0"後面都加上一個"1".
- MFM, Modified frequency modulation. "1"變成"01","0"變成"x0". x在這裡的意思是前一位相反. 比如 "1100011"在用了MFM之後,變成"01 01 00 10 10 01 01".
因為以上兩個方法太蠢,會浪費掉50%的空間(code rate=1/2),所以早就不用了.
下面這個方法可以讓code rate變成2/3.
後來又加上了奇偶校驗,以及越來越多酷炫狂拽的技能, 貌似最新的已經超過16/17了.我想關於磁碟上面的磁疇,到底是朝哪個方向的多,這不是一個能解答的問題. 何況我們還沒考慮是NRZ還是NRZI.就好像我們無法數清沙灘上面的沙子; 電視機雪花屏的時候,黑點白點哪個多一樣.
-以上-
統計了一下我們 Android 項目里的 Java 代碼,一共 4069461 個 1, 6239691 個 0。
比例為 0.65 : 1
for i in `find . -name "*.java"`; do cat $i; done | xxd -b -c 1 | awk "{print $2}" | tr "
" " " | sed "s/ //g;s/0//g" | wc -c
4069461
for i in `find . -name "*.java"`; do cat $i; done | xxd -b -c 1 | awk "{print $2}" | tr "
" " " | sed "s/ //g;s/1//g" | wc -c
6239691
同樣方法統計了:
項目的 XML 代碼:1969047 個 1,2807977 個 0,比例 0.70 : 1
項目的 PNG 圖片:11023111 個 1,11665961 個 0,比例 0.94:1
代碼里 1 和 0 的比例小於普通文本的 0.8 應該是縮進用的空格導致的。因為空格的 ASCII 碼是 32,二進位表示只有 1 個 1。
順便統計了下硬碟的前1M數據,1125829 個 1,6874171 個 0:sudo head -c 1000000 /dev/sda | xxd -b -c 1 | awk "{print $2}" | tr "
" " " | sed "s/ //g;s/0//g" | wc -c
1125829
sudo head -c 1000000 /dev/sda | xxd -b -c 1 | awk "{print $2}" | tr "
" " " | sed "s/ //g;s/1//g" | wc -c
6874171
0 比 1 多太多了(磁碟空間都空著呢)。
泛泛地討論這個問題沒有意義:一個文字編輯的書庫硬碟和一個音樂工作者mp3庫的硬碟中0和1的分布肯定是不同的。討論各種文件比較有意義:
總體來說:壓縮文件中0和1的數量更為接近——這是為了以更少的容量表示更多信息,而非壓縮文件中的0和1的個數取決於其所用字符集的編碼情況。
舉例,假定某種文件只有四個字元WXYZ。我們來看:
編碼方式甲:W(0001), X(0010), Y(0100), Z(1000)
編碼方式乙:W(1110), X(1101), Y(1011), Z(0111)
就和扔硬幣幾億次後正面背面那個多一樣
還以為你問的是我硬碟里的GV...
從資訊理論的角度來看,當0與1等概分布時,香農熵最大。換成人話就是,當0與1的個數相等時,用同等大的空間,可以存儲更多的信息資源。所以,如果0與1的個數不同,那就說明數據壓縮的不好,也可以說,如果用最優的方式來儲存滿信息,那麼0與1的個數應該是相等的。
先貼代碼,明天再統計我的2T硬碟,簡單的統計了一下一個3GB的文件夾,數據如下:
總計 文件總數:82 長度:288618291 0bit:1155315616 1bit:1153630712 rate=1.0014605228367048
zip 文件總數:41 長度:175045518 0bit:701205717 1bit:699158427 rate=1.0029282204446632
txt 文件總數:3 長度:751 0bit:2839 1bit:3169 rate=0.8958662038497949
rar 文件總數:29 長度:104351603 0bit:417008975 1bit:417803849 rate=0.9980974947887568
gz 文件總數:1 長度:5934 0bit:23550 1bit:23922 rate=0.9844494607474291
7z 文件總數:3 長度:3937622 0bit:15752884 1bit:15748092 rate=1.0003042908309145
pdf 文件總數:1 長度:606272 0bit:2402659 1bit:2447517 rate=0.981672037415879
apk 文件總數:4 長度:4670591 0bit:18918992 1bit:18445736 rate=1.0256566612468052
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
class FileStruct {
public int sum;
public long length;
public long zero;
public long one;
@Override
public String toString() {
double rate = (double) zero / one;
return "文件總數:" + sum + " 長度:" + length + " 0bit:" + zero + " 1bit:"
+ one + " rate=" + rate;
}
}
public class FileCounter {
/**
* @param args
*/
public static void main(String[] args) {
// String [] inputFileList = new String[]{"G:","H:","K:"};
String dir = "D:";
FileCounter counter = new FileCounter();
counter.start(dir);
}
private File root = null;
public long totalLength;
public long usedLength;
public long usableLength;
private EnumFile enumFile;
private CountingBit countingBit;
private Map&
private LinkedBlockingQueue&
private FileStruct global = new FileStruct();
public boolean start(String filename) {
File rootDir = new File(filename);
if (!rootDir.isDirectory()) {
return false;
}
this.root = rootDir;
this.totalLength = root.getTotalSpace();
this.usableLength = root.getUsableSpace();
this.usedLength = totalLength - usableLength;
result = new ConcurrentHashMap&
// 隊列限制為 1000 個文件
fileQueue = new LinkedBlockingQueue&
countingBit = new CountingBit();
countingBit.start();
enumFile = new EnumFile();
enumFile.start();
return true;
}
public void exit() {
countingBit.exit();
}
public class EnumFile extends Thread {
private Stack&
@Override
public void run() {
File dir = root;
stack.add(dir);
while (!stack.empty()) {
dir = stack.pop();
File[] lists = dir.listFiles();
for (File file : lists) {
if (file.isDirectory()) {
stack.add(file);
// System.out.println(file);
} else {
try {
fileQueue.put(file);
global.sum++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
System.out.println("文件枚舉完畢!");
exit();
}
}
public class CountingBit extends Thread {
// 文件統計的進度
public int schedule;
private boolean exit = true;
public void exit() {
this.exit = false;
}
@Override
public void run() {
while (fileQueue.size() &> 0 || exit) {
int s = (int) (1000 * global.length / usedLength);
if (s &> schedule) {
schedule = s;
System.out.println("進度 : " + (double) schedule / 1000 * 100
+ "% , 已完成:" + (double) global.length / 1024 / 1024
/ 1024 + "GB");
}
File file = null;
try {
file = fileQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
// 讀取並計算文件的位元組數
try {
countFile(file);
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("文件統計完畢!");
printResult();
}
public void countFile(File file) throws IOException {
FileInputStream fin = null;
try {
fin = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
long filelength = file.length();
global.length += filelength;
String filename = file.getName();
String last = getLastName(filename);
if (last == null) {
last = "other";
}
last = last.toLowerCase();
FileStruct struct = result.get(last);
if (struct == null) {
struct = new FileStruct();
}
struct.sum++;
struct.length += filelength;
// 獲取輸入輸出通道
byte[] data = new byte[4096];
int len = -1;
int zero = 0;
int one = 0;
while ((len = fin.read(data)) != -1) {
for (int i = 0; i &< len; i++) {
int b = data[i];
if (b &< 0) {
b += 256;
}
zero += tbl0[b];
one += tbl1[b];
}
}
struct.zero += zero;
struct.one += one;
global.zero += zero;
global.one += one;
result.put(last, struct);
fin.close();
}
}
public static String getLastName(String name) {
int pos = name.lastIndexOf(".");
if (pos == -1 || pos == name.length()) {
return null;
}
return name.substring(pos + 1, name.length());
}
public void printResult() {
System.out.println(global);
for (String key : result.keySet()) {
FileStruct struct = result.get(key);
System.out.println(key + " " + struct);
}
}
public static int[] tbl0 = new int[256];
public static int[] tbl1 = new int[256];
static {
for (int i = 0; i &< 256; i++) {
int j = i;
int s = 0;
while (j &> 0) {
if (j % 2 == 1) {
s++;
}
j /= 2;
}
tbl1[i] = s;
tbl0[i] = 8 - tbl1[i];
}
}
}
我覺得他是想知道硬碟的初始狀態是0多還是1多。這樣的話答案應該是1多,因為這樣更節能,具體原因可以去看《數字電路》相關的書籍
你硬碟裝滿了GV嗎?
我來貼一段Java的代碼吧,整個D盤二十幾G的內容掃下來1和0大約3:4的比例,內容以文本,PDF,代碼和圖片為主,代碼如下,歡迎大大指正:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author humiao
*
*/
public class Test {
public static long totalOne= 0;
public static long totalZero = 0;
public static List&
static{
for(int i = Byte.MIN_VALUE ;i&<= Byte.MAX_VALUE;i++){
int oneNum = bitCount(i);
oneList.add(oneNum);
}
}
public static void main(String[] args) throws IOException {
File portDir = new File("D:");
File[] portFileArr = portDir.listFiles();
for(File portFile : portFileArr){
listFile(portFile);
}
}
public static void listFile(File portFile){
if(portFile == null){
return;
}else if(portFile.isDirectory() portFile.listFiles() != null){
for(File temp : portFile.listFiles()){
listFile(temp);
}
}else{
FileInputStream fis = null;
int zero = 0;
int one = 0;
byte[] tempArr = null;
try{
int length = (int)portFile.length();
if(length &> 0 ){
tempArr = new byte[length];
fis = new FileInputStream(portFile);
fis.read(tempArr);
for(byte temp : tempArr){
int oneCount = oneList.get(temp+128);
one += oneCount;
zero += (8-oneCount);
}
}
}catch(Exception e){
}finally{
try {
if(fis != null) fis.close();
fis = null;
} catch (IOException e) {
e.printStackTrace();
}
if(tempArr != null) tempArr = null;
}
totalZero += zero;
totalOne += one;
System.out.println("totalOne : "+totalOne+" totalZero : "+totalZero+" Current File : "+portFile.getAbsolutePath());
}
}
int count = 0;
for(int i = 0 ; i &< 8 ; i ++ ){
if((byteNum 1)%2 != 0){
count ++;
}
byteNum = byteNum &>&> 1;
}
return count;
}
}
顯然是0比較多。當然,有的數據可能1/0接近對半。
如果你對計算機硬體比較了解的話,很容易做出這個判斷。
不得先定義用什麼裝么?
好蛋疼的感覺……
取決於存儲了何種數據。單純從概率的角度來看,0和1應該是一樣多的,因為在邏輯地位上0和1是等價的,不應該有誰比誰多。
裝滿GV的硬碟裡面應該是1多。
除了solo的,一般一部片子裡面至少會有一個1吧,那種orgy的就是1多0少了。
推薦閱讀:
※Windows API 編程還能走多遠,長遠的考慮學習它的價值能有多大?
※物理系學生如何學習人工智慧?
※計算機科學與技術專業的學生要怎樣做才能避免成為 一個低級的碼農?
※想要完全掌握金融工程需要哪些學科的哪些教材?
※是不是機器學習的框架都偏向 Python ?如果是,為什麼?