中文字幕一区二区人妻电影,亚洲av无码一区二区乱子伦as ,亚洲精品无码永久在线观看,亚洲成aⅴ人片久青草影院按摩,亚洲黑人巨大videos

Swift中的命令行rools:Words

發(fā)布于:2021-01-25 10:35:43

0

111

0

swift 教程

我喜歡偶爾編寫自定義命令行工具。最近的一些例子是walkstest。

不久前我讀到Rob Landley寫一個words工具的想法,因為我喜歡這個想法,我想(重新)實現(xiàn)這個工具,同時試著看看我是否能保持它比Rob的C版本更簡單。

Swift

閱讀/打印

Words應該是一個過濾器,這意味著它應該從標準輸入讀取并寫入標準輸出。讓我們開始編寫一個最簡單的過濾器,它只回顯stdin而不做任何修改。

while let line = readline() {
   print(line)
}

拆分/聯(lián)接

要解決手頭的問題,我們可以將每行處理為

  • 將行拆分為單詞

  • 選擇其中的一些單詞

  • 按任意順序

  • 允許多個選擇

  • 將所選單詞組合成新行

讓我們實現(xiàn)更簡單的部分其中:

while let line = readLine() {
   let words = line.components(separatedBy: .whitespaces)
   print(words.joined(separator: " "))
}

不同之處在于,這個版本用單空格字符替換了空格序列。

透明/不透明

需要選擇哪些單詞(及其順序)

  • 由用戶以某種方式指示

  • 由程序以某種方式評估

稍后我們將處理用戶界面。程序可以將所需的信息存儲為(被動)數(shù)據(jù)結(jié)構(gòu)或(主動)函數(shù)或?qū)ο蟆?/span>

這說明了一個典型的設計沖突…嗯,不是函數(shù)式編程和面向?qū)ο缶幊讨g的沖突,我相信這是完全獨立于此的。我指的是Noel Welsh所描述的“不透明和透明的口譯員”。

在我們的程序中,差異如下所示:

透明

let wanted: [Int] = // TODO
let words = line.components(separatedBy: .whitespaces)
let selected = select(wanted, from: words)
print(selected.joined(separator: " "))

不透明

let select: ([String]) -> [String] = // TODO
let words = line.components(separatedBy: .whitespaces)
let selected = select(words)
print(selected.joined(separator: " "))

取舍可以歸結(jié)為一個問題:我們想把解決方案的哪些部分分開?

我們先來看看透明的變體,看看它會帶我們?nèi)ツ睦铩?/span>

論據(jù)

我們要在命令行中輸入選擇。我可以想到兩種方法:

  • 每個命令行參數(shù)表示一個選擇,程序僅對標準輸入進行操作。

words 3 4 2 < file

  • 第一個命令行參數(shù)指定所有選擇,其余參數(shù)指示要處理的文件。

words 3,4,2 file

我們將使用第一種方法,因為我相信它更容易實現(xiàn)

func index(_ string: String) -> Int {
   guard let int = Int(string) else {
       print("invalid index: (string)")
       exit(EX_USAGE)
   }
   return int - 1
}

let arguments = CommandLine.arguments.dropFirst()
let wanted = arguments.map(index)

這里發(fā)生了很多事情,讓我們來看看代碼:

let arguments = CommandLine.arguments.dropFirst()
let wanted = arguments.map(index)

CommandLine.arguments的第一個元素包含可執(zhí)行文件的路徑,我們將把剩余的參數(shù)轉(zhuǎn)換成索引

guard let int = Int(string) else {
   print("invalid index: (string)")
   exit(EX_USAGE)
}

我們把string轉(zhuǎn)換成一個數(shù)字。如果失敗,比如當string不包含數(shù)字時,我們打印一條錯誤消息,并使用EX_USAGE中止。

return int - 1

命令行上提供的索引應該從1開始,因此我們在這里按1更正以獲得數(shù)組索引。

選擇

一旦我們有了索引,我們就可以從數(shù)組中獲取相應的元素。

func select(_ indices: [Int], from array: [A]) -> [A] {
   return indices.map { array[$0] }
}

“致命錯誤:索引超出范圍”

只要有一個索引超出了anyline的范圍,這個簡單版本的程序就會崩潰。我們可以通過忽略超出給定行界限的索引來防止這個問題。

func select(_ indices: [Int], from array: [A]) -> [A] {
   let valid = { array.indices.contains($0) }
   return indices.filter(valid).map { array[$0] }
}

射程

一個非常有用的特性是,除了數(shù)字之外,還可以提供數(shù)字的范圍,如-f 3-6以打印第三到第六個字段。

我們可以用Range類型對這些范圍進行建模,所以讓我們嘗試將該特性添加到我們的程序中。

同樣,更復雜的部分是解析索引:

func index(_ string: String) -> CountableRange{
   func parse(_ component: String, default empty: Int) -> Int {
       if (component.isEmpty) {
           return empty
       }
       if let int = Int(component) {
           return int
       }
       print("invalid component: `(component)' in range: `(string)'")
       exit(EX_USAGE)
   }
   let components = string.components(separatedBy: "-")
   let lower = parse(components.first!, default: 1) - 1
   let upper = parse(components.last!, default: Int.max)
   return lower..<upper
}

我們將單個數(shù)字的解析提取到一個可以多次調(diào)用的內(nèi)部函數(shù)中。作為一個內(nèi)部函數(shù),它可以訪問其包含函數(shù)的參數(shù),我們可以利用它來提供更好的錯誤消息。此函數(shù)還期望在缺少范圍的一個邊界時返回一個默認值,這意味著我們可以將3-寫為“從第三個開始的每個單詞”。

對于每個范圍,我們使用1Int.max作為默認值,將第一個和最后一個組件分別解析為其下限和上限,并從這些邊界構(gòu)造一個CountableRange。我們只需要將下限改為1,因為CountableRange希望排除上限。

func select(_ ranges: [CountableRange], from array: [A]) -> [A] {
   return ranges.flatMap { range in
       return array[range.clamped(to: array.indices)]
   }
}

要應用我們的選擇,我們獲取每個范圍,使用clamp將其修剪到數(shù)組的邊界,然后從數(shù)組中獲取相應的元素。因為它返回集合而不是單個單詞,所以我們調(diào)用flatMap而不是map來展平所有這些集合。

結(jié)論

以下是整個代碼:

import Foundation

func index(_ string: String) -> CountableRange{
   func parse(_ component: String, default empty: Int) -> Int {
       if (component.isEmpty) {
           return empty
       }
       if let int = Int(component) {
           return int
       }
       print("invalid component: `(component)' in range: `(string)")
       exit(EX_USAGE)
   }
   let components = string.components(separatedBy: "-")
   let lower = parse(components.first!, default: 1) - 1
   let upper = parse(components.last!, default: Int.max)
   return lower..<upper
}

func select(_ ranges: [CountableRange], from array: [A]) -> [A] {
   return ranges.flatMap { range in
       return array[range.clamped(to: array.indices)]
   }
}

let arguments = CommandLine.arguments.dropFirst()
let wanted = arguments.map(index)
while let line = readLine() {
   let words = line.components(separatedBy: .whitespaces)
   let selected = select(wanted, from: words)
   print(selected.joined(separator: " "))
}

在生成和使用修改后的數(shù)據(jù)的兩個地方,需求的變化保持了令人愉快的局部性。我們沒有修改腳本的主體,因為我們省略了更改數(shù)據(jù)的類型簽名,并且在處理過程中不需要任何其他更改。

腳本的大小與C代碼相當,盡管兩個版本支持不同的功能(我們的版本支持范圍,而另一個版本支持自定義字分隔符),并且使用完全不同的框架(toybox基礎結(jié)構(gòu)用于Unix命令行工具,swift標準庫有一些關于它們的基本規(guī)定),所以把這個比喻為一個巨大的鹽粒。