BOO大全/輸入與輸出
輸入與輸出
编辑讀取終端機文字
编辑Boo 已經內建了用來處理輸入的函數,名字是prompt。它會提示使用者並等待輸入,只適用於終端機應用程式:
>>> prompt('enter some text:') enter some text:here we go again 'here we go again'
也可以改使用 .NET 內建的 Console:
>>> import System >>> Console.In.ReadLine() here we go again 'here we go again'
我們也可以讀取輸入,作處理之後,再進行輸出。下面就是一個用來將輸入的字串轉換為大寫的代碼:
# uppercase.boo
text = System.Console.In.ReadToEnd()
print text.ToUpper()
在編譯之後,就可以像其他指令一樣使用。使用方法有兩種,第一種方法,你可以將輸入檔案指定為程式的標準輸入,然後將程式的標準輸出導向到一個輸出檔案。第二種方法,可以使用 pipe ( 就是 | ),將 sort 的輸出導向到 uppercase,再輸出到 myfile-upper-sorted.txt。
uppercase < myfile.txt > myfile-upper.txt sort myfile.txt | uppercase > myfile-upper-sorted.txt
當然也可以一次讀取一行,下面程式就是在讀取每行之後,再加上行號並輸出:
import System
k = 0
while (line = Console.In.ReadLine()) != null:
print ++k,line
我覺得這程式有點笨拙,感覺上就是直接以 Boo 改寫 C# 程式。所以下面改用 for 迴圈取代複雜的 while 迴圈,來迭代文字流裡的每一行,這是一個很棒的特性,不是嗎?(注意,Boo 的變數指派是一個運算式,而非述句,會有值傳出。譯註,C/C++/C# 也很常看到同樣的用法。)
import System
k = 0
for line in Console.In:
print ++k,line
這是只有兩行的版本:
for k,line in enumerate(System.Console.In):
print k,line
內建的 enumerate 函式就是為了這種情況產生的,它被設計來迭代集合並保有索引值。
最後再介紹一個把文字檔裡的每行放到陣列裡的方法,只有一行:
lines = array(string,line for line in System.Console.In)
尋找檔案
编辑.NET 在處理檔案與目錄上,提供了豐富的函式。這些函式都在 System.IO 命名空間裡。舉例來說,Directory.GetFiles 回傳一個包含某目錄下所有檔案的陣列。這裡, "." 通常用來表示當前的目錄,這兒並使用 GetFullPath 取得完整路徑。
>>> import System.IO >>> files = Directory.GetFiles(".","*.boo") >>> files ('.\\ast.boo', '.\\AttemptMacro.boo', '.\\PlotMacro.boo', '.\\SampleMacro.boo', '.\\TestAttemptMacro.boo', '.\\TestPlotMacro.boo', '.\\TestSampleMacro.boo') >>> Path.GetFullPath(files[0]) 'C:\\net\\sciboo\\examples\\macros\\ast.boo'
讀取檔案
编辑常常我們會想要存取任意的檔案﹔讀取的方法都一樣。與前面用過的 System.Console.In 的不同點,在於輸入文字流必須要先打開,使用完則必須關閉。
>>> import System.IO >>> fin = File.OpenText("tmp.txt") System.IO.StreamReader >>> fin.ReadLine() 'Here are ' >>> fin.ReadLine() 'a few lines' >>> fin.Close()
這邊把前面加上行號的例子再改寫,讓它可以依據程式參數來讀取指定檔案,並且加上一些基本的必要檢查:
import System.IO
if len(argv) == 0:
print "usage: <text file>"
return
if not File.Exists(argv[0]):
print "file does not exist: ",argv[0]
return
fin = File.OpenText(argv[0])
k = 0
for line in fin:
print ++k,line
fin.Close()
(我知道在一個程式裡使用 return 來離開顯得很奇怪,但別忘了,其實這裡面有個隱性的 main 函式。)
我建議你改用 using 述句。不只是因為它看起來比較簡潔 (把讀取的動作放到程式區塊裡,看來很清楚),而是因為不管發生什麼事,它保證你的檔案一定會關閉。所以最後五行可以再改寫為:
using fin = File.OpenText(argv[0]):
k = 0
for line in fin:
print ++k,line
讀取數值
编辑當我開始學 C# 時,我很驚訝,C# 居然沒有一個簡單的方法用以讀取數值。(也許對一個新語言來說,這太過時了,顯然,Java 也有此問題。) 你必須要分開輸入,並將字串轉為數值。這裡有個程式,它從檔案裡讀取所有數值,並計算其平均值:
import System
import System.IO
sum = 0.0
k = 0
for line in Console.In:
words = line.Split(char(' '),char('\t'))
for w in words:
if len(w) > 0:
try:
x = double.Parse(w)
sum += x
++k
except:
print w,"was not a number"
return
print "numbers " + k + " average was " + sum/k
這不是個漂亮的程式,字串的處理與累加被綁在一起了。
這裡使用 Generator 方法(使用 yield),將字串處理與累加的邏輯分開,
import System
import System.IO
def Numbers():
for line in Console.In:
words = line.Split(char(' '),char('\t'))
for w in words:
if len(w) > 0:
yield double.Parse(w)
sum = 0.0
k = 0
try:
for x in Numbers():
sum += x
++k
except e as Exception:
print e.Message
return
print "numbers " + k + " average was " + sum/k
如果我們在 double.Parse 失敗時,假設結果為 0 的話,可以讓程式更簡單些。前面我們在 Parse 發生錯誤時提出例外,雖然這看來很愚蠢,不過這卻是某些 script 語言,如 AWK,所作的事情。這邊我們再加上 SafeParse 來專注於解析數值的任務。(當然我們可以假設檔案的格式都是正確的,不做處理,但難保不會遇到不正常的狀況,你說是嗎?)
def SafeParse(w as string):
try:
return double.Parse(w)
except:
return 0.0
def Numbers():
for line in Console.In:
words = line.Split(char(' '),char('\t'))
for w in words:
if len(w) > 0:
yield SafeParse(w)
主要的迴圈現在看來更簡單了。
sum = 0.0
k = 0
for x in Numbers():
sum += x
++k
print "numbers " + k + " average was " + sum/k
下一節會提到將字串與數值分開的方法,請參考讀取字串。
(如果想再把主要迴圈作成函式的話,可以參考其他的作法)。
讀取字串
编辑TextReader 類別並不具有讀取有分隔符號文字的能力,它只能讀取行。你可以逐字元讀取,然後自行收集。Read 回傳一個整數,可以轉為字元。在遇到檔案結尾時,則傳回 -1(-1 不是個合法的字元)。所以我們可以一次讀取一個字元,並將其附加到 StringBuilder 物件裡,直到發現非空白字元或是遇到 -1 時,再把結果傳回。使用StringBuilder來組字串的原因是因為它比較有效率。
import System.Text
import System.IO
def ReadString(tr as TextReader):
ch = tr.Read()
while ch != - 1 and char.IsWhiteSpace(cast(char,ch)):
ch = tr.Read()
# 結束了,傳回 null
if ch == -1:
return null
# 遇到非空白字元!
sb = StringBuilder()
while ch != -1 and not char.IsWhiteSpace(cast(char,ch)):
sb.Append(cast(char,ch))
ch = tr.Read()
return sb.ToString()
s = """
1 2.3 hello
2 5.6 dolly
"""
sr = StringReader(s)
s = ReadString(sr)
while s:
print s
s = ReadString(sr)
print "that's all, folks!"
如果要處理整數或浮點數的話,可以這樣用:
def ReadInt(tr as TextReader):
return int.Parse(ReadString(tr))
def ReadDouble(tr as TextReader):
return double.Parse(ReadString(tr))
其他的作法
编辑這兒有個方法可以用來計算序列的加總,並回傳元素總數與加總值。這兒宣告了 duck 型別的序列,然後迭代這個序列並加總,所以這個函式可以處理任何序列,序列裡的所有元素都將被視作 double 型別再加總。
def sum(seq as duck):
y = 0.0
k = 0
for x as duck in seq:
y += x
++k
return k,y
import Tokens
print "array sum is ",sum((2.3,4.2,5.5))[1]
print "integers ",sum((10,20,30,40,50))[1]
y = sum(Tokenizer("numbers.txt").Numbers())
print "num ",y[0],"sum is ",y[1]
我們可以做的更好。這個版本可以支援所有型別,只要型別支援 + 運算子:
def sum(seq as duck):
y as duck
k = 0
for x as duck in seq:
if k == 0:
y = x
else:
y += x
++k
return k,y