how to recover the windows desktop in an RDP session

使用遠端桌面 (RDP) 工具連線到遠方 Windows 電腦時,有時候不小心關閉或弄壞了檔案總管 ( explorer.exe) 行程,導致工作列消失,或桌面變成一片黑,無法操作。這種情況下,毋須驚慌,以下是一步步如何啟動 工作管理員 (Task Manager),恢復桌面的方法。

  1. 步驟一:打開命令列視窗 (CMD)
    1. 打開工作管理員:按下 Windows Key + X  或 Ctrl + Shift + Esc 或 Ctrl + Alt + End,然後選擇「工作管理員」。
    2. 啟動新工作:在工作管理員中,點擊「檔案」選單,然後選擇「執行新工作」。
    3. 輸入命令:在彈出的對話框中,輸入 cmd,然後按下「確定」或 Enter 鍵。

  2. 步驟二:登出並重新登入
    1. 輸入登出命令:在命令提示字元後,輸入以下指令並按下 Enter:
      shutdown /l
      這個關機指令加上 (l)ogout 登出參數,會立即登出當前使用者。
    2. 重新登入:登出後,重新登入你的帳號。此時,桌面應該會恢復正常。

這個方法利用了 Windows 的內建功能來解決因誤操作導致的桌面消失問題。通過工作管理員啟動命令提示字元,然後使用登出命令,再重新登入,可以快速恢復桌面環境。這是一個簡單而有效的解決方案,適用於遠端桌面連線遇到類似無法操作的情況。

is an object with a string field mutable or immutable

In Java, strings are immutable objects. You might wonder if an object contains a String field which can be changed to other strings, is it an immutable or mutable object?

In fact, an object is considered immutable if its state cannot be changed after it is created. This means that all fields of the object must be final and cannot be modified after the object is constructed. Even though String objects themselves are immutable, if an object contains a field of type String that can be changed to reference a different String object, then the containing object is considered mutable. This is because the state of the object (i.e., the value of the String field) can change.

Here’s an example to illustrate this:

public class MutableObject {
    private String mutableField;  // fields without final can be changed

    public MutableObject(String initialField) {
        this.mutableField = initialField;
    }

    public void setMutableField(String newField) {
        this.mutableField = newField;
    }

    public String getMutableField() {
        return mutableField;
    }
}

In this example, MutableObject is mutable because the mutableField can be changed using the setMutableField method.

On the other hand, an immutable object would look like this:

public final class ImmutableObject {
    private final String immutableField; // final fields cannot be changed

    public ImmutableObject(String initialField) {
        this.immutableField = initialField;
    }

    public String getImmutableField() {
        return immutableField;
    }
}

In this case, ImmutableObject is immutable because the immutableField is final and cannot be changed after the object is created.

So, if your object allows its String field to be changed, it is considered mutable.

how many instances and containers will be launched in AWS ECS service

AWS 彈性容器服務 (ECS) 分成 ECS on Fargate 及 ECS on EC2 兩種。其中,ECS on Fargate 由 Fargate 服務全權負責容器如何配置到個體,屬無伺服器 (serverless)或 託管 (managed)的運算類服務。ECS on EC2 則由用戶自行負責容器如何配置到個體,其服務參數的任務配置(task placement)選項包含: az balanced spread,az balanced binpack,binpack,one task per host,random,custom等。預設 az balanced spread 為先平衡分散到不同的可用區,再平衡分散到可用區內個體。binpack乃依記憶體儘量配置容器到最少數量的個體上,即挑選足夠放置之下,剩餘記憶體最小之個體先放容器。

至於 ECS 到底會啟動幾個虛擬機個體 (instance)及容器 (container)來提供服務,可由如下因素決定: 
  1. 個體數: 由 ECS 叢集參數的個體希望容量(desired capacity) 上下限決定。其實際數量可由叢集的自動伸縮群組(ASG, Auto Scaling Group)參數,依個體負載指標值(metric)變化,在希望容量的上下限之間,採用目標追蹤(target tracking)類型原則,作伸縮調整。
     
  2. 容器數: 由 ECS 部署建立服務的所須任務數 (desired tasks)參數,及建立任務定義時使用的容器數量有多少兩者決定。ECS服務的實際任務數可由服務自動伸縮 (Service Auto Scaling) 參數,依服務負載指標值變化,看是採用目標追蹤(target tracking)類型或步進伸縮(step scaling)類型的原則,作伸縮調整。
Reference:
1. ECS叢集個體自動擴展: Automatically manage Amazon ECS capacity with cluster auto scaling - Amazon Elastic Container Service

what's the mechanism behind hash tables

雜湊表格 (HashTable),雜湊集合 (HashSet),或 雜湊映射 (HashMap),以下統稱雜湊表格,是效能最佳的一種資料結構,其增刪查找的時間複雜度皆為 O(1),和容器存放的元素個數 n 無關。Python語言的集合 (Set) 及字典 (Dictionary) 就是分別基於 雜湊集合 及 雜湊映射 而實現的,所以存取效能很好。

話雖如此,雜湊表格苦於兩個缺點,一是不能發生踫撞,只要踫撞嚴重,存取效能就會降低,退化成 O(n)。二是不能排序,不能查詢比某元素大/小的元素集合(tailSet/headSet)。若容器須要排序,只能退而求其次,改用樹狀結構 (TreeSet/TreeMap),例如搜索樹等,其增刪查找效能降到 O(log(n));或堆積 (Heap),其增刪效能 O(log(n)),但查找最小或最大值效能 O(1)。

以下參考文獻,列出雜湊表格常採用的技術分類,供了解其極佳效能的作法及補救法。

A. Hash code and compression: (雜湊碼及壓縮技術)

  A1. Hash code function 雜湊碼函數
         如何依據元素內容計算雜湊值

  1.  Bit representation (位元表現法)
  2.  Polynomial hash codes (多項式雜湊碼)
  3.  Cyclic-shift hash codes (循環平移雜湊碼)

  A2. Compression function 壓縮函數
        如何將雜湊碼 (hash_code) 依表格大小 (hash_table_size) 轉為找尋起點格下標

  1.  Division method (求餘數法)
         hash_code mod hash_table_size
  2.  MAD (multiply-add-and-divide) method (乘加求餘數法)
         [(a * hash_code + b) mod p] mod hash_table_size

B. Hash collision handling 雜湊踫撞處理法

  B1. Closed addressing / Open hashing or Separate chaining
         封閉式定址         /  開放式雜湊法,個別串接

         放元素的格子全由元素的雜湊碼決定 / 放元素的格子在雜湊表之外

  B2. Open addressing / Closed hashing
         開放式定址       / 封閉式雜湊法

        放元素的格子不全由元素的雜湊碼決定,視當時踫撞情形而定 / 放元素的格子在雜湊表

  1. Linear probing 線性探索
  2. Quadratic probing 平方探索
  3. Double hashing 雙雜湊

Reference:

  1. Open Addressing Collision Handling technique in Hashing - GeeksforGeeks
  2. 2015-goodrich-wiley-data structures & algorithms in java, 6th ed.

cybersecurity and privacy protection related standards

隨著資安及隱私越來越受公私部門重視,以下列出資通安全 (cybersecurity) 及隱私保護常見的 ISO (International Standardization Organization ,國際標準化組織) 及其他標準供參考。

  • ISO 9000系列品質保證管理標準 
    • 9001品質管理系統(QMS)要求

  • ISO 22300系列安全和復原力標準 
    • 22301事業持續性管理系統(BCMS)要求 
    • 22317事業持續性管理系統指導綱要

  • ISO 27000系列資訊安全管理系統(ISMS)標準
    • 27001資訊安全管理系統要求,附錄A含控制目標、控制措施 
    • 27002資訊安全管理系統作業規範,供選擇、實施、管理控制措施之用 
    • 27003資訊安全管理系統規劃指引,有關必要活動的建議(應該),可能(能力可以),允許(權限可以)作法指引 
    • 27004資訊安全管理系統評測指導網要,供監測、測量、分析、評測之用 
    • 27005資訊安全風險管理指導綱要 
    • 27006稽核驗證機構之要求

    • 27017雲端服務資訊安全管理 
    • 27018雲端服務個資保護作業規範 

    • 27102資安管理的網宇保險指導綱要

    • 27701隱私資訊管理要求及指導綱要,含PII控制者及處理者

  • ISO 29100系列隱私權框架標準 
    • 29134隱私衝擊評估指導綱要 
    • 29151個資保護作業規範

  • ISO 31000風險管理指導綱要

  • CIS,網際網路安全中心 (Center for Internet Security) 
    • Critical Security Controls 重要安全控制措施: 基礎型(6),功能型(10),組織型(4)

  • EU,歐洲議會及委員會
    • GDPR 2016/679 一般資料保護法規 (General Data Protection Regulation)

  • IEC,國際電工委員會 (International Electrotechnical Commission)
    •  62443 工業通信及網路-網路及系統安全系列標準,乃工業控制系統資訊安全,風險控管基準

  • ISAC,資訊分享與分析中心 (Information Sharing and Analysis Center)
    • COBIT 2019,  資訊及相關技術控制目標 (Control OBjectives for Information and related Technology) 標準

  • NIST,美國商業部國家標準暨技術研究院 (National Institute of Standards and Technology) 
    • CyberSecurity Framework 網宇安全框架 
    • SP800-137資安持續監視 
    • SP800-207零信任架構 

  • NICS,行政院數位發展部國家資通安全研究所 (National Institute of Cyber Security)
    • GCB,政府組態基準 (Government Configuration Baseline)
    • VANS,弱點分析及通報系統 (Vulnerability Analysis and Notice System)
    • EDR,端點偵測及應變機制 (Endpoint Detection and Response)
    • ZTA,零信任架構 (Zero Trust Architecture)
    • N-SOC,國家資安聯防監控中心 (National Security Operation Center)
    • N-CERT,國家資通安全通報應變中心 (National Computer Emergency Response Team)

  • ACS,行政院數位發展部資通安全署 (Administration for Cyber Security)
    • 資安政策與法規
    • 關鍵基礎設施資安防護
    • 資安事件通報應變
參考:
1.經濟部產業發展署 產業人才能力鑑定計畫 iPAS 數位課程平台 (industry Professional Assessment System)
2.ACW 資安網路學院: (Art of Cyber War)

discontinuous points of 12-hour vs 24-hour time systems

12小時制 (12-hour  time system) 和24小時制時間 (24-hour time system) 乍看很容易轉換。凡遇到12小時制的 PM (Post Meridem, 下午) 就數字加12小時,刪掉 PM,變成24小時制;遇到12小時制的 AM (Ante Meridem,上午) 就數字不動,刪掉 AM,變成24小時制。大部份情況是如此沒錯,但是在半夜12點,半夜1點,中午12點,中午1點前後,兩者轉換有點跳躍,不連續,易生混淆。

簡單講,12小時制是沒有半夜 00:00AM-00:59AM 或中午 00:00PM-00-59PM 這兩種寫法的。

    12小時制的四處不連續點 (discontinuous points) 包括
  1. 中午 11:59AM 的下一分鐘是 中午12點 12:00PM,由 AM 跳成 PM
  2. 中午 12:59AM 的下一分鐘是 中午1點 01:00AM,由 12:59 跳成 01:00
  3. 半夜 11:59PM 的下一分鐘是 半夜12點 12:00AM,由 PM 跳成 AM
  4. 半夜 12:59PM 的下一分鐘是  半夜1點 01:00PM,由 12:59 跳成 01:00
    而相形之下,24小時制只有一處不連續點
  1. 半夜 23:59 的下一分鐘為半夜 00:00,由 23:59 跳成 00:00

以下為兩者24小時對照表,其中紅色標記處為常易混淆處。即半夜0-1點,在12小時制稱為半夜 12:00AM-12:59AM,而非 00:00AM-00:59AM。中午12-13點,在12小時制稱為中午 12:00PM-12:59PM,而非 00:00PM-00-59PM

24小時制 12小時制
00:00 - 00:59 半夜 12:00AM - 12:59AM
01:00 - 01:59 01:00AM - 01:59AM
02:00 - 02:59 02:00AM - 02:59AM
03:00 - 03:59 03:00AM - 03:59AM
04:00 - 04:59 04:00AM - 04:59AM
05:00 - 05:59 05:00AM - 05:59AM
06:00 - 06:59 06:00AM - 06:59AM
07:00 - 07:59 07:00AM - 07:59AM
08:00 - 08:59 08:00AM - 08:59AM
09:00 - 09:59 09:00AM - 09:59AM
10:00 - 10:59 10:00AM - 10:59AM
11:00 - 11:59 11:00AM - 11:59AM
12:00 - 12:59 中午 12:00PM - 12:59PM
13:00 - 13:59 01:00PM - 01:59PM
14:00 - 14:59 02:00PM - 02:59PM
15:00 - 15:59 03:00PM - 03:59PM
16:00 - 16:59 04:00PM - 04:59PM
17:00 - 17:59 05:00PM - 05:59PM
18:00 - 18:59 06:00PM - 06:59PM
19:00 - 19:59 07:00PM - 07:59PM
20:00 - 20:59 08:00PM - 08:59PM
21:00 - 21:59 09:00PM - 09:59PM
22:00 - 22:59 10:00PM - 10:59PM
23:00 - 23:59 11:00PM - 11:59PM

考慮到12小時制有4處不連續跳躍點,若要設定任何日期時間期限到半夜12點,建議截止期 (deadline) 選擇截止日期的 11:59PM,比較不會出錯。因為選擇截止日期的 12:00PM 將會變成當天中午截止,短少12小時。而若選擇截止日期加1日的 12:00AM,雖然正確,卻有點不自然,會想東想西,怕哪裏出問題。

how to add libraries for a NetBeans project

如何為 NetBeans 專案添加類別庫,下面以添加 Apache Commons-CSV 讀取CSV檔案的類別庫為例。

下載及解開 Apache Commons-CSV 類別庫
    URL: https://commons.apache.org/proper/commons-csv/download_csv.cgi
	     點選 Binaries/commons-csv-1.10.0-bin.zip
    解壓後路徑: C:\...\commons-csv-1.10.0 

從 NetBeans 左欄面板,如下點選填入名稱,類別檔,原始碼,註解檔路徑。
Projects/Project Name/Libraries/Add Library.../Create
	Library Name: Commons-CSV
	Library Type: Class Libraries

	Classpath/Library Classpath: 類別檔路徑
		Add JAR/Folder...
		C:\...\commons-csv-1.10.0\commons-csv-1.10.0.jar

	Sources/Library Sources: 原始碼路徑
		Add JAR/Folder...
		C:\...\commons-csv-1.10.0\commons-csv-1.10.0-sources.jar

	Javadoc/Library Javadoc: 註解檔路徑
		Add JAR/Folder...
		C:\...\commons-csv-1.10.0\apidocs

設定類別檔,原始碼,註解檔的完整路徑,安裝類別庫之後,NetBeans專案就可以使用如下類別庫套件:
  org.apache.commons.csv.*

遇到有疑問的類別或方法,可選擇其名字,按如下選單或快速鍵,查看其註解或原始碼:
看註解: Source/Show Documentation (Ctrl-Shift-Space)
看原始碼: Navigate/Go to Source (Ctrl-Shift-B)

why thrashing occurs in virtual memory systems

依據現代電腦的馮紐曼運算架構,CPU始終只能從記憶體讀取資料執行,不能直接從硬碟讀取資料。因此,程式常受限於記憶體的容量限制而無法載入執行。可是明明電腦系統的硬碟容量常是記憶體的上千倍,難道不能克服困難,將硬碟當成記憶體使用嗎?

虛擬記憶體 (Virtual Memory)就是這樣一個應急措施,可將硬碟分一塊置換空間 (Swap Space),當成記憶體用。其明顯好處是從此電腦不再受記憶體的容量限制,再大、再多的程式也可執行。只要硬碟夠大,頂多暫時存放在硬碟的置換空間,要用時再搬進記憶體執行即可。

但記住虛擬記憶體始終只是一個應急措施,不能當成常態使用。理由是資料在記憶體和硬碟之間搬移 (swapping) 時,要花費很久的等候時間,互動式用戶將等的不耐煩,故只適合非不得已偶一為之。例如,記憶體的批次用戶較多,可暫時挪到硬碟休息,先讓互動用戶使用記憶體。

採用虛擬記憶體有一個副作用是如果管理不好,分配給程式的實體記憶體不夠,須要先將暫時不用的程式從記憶體搬出到硬碟 (page out),才能騰出空間,讓須要的程式碼從硬碟搬回到記憶體 (page in) 執行。萬一記憶體極端不足情況下,很可能剛搬出的程式,等下又要再搬入,造成所謂的掙扎 (thrashing) 現象。出現此反常現象時,系統效能將變極差,只見硬碟不斷亮燈,苦苦忙著將資料從記憶體搬出搬進,I/O使用率陡升,而CPU卻坐等資料搬好,閒閒沒事幹,CPU使用率陡降。

註: 上述 掙扎現象的英文 thrashing 查字典有如下諸多意思:
           http://www.merriam-webster.com/dictionary/thrash
           掙扎,輾轉,擺盪,振盪,顛簸,抖動,猛移,往復移動,頻繁置換;
           徒勞,作虛功,無進展,白費力氣,原地踏步;
           打敗,痛打,窮忙,窮打,窮追猛打
       常見直譯有 輾轉,擺盪,抖動,甚至意譯 窮忙,作虛功,也都很傳神。
       選擇 掙扎 則兼有直譯及意譯,猶如泳者奮力浮水,離水之魚奮拍入水之勢。

active versus passive multiprogramming

多元規劃 (multiprogramming) 是作業系統演進上,早期一項重要發明。早期電腦的記憶體一次只放一支程式,遇到程式執行I/O指令,等候I/O結果,CPU就閒置浪費掉。因此,利用多元規劃技術,在記憶體放進多支程式,遇程式等I/O時,就將CPU分配給其他程式使用,如此即可提升CPU使用率 (utilization)。

至於為何將技術取名為多元規劃,讓人從字面不易猜透其義? 猜測可能是參考當時流行的線性規劃 (linear programming),整數規劃 (integer programming) 等最佳化技術的命名。顧名思義,多元 指的是記憶體一次載入多支程式,規劃 指的是安排多支程式執行。而某種意義上,這也的確是讓CPU使用率提升的一種最佳化技術。如果對多元用語的語義不明有芥蒂,也許稱為 多程式規劃 會更一目暸然。一個電腦系統的 多元規劃度 (degree of multiprogramming) 為記憶體可放進多少支程式的數量,其值越高,表示CPU有越多程式可執行,越有利於提升CPU使用率。

多元規劃依據作業系統排班器對CPU掌控程度分為兩種。主動式多元規劃 (active multiprogramming) 利用計時器中斷主動介入, 給定時間一到,CPU即須讓出跑其他程式,可防止CPU遭程式霸佔;被動式多元規劃 (passive multiprogramming) 的排班器只能被動等候程式志願退出,或作I/O,或收到外部週邊中斷,才能重新取得CPU分配給其他程式,容易造成CPU遭程式霸佔。 

how to solve the tower of hanoi by recursion versus simulated call stack?


/*
   TowerOfHanoi.java  遞迴版 及 模擬呼叫堆疊版 求解河內塔 

> java TowerOfHanoi
Hanoi Tower by Implicit Call Stack 遞迴版
A:[3, 2, 1], B:[], C:[]		1: Move disk 1 from A to C
        A:[3, 2], B:[], C:[1]		2: Move disk 2 from A to B
        A:[3], B:[2], C:[1]		3: Move disk 1 from C to B
A:[3], B:[2, 1], C:[]		4: Move disk 3 from A to C
A:[], B:[2, 1], C:[3]		5: Move disk 1 from B to A
        A:[1], B:[2], C:[3]		6: Move disk 2 from B to C
        A:[1], B:[], C:[3, 2]		7: Move disk 1 from A to C
A:[], B:[], C:[3, 2, 1]

Hanoi Tower by Explicit Stack 模擬呼叫堆疊版
A:[3, 2, 1], B:[], C:[]		1: Move disk 1 from A to C
        A:[3, 2], B:[], C:[1]		2: Move disk 2 from A to B
        A:[3], B:[2], C:[1]		3: Move disk 1 from C to B
A:[3], B:[2, 1], C:[]		4: Move disk 3 from A to C
A:[], B:[2, 1], C:[3]		5: Move disk 1 from B to A
        A:[1], B:[2], C:[3]		6: Move disk 2 from B to C
        A:[1], B:[], C:[3, 2]		7: Move disk 1 from A to C
A:[], B:[], C:[3, 2, 1]
*/
import java.util.Stack;

public class TowerOfHanoi 
{
    static Stack stackA = new Stack<>();  // 柱A
    static Stack stackB = new Stack<>();  // 柱B
    static Stack stackC = new Stack<>();  // 柱C
    static int nDisks = 5; // Number of disks 盤數
    static int count = 0;  // 步數
    static boolean printGoal = false;  // 列印目標否
    static boolean printOperation = true;  // 列印步驟否
    static boolean printStack = false;  // 列印模擬呼叫堆疊否

    // 印n格空白
    public static void printSpaces(int n)
    {
        for(int i=0; i <= n -1; i++) System.out.print(" ");
    }
    
    // 印柱A,柱B,柱C堆疊,前面n層內縮
    public static void printStacks(int n)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("A:" + stackA);
        sb.append(", B:" + stackB);
        sb.append(", C:" + stackC);

        System.out.println();
        printSpaces(n*8);  // 每層內縮8格
        System.out.print(sb.toString());
    }
    
    // 搬移柱from頂一個盤子到柱to
    public static void transfer(char from, char to)
    {
        if(from=='A' && to=='B') stackB.push(stackA.pop());
        if(from=='A' && to=='C') stackC.push(stackA.pop());
        if(from=='B' && to=='A') stackA.push(stackB.pop());
        if(from=='B' && to=='C') stackC.push(stackB.pop());
        if(from=='C' && to=='A') stackA.push(stackC.pop());
        if(from=='C' && to=='B') stackB.push(stackC.pop());
    }
    
    // 遞迴版解河內塔,將n個盤子從柱sourc,搬到柱target,透過柱auxiliary
    public static void solveHanoi(int n, char source, char auxiliary, char target) 
    {        
        if(printGoal) 
        {
            System.out.println();
            printSpaces((nDisks - n)*8);
            System.out.print(String.format("hanoi(n:%d,s:%c -> t:%c)",n,source,target));
        }

        if (n == 1) 
        {
            if(printOperation) 
                System.out.print(String.format("\t\t%d: Move disk 1 from %c to %c", ++count, source, target));

            transfer(source, target);            
        } 
        else 
        {
            solveHanoi(n - 1, source, target, auxiliary);

            printStacks(nDisks - n);            
            if(printOperation) 
                System.out.print(String.format("\t\t%d: Move disk %d from %c to %c", ++count, n, source, target));
            transfer(source, target);
            printStacks(nDisks - n);

            solveHanoi(n - 1, auxiliary, source, target);
        }
    }

    // 模擬呼叫記錄    
    static class HanoiCallRecord
    {
        int num;
        char source;
        char auxiliary;
        char target;
        int stage; // 0 for moving n-1 disks from source to auxiliary rods; 
                   // 1 for moving the disk n from source to target rods
                   //   and moving n-1 disks from auxiliary to target rods
                   // 2 for backtracking to the previous call record
        
        // 建構子
        public HanoiCallRecord(int num, char source, char auxiliary, char target)
        {
            this.num = num;
            this.source = source;
            this.auxiliary = auxiliary;
            this.target = target;
            this.stage = 0;  // 預設從階段0開始
        }
        
        // 列印呼叫記錄
        public String toString()
        {
            return String.format("(n:%d,s:%c,a:%c,t:%c,s:%d)",
                    num, source, auxiliary, target, stage);
        }
    }
    
    // 模擬呼叫堆疊,解河內塔,將n個盤子從柱sourc,搬到柱target,透過柱auxiliary
    public static void hanoiUsingStacks(int num, char src, char aux, char tgt) 
    {
        Stack stack = new Stack<>();
        
        HanoiCallRecord initial = new HanoiCallRecord(num, src, aux, tgt);
        stack.push(initial);  // 壓入第1層呼叫記錄

        while (stack.isEmpty()==false) 
        {
            if(printStack) System.out.print("\n" + stack);
            
            HanoiCallRecord current = stack.peek();  // 檢視本層呼叫記錄
            int n = current.num;
            char source = current.source;
            char auxiliary = current.auxiliary;
            char target = current.target;
            int stage = current.stage;
            
           if (n == 1) // 執行特別任務,然後退回上一層任務
           {
                if(printOperation) 
                    System.out.print(String.format("\t\t%d: Move disk 1 from %c to %c", ++count, source, target));

                transfer(source, target);
                stack.pop();  // 彈出本層呼叫記錄,
            } 
            else if(stage == 0) // 階段0, 執行本層第0階段任務
            {
                // solveHanoi(n - 1, source, target, auxiliary);
                HanoiCallRecord next = new HanoiCallRecord(n - 1, source, target, auxiliary);
                stack.push(next); // 壓入下層呼叫記錄
                current.stage++;  // 更新本層呼叫記錄的階段欄位
            }
            else if(stage == 1) // 階段1, 執行本層第1階段任務
            {
                printStacks(nDisks - n);            
                if(printOperation)
                    System.out.print(String.format("\t\t%d: Move disk %d from %c to %c", ++count, n, source, target));
                transfer(source, target);
                printStacks(nDisks - n);

                // solveHanoi(n - 1, auxiliary, source, target);
                HanoiCallRecord next = new HanoiCallRecord(n - 1, auxiliary, source, target);
                stack.push(next);  // 壓入下層呼叫記錄
                current.stage++; // 更新本層呼叫記錄的階段欄位
            }
            else if(current.stage == 2) // 階段2,本層任務完成,退回上一層任務
            {
                stack.pop();  // 彈出本層呼叫記錄,
            }
        }
    } 

    // 測試主程式
    public static void main(String[] args) 
    {
        nDisks = 3; // Number of disks
        count = 0;
        for(int i=nDisks; i >= 1; i--) stackA.push(i);
        
        System.out.print("Hanoi Tower by Implicit Call Stack 遞迴版");

        printStacks(0);
        solveHanoi(nDisks, 'A', 'B', 'C');
        printStacks(0);
        
        // ===================================
    
        count = 0;
        stackA.clear();
        stackB.clear();
        stackC.clear();
        for(int i=nDisks; i >= 1; i--) stackA.push(i);
                    
        System.out.print("\n\nHanoi Tower by Explicit Stack 模擬呼叫堆疊版");
        
        printStacks(0);
        hanoiUsingStacks(nDisks, 'A', 'B', 'C');
        printStacks(0);
    }
}    

how to connect codex app to remote ollama backend through caddy relay

如何讓 Codex App 連接公司架設的 LLM 從 Caddy 閘道安裝、遠端 Ollama API 金鑰,到 Codex 的 config.toml 組態設定,一次整理成可直接照做的流程。 前言 OpenAI 提供免費下載...

總網頁瀏覽量