iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
AI/ ML & Data

使用 jq 處理資料系列 第 27

Day27: jq module

  • 分享至 

  • xImage
  •  

前幾天我們認識了自訂函式 def 的寫法,在昨天完成了第二個函式的程式碼;並且在提取函式的過程中,我們認識了 debug 內建函式的使用情境和方法。今天我們來認識 jq 的模組(module)。透過 jq module,可以幫助我們組織和重複使用程式碼。

對話中學習

徒弟:師父,我聽說 jq 有模組(module),它和其他程式語言的模組有什麼不同嗎?

師父:很好的問題!jq 的模組系統確實有其獨特之處。它允許我們將常用的函式(function)表達式(filter)組織成可重複使用的程式碼單元,這對於大型專案或複雜的資料處理任務非常有用。

jq module

jq 模組(module)是一種將相關功能組織在一起的方式,可以提高程式碼的可讀性和可維護性。以下透過先前山陀兒颱風的例子,示範建立和使用 jq 模組的基本步驟:

1. 建立模組

建立一個檔名為 data_processing.jq 的檔案,內容如下:

首先先將原本定義寫在 main.jq 定義的函式 separate(f)beaufort(f; wind_array),移到 data_processing.jq 之中。

2. 使用模組

使用 include 或 import 來導入模組。其中如果模組的.jq檔案和主程式不同目錄,則使用 -L 參數來指定模組的路徑;相同目錄則不需要此參數。

Import 和 Include 的差異

jq 提供了兩種導入模組的方式:import 和 include。它們有以下主要區別:

  • Import: 使用 import 時,你需要通過"模組名稱"來使用其中的函數。例如:
import "data_processing" as dp;
dp::separate("Hello,World")
  • Include: 使用 include 時,模組中的函數會直接導入到當前命名空間,可以直接使用。例如:
include "data_processing";
separate("Hello,World")[0]

選擇使用 import 還是 include 取決於實際應用需求:

  • 如果想避免命名衝突,或者想明確指出某個函數來自哪個模組,使用 import 更合適。
  • 如果想更方便地使用模組中的函數,不需要每次都指定模組名,那麼 include 會更適合。

修改 main.jq

我們只有一個 module,因此使用 include 就足夠。main.jq使用 include 初步修改如下:

include "data_processing";
.[0] as $direction | .[1] as $ty | .[2] as $wind |
...(略)...

更進一步,我打算根據 Day24: 認識 generator 概念 這篇整理的最外層結構來提取函式、擺放在模組中,有:

  1. 將各個輸入陣列元素儲存為變數
  2. 組合標題陣列與每一行 AnalysisData
  3. 以 TSV 格式輸出

因此,我將 main.jq 改寫如下:

include "data_processing"; process_input | header, [ content ][] | @tsv

include 之後就是上面最外層結構的 1 | 2 | 3。

故還要再將原本寫在 main.jq 的部分提取至 data_processing.jq 模組裡面。提取後, data_processing.jq增加了以下三個函式:

# 處理透過 slurp 讀進來的陣列,回傳物件
def process_input:
  {
    direction: .[0], # 來自 direction.json
    typhoon: .[1],   # 來自 typhoon1005.json
    beaufort: .[2]   # 來自 beaufort_wind.json
  };

# 輸出固定標題,回傳字串陣列
def header:
  ["時間     ","經度","緯度","氣壓","風速MAX   ","陣風MAX   ","方向 ","預測"];
  
# 主要的資料內容,回傳颱風路徑陣列
def content:
  .beaufort as $wind | 
  .direction as $direction |
  .typhoon.records.tropicalCyclones.tropicalCyclone[] | 
  select(.typhoonName=="KRATHON") | 
  .analysisData.fix[-5:][] |
  [
    .fixTime[0:13], 
    separate(.coordinate)[0], 
    separate(.coordinate)[1], 
    .pressure, 
    beaufort(.maxWindSpeed; $wind),
    beaufort(.maxGustSpeed; $wind),
    $direction[.movingDirection], 
    .movingPrediction[0].value
  ]; 

在定義 process_input 也就是"1. 將各個輸入陣列元素儲存為變數"的時候,我稍微改變了原本的作法。原本直接使用 as $變數作法,改為直接將 slurp 輸入的陣列 -轉換- 成物件,用屬性的值存放輸入的陣列元素。好處在於,後續要取得可以直接使用 .direction,.typhoon,.beaufort 表達式。其他部分就照舊。

這樣看 main.jq 最外層的輸入/輸出結構一目了然;當然詳細還是要看模組裡面的函式定義囉。 今天修改的部分,可以參考 程式碼

結論

今天我們認識了 jq module 的寫法和使用方式,如此一來,就能把重複使用到的邏輯像是打包程式庫一樣,重複使用它;也可以按照我們期望的方式,將主程式安排成自己一看就懂的樣子,很棒。 👍

感謝今天的自己,認真的練習和學習。😄


上一篇
Day26: 提取函式,善用 debug
下一篇
Day28: jq 的錯誤處理
系列文
使用 jq 處理資料30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言