深入理解鏈表(Linked List)與資料結構 - 以 JavaScript 實作

閱讀時間約 25 分鐘


會開始寫有關演算法的文章,一部分是知道自己在演算法沒有這麼熟悉,另一個部分,則是發現現在面試考演算法的比例變高了。因此也下定決心,在這個過程慢慢地把演算法補齊。今天要介紹的資料結構便是——鏈表(Linked List)。


如何理解 Linked List 與 Array 的差異?

與常見的數組(Array)不同,我們可以用書櫃和貨櫃車的貨櫃來比喻。想像數組就像是一個書櫃,其中的書籍排列整齊,每本書都在自己的位置上。當你需要找一本書時,只要知道它的位置,就可以直接拿到它。這就像是在 Array 中,可以直接透過 index訪問元素。

但當你需要在書櫃中放入或拿出一本書時,可能需要移動其他書籍來為新書騰出空間,或移動被拿出的書籍所留下的空白,這就像 Array 在插入或刪除元素時,需要移動其他元素一樣。

鏈表更像是一列貨櫃車上的貨櫃。每個貨櫃都連接到下一個,但它們不一定需要排列在一條直線上。當你想增加或移除一個貨櫃時,只需要解開它與前後貨櫃的連接,然後將它移出或加入即可。這就像在鏈表中添加或刪除節點那樣,不需要移動其他節點,只需要更改一些鏈接即可。

不過,如果你需要找到特定順位的貨櫃,你就會需要從頭開始計算,直到找到目標為止,這也說明了 Linked List 在搜尋上的特性。


這一篇文章有什麼?

本文將深入探討鏈表的核心概念,並使用 JavaScript 來說明如何實現和操作鏈表。我們將一步一步說明鏈表的六大方法:

  1. append(添加到末尾)
  2. prepend(添加到開頭)
  3. insert(插入元素至特定位置)
  4. remove(刪除元素)
  5. find(查找元素)
  6. reverse(反轉鏈表)

每個方法都將通過詳細的程式碼和解釋,讓您不僅學會如何使用這些方法,還能深入理解其背後的原理。接著,就讓我們開始吧!


鏈表(Linked List)的核心概念

鏈表(Linked List)是一種數據結構,它由一系列節點(Node)組成,每個節點包含數據和指向下一節點(next)的連接。也是因為結構的特性,使得元素的插入和刪除等操作,可以在任何位置有效率地進行,不同於需要連續記憶體空間的數組(Array)。

例如 React 的 hooks 儲存,就是使用 Linked List 的結構進行儲存,這也是為什麼 hook 不能被放在條件式之中的主因。但礙於本篇的主題,之後有機會再開一篇。


為何 Linked List 插入與刪除優於 Array?

鏈表與數組的主要區別在於數據存儲和訪問方式。數組是連續存儲的,這意味著它們可以快速訪問任何位置的元素(時間複雜度 O(1) ),但在中間插入或刪除元素時可能效率較低。相反,鏈表在插入和刪除操作上更有優勢,但對於隨機訪問與搜尋來說效率較低。


常見的單鏈表與雙鏈表

鏈表可以分為兩類:單鏈表(Singly Linked List)和雙鏈表(Doubly Linked List)。單鏈表的節點僅包含指向下一節點(next)的鏈接,而雙鏈表的節點則包含兩個鏈接,分別指向前一個(prev)和下一個節點(next)。

  1. 單鏈表:在這種類型的鏈表中,每個節點只含有指向下一節點的鏈接。這意味著它們只能單向遍歷。單鏈表結構簡單,使用的記憶體較少,但在某些操作中,如反向遍歷或查找前節點時效率會很差。
  2. 雙鏈表:與單鏈表不同,雙鏈表的節點不僅有指向下一節點的鏈接,還有指向前一節點的鏈接。這種結構使得雙向遍歷成為可能,提高了某些操作的效率,例如反向遍歷和在特定位置插入或刪除節點。然而,這種靈活性是以增加額外內存為代價,因為每個節點需要額外的記憶體空間,來存儲前驅節點的引用。

因此,在選擇使用哪種類型的鏈表時,就會需要思考何種限制與需求需要被優先滿足。



實現鏈表的基礎結構

在 JavaScript 中實現一個鏈表,最重要的有兩個核心元素:

  1. 節點類(Node class)
  2. 鏈表類(LinkedList class)

首先需要創建一個節點類(Node class),用於表示鏈表中的每個節點。隨後,我們將建立一個鏈表類(LinkedList class),用於管理這些節點並提供鏈表操作的方法。


節點類(Node class)的實現

節點類通常包含兩個屬性:value(用於存儲數據)和 next(用於指向鏈表中的下一個節點)。以下會各自實現單向列表(Singly Linked List)與雙向列表(Doubly Linked List)。兩者唯一的差異,就在於每一個節點,需要多一個 prev 來記錄前一個 Node 的指向。

// Singly Node
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}

// Doubly Node
class DoublyNode {
constructor(value) {
this.value = value;
this.prev = null;
this.next = null;
}
}

鏈表類(Linked List class)的初始結構

鏈表類則負責組織節點(Node),並提供各種操作鏈表的方法。在這個基本結構中,head 屬性指向鏈表的第一個節點,且因為尚未有任何節點,因此 tail 也指向 head 的 null。而 size 屬性則用於追蹤鏈表中的節點總數。

class LinkedList {
constructor() {
this.head = null;
this.tail = this.head;
this.size = 0;
}
}



如何實作 Linked List 常見的方法

接下來,筆者將六個常見的方法「append」、「prepend」、「insert」、「remove」、「find」和「reverse」等鏈表操作,以實際的程式碼逐行說明。但也要提醒大家,實際上不只有一種實現方式,這篇文章的只是其中一種實現方式,提供給大家參考與對照。


append - 在鏈表尾部添加元素

append 這個方法,主要用於在鏈表的末尾添加一個新節點。這個操作首先檢查是否存在頭節點(head)。如果沒有,新節點將成為頭節點。

接著,因為我們有設定 tail 來直接找到最後一個節點,不然就需要從頭遍歷鏈表,找到最後一個節點,並將其 next 指針指向新節點。

而雙向鏈表需要額外做的步驟,在於將新節點的 prev 與原先 tail 的節點相連,讓我們可以從新的節點找到前一個的節點。

// Singly Linked List
class LinkedList {
append(item) {
const newNode = new Node(item);
if (this.head === null) { // 如果為空鏈表
this.head = newNode; // 將 newNode 新增至 head
this.tail = this.head; // 將 newNode 新增至 tail
} else {
this.tail.next = newNode; // 將 newNode 接在 tail 的後方
this.tail = newNode; // 將 tail 換成 newNode
}
this.size++;
return this; // 方便鏈式調用
}
}

// Doubly Linked List
class DoublyLinkedList {
append(item) {
const newNode = new Node(item);
if (this.head === null) {
this.head = newNode;
this.tail = this.head;
} else {
newNode.prev = this.tail; // 將 newNode 的前節點設定為原先的 tail 節點
this.tail.next = newNode;
this.tail = newNode;
}
this.size++;
return this;
}
}



prepend - 在鏈表頭部添加元素

prepend 方法將新節點添加到鏈表的開頭。對於單向鏈表,這涉及將新節點的 next 指向當前的頭節點,然後更新頭節點為新節點。對於雙向鏈表,還需要設置新節點的前驅節點為 null 並更新原頭節點的 prev 為新節點。

// Singly Linked List
class LinkedList {
prepend(item) {
const newNode = new Node(item);
newNode.next = this.head; // 將 newNode 的 next 指向當前的頭節點 head。
this.head = newNode; // 更新 head 為 newNode。
return this;
}
}

// Doubly Linked List
class DoublyLinkedList {
prepend(item) {
const newNode = new DoublyNode(item);
if (!this.head) { // 如果鏈表為空,則將 newNode 設為頭節點和尾節點。
this.head = newNode;
this.tail = newNode;
} else {
newNode.next = this.head; // 將 newNode.next 指向當前的頭節點
this.head.prev = newNode; //將原頭節點的 prev 指向 newNode
this.head = newNode; // 更新 head 為 newNode
}
return this;
}
}



insert - 從鏈表的特定位置插入元素

在單向鏈表中,這需要遍歷鏈表到特定的 index 元素後,再更新前一節點的 next 指針以指向插入的新節點。並將新節點的 next 與原先的下一個節點連結在一起。而在雙向鏈表中,除了要更新前一個節點的 next 指針,也要更新插入節點的 prev ,以及下一個的 prev 指針。

// Singly Linked List
class LinkedList {
insert(item, index) {
if (index < 0 || index >= this.size) return null;
if (index === 0) return this.prepend(item);
if (index === this.size - 1) this.append(item);

const newNode = new Node(item);
let current = this.head;

for (let i = 0; i < index; i++) { // 找到要插入的前一個節點
current = current.next;
}

newNode.next = current.next; // 將 newNode 的 next 指向當前的頭節點 head。
current.next = newNode; // 更新 head 為 newNode。
this.size++; // 更新長度
return this;
}
}

// Doubly Linked List
class DoublyLinkedList {
insert(item, index) {
if (index < 0 || index >= this.size) return null;
if (index === 0) return this.prepend(item);
if (index === this.size - 1) this.append(item);

const newNode = new Node(item);
let current = this.head;
let previous = null;

for (let i = 0; i < index; i++) { // 找到插入的當前節點
previous = current;
current = current.next;
}

newNode.next = current; // 將 newNode 的 next 指向當前節點。
newNode.prev = previous; // 將新節點的 prev 接上上一個節點
current.prev = newNode; // 將當前節點的 prev 接上 newNode
previous.next = newNode;

this.size++; // 更新長度
return this;
}
}



remove - 從鏈表中刪除給定 index 元素

remove 方法用於從鏈表中刪除元素。在單向鏈表中,需要遍歷整個鏈表,以找到要刪除的元素 index,並更新前一節點的 next 指針以刪除目標節點。而在雙向鏈表中,除了更新前一節點的 next 指針,還需要更新後一節點的 prev 指針。

// Singly Linked List
class LinkedList {
remove(index) {
if (!this.head) return null; // 如果鏈表為空,直接返回
if (index < 0 || index >= this.size) return null; // index 為負數或超出範圍

// 如果頭節點是要刪除的節點
if (index === 0) {
const removedNode = this.head;
this.head = this.head.next; // 更新 head 節點
this.size--;
return removedNode;
}

let current = this.head; // 記錄當前的 Node
let previous = null; // 記錄上一個節點
let counter = 0; // 記錄遍歷到第幾次

// 找到 index 的當前 Node,且確認該節點不為空(超出範圍)​
while (counter < index && current !== null) {
[previous, current] = [current, current.next];
counter++;
}

// 如果確定當前節點不為空​,則將上一個節點的 next 接在 current 的下一個節點
if (current !== null) previous.next = current.next;

this.size--;
return this;
}
}

// Doubly Linked List
class DoublyLinkedList {
remove(index) {
if (!this.head) return null; // 如果鏈表為空,直接返回
if (index < 0 || index >= this.size) return null; // index 為負數

// 如果頭節點是要刪除的節點
if (index === 0) {
const removedNode = this.head;
this.head = this.head.next; // 更新頭節點

if (this.head) this.head.prev = null; // 如果新的頭節點存在,更新其 prev 指針
else this.tail = null; // 如果移除後沒有 Node,則將 tail 一併設為 null

this.size--;
return removedNode;
}

let current = this.head; // 從頭開始計算
let counter = 0; // 用以計數

while (counter < index && current !== null) {
current = current.next;
counter++;
}

const removedNode = current; // 保存被移除的節點

// 處理目標不為最後一個節點時,將下一個節點的 prev 接上上一個節點
if (current.next) current.next.prev = current.prev;

// 如果不為第一個節點,則將前一個節點的 next 接上下一個節點
if (current.prev !== null) current.prev.next = current.next;

// current 為最後一個節點的情況,將前一個節點更新為 tail
if (current === this.tail) this.tail = current.prev;

this.size--;

return removedNode; // 如果沒有找到,返回鏈表本身
}
}



find - 在鏈表中查找元素

find 方法用於在鏈表中查找特定元素。無論是單向鏈表還是雙向鏈表,此方法的基本操作相同:從頭節點開始遍歷,直到找到匹配的元素或達到鏈表末尾。

// Singly Linked List
class LinkedList {
find(value) {
let current = this.head;
while (current) {
if (current.value === value) return current; // 找到匹配的元素,返回該節點
current = current.next;
}
return null; // 沒有找到,返回 null
}
}

// Doubly Linked List
class DoublyLinkedList {
find(value) {
let current = this.head;
while (current) {
if (current.value === value) return current; // 找到匹配的元素,返回該節點
current = current.next;
}
return null; // 沒有找到,返回 null
}
}



reverse - 反轉鏈表

reverse 方法將鏈表的元素順序反轉。在單向鏈表中,此操作需要更改每個節點的 next 指針。在雙向鏈表中,還需要更改每個節點的 prev 指針。

// Singly Linked List with iteration
class LinkedList {
reverse() {
let current = this.head;
let prev = null;
let next = null;

while (current) {
next = current.next; // 保存下一個節點
current.next = prev; // 反轉當前節點的 next 指針
prev = current; // 移動 prev 節點
current = next; // 移動到下一個節點
}

this.head = prev; // 更新頭節點
return this;
}
}

// Singly Linked List with Recursion
class LinkedList {
reverse() {
// 定義 recursion 的函式
function reverseList(head) {
// 確定當前節點或下一個節點為空,則直接返回傳入的當前節點​
if (head === null || head.next === null) return head;
const reversedList = reverseList(head.next);
head.next.next = head; // 將下個節點的 next 接上當前節點
head.next = null; // 將當前節點的 next 暫時移除連接(因為往後執行時會再接上)
return reversedList;
}

// 處理無節點,或僅有一個節點的情況​
if (this.head === null || this.head.next === null) return this;

const originHead = this.head; // 記得原先的 head
this.head = reverseList(this.head);
this.tail = originHead; // 將原本的 head 換成 tail
return this;
}
}

// Doubly Linked List
class DoublyLinkedList {
reverse() {
let current = this.head;
let temp = null;

while (current) {
// 交換 prev 和 next 指針
[current.next, current.prev] = [current.prev, current.next];
current = current.prev; // 移動到原本的下一個節點
}

if (temp) this.head = temp.prev; // 更新頭節點
return this;
}
}

其中,要注意的是遞迴(Recursion)會將欲執行的函式疊在 Stack 中,因此最後執行的函式會被優先處理。換句話說,最後執行的結果就會是終止函式,接著才從終止函式中往回執行,直到處理第一個函式。



Linked List 的應用場景

鏈表(Linked List)作為一種靈活且強大的數據結構,目前搜尋到的有以下是幾個經典應用場景:

  1. 動態數據存儲:由於鏈表可以在運行時動態地增加或減少節點,它們非常適合用於不確定大小的數據集合,例如用戶生成的內容列表或實時消息體系。
  2. 實現隊列和堆棧:鏈表非常適合實現如 Queue 和 Stack 這樣的抽象數據類型。尤其是在需要頻繁進行插入和刪除操作的場景中,Linked List 的效能會優於 Array。
  3. 圖的表示:在圖形算法中,鏈表可用來有效地表示圖的邊和節點,尤其是在實現鄰接表時。
  4. 記憶體管理:操作系統中的記憶體管理,也常使用鏈表來追蹤空閒的記憶體塊。



結語

第一次在了解與實作 Linked List 時,花了非常多時間在了解其核心的觀念,以及如何對 Linked List 的資料結構進行操作,這些都讓一開始學習 Linked List 有著一定的門檻。筆者自己也是在一點一滴的實作中,才慢慢知道如何操作 Linked List,甚至慢慢能夠寫出常見的方法。

因此這一篇文章,希望能夠給剛接觸 Linked List 的人一些切入方向,並透過文章中的逐步說明,了解 Linked List 這一個資料結構的美妙!

如果以上內容有任何錯誤或建議,都歡迎直接留言或來信交流!


Reference 參考資料

此處作為整理前端(Frontend)和相關的 HTML、CSS、JavaScript、React 等前端觀念與技巧,全部都會收錄在這個專題之中。同時也會將相關的技術與反思記錄在此,歡迎各位讀者互相交流。
留言0
查看全部
發表第一個留言支持創作者!
「px」,即像素,是最基本的單位,它常被用於指定字體大小、邊框粗細等。「em」和「rem」通常用於調整相對大小,「em」在子元素中的適用,而「rem」則以根元素為參考。另一方面,「vh」和「vw」分別代表視窗的高度和寬度百分比,特別適合實現響應式設計。「vmin」和「vmax」則根據視窗的最小或最大
在 React 測試生態系統中,React Testing Library 成為了方便好用的選擇,因其強調測試應該關注於使用者的操作與觀察元件行為,而不是測試細節實現。
在軟體領域中,"Thunk" 是一個常用的術語,它指的是一種用於延遲計算,或將運算延後執行的程式碼片段。它通常用於函數式編程,或編譯器的設計中。Redux 透過 createAsyncThunk 實作了該非同步/異步操作,並提供數個 API 協助我們使用 Redux。
React 表單驗證是一種技術與使用者體驗的設計,讓使用者能夠即時檢查輸入的資料並修正,提升使用者的使用體驗,並確保資料的正確性。
useContext 是一種 React hook,讓我們能夠直接取用其他元件的 Context,而無須層層傳遞 props,進而使程式碼簡潔易讀。
在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。本文就以實際的例子來說明各自的差異,以及在未來使用時需要注意哪一些細節。
「px」,即像素,是最基本的單位,它常被用於指定字體大小、邊框粗細等。「em」和「rem」通常用於調整相對大小,「em」在子元素中的適用,而「rem」則以根元素為參考。另一方面,「vh」和「vw」分別代表視窗的高度和寬度百分比,特別適合實現響應式設計。「vmin」和「vmax」則根據視窗的最小或最大
在 React 測試生態系統中,React Testing Library 成為了方便好用的選擇,因其強調測試應該關注於使用者的操作與觀察元件行為,而不是測試細節實現。
在軟體領域中,"Thunk" 是一個常用的術語,它指的是一種用於延遲計算,或將運算延後執行的程式碼片段。它通常用於函數式編程,或編譯器的設計中。Redux 透過 createAsyncThunk 實作了該非同步/異步操作,並提供數個 API 協助我們使用 Redux。
React 表單驗證是一種技術與使用者體驗的設計,讓使用者能夠即時檢查輸入的資料並修正,提升使用者的使用體驗,並確保資料的正確性。
useContext 是一種 React hook,讓我們能夠直接取用其他元件的 Context,而無須層層傳遞 props,進而使程式碼簡潔易讀。
在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。本文就以實際的例子來說明各自的差異,以及在未來使用時需要注意哪一些細節。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
面對老年睡眠問題,了解自然生理變化至關重要。我們的視交叉上核隨年齡老化,影響晝夜節律。學習如何適應這些變化,以及提高褪黑激素水平,是維持健康睡眠模式的關鍵。探索有效策略,保證高齡生活中的良好睡眠質量。
Thumbnail
當我們身邊有人正與憂鬱症抗爭時,我們經常困惑於如何提供幫助。許多時候,即使心懷善意,我們的舉動也可能不但沒有幫助,反而造成更多困擾。這是為什麼呢?憂鬱症不僅僅是思想上的問題,而是一個複雜的心理狀態。當我們試圖強迫一個無力前行的人走路時,最終往往導致對方跌倒。
Thumbnail
在軟體開發領域,MVC(Model-View-Controller)是一種被廣泛使用的設計模式,它有助於將應用程序的不同部分進行組織、分離,以實現更好的可維護性和可擴展性。本文將深入介紹MVC模式的核心概念,以及如何在您的項目中應用這種強大的架構。
Thumbnail
今天我們會開始深入探討到EVM(以太坊虛擬機)上的一點點基礎知識。 若您是一個常常在etherscan.io上追蹤交易的朋友,一定對上面提到的各項資訊並不陌生。但我們觀察一筆交易時會發現,etherscan.io又額外提供了Internel Txns,這又是什麼呢?本篇將一同來探討。
Thumbnail
在前幾篇的文章中。有和各位分享了使用Ganache來模擬一個以太坊主網進行交易,也嘗試使用了web3.py來開發區塊鏈互動程式。相信對您而言,一定有更深入探索區塊鏈交易的需求。本篇想和各位介紹一個Ganache的神奇功能:解鎖任意帳號
Thumbnail
在上一篇文章中,有和您分享了使用Ganache模擬了一個以太坊主網。想必您已經迫不及待的想對它進行更深入的了解了。本篇預計會使用Python來和您建立好的模擬網路進行互動。在開發套件的細節上我們並不會很深入的地為您進行解釋,而會透過一些簡單的例子來引導一個學習的方向。
Thumbnail
關於乙太坊上的交易,通常我們都會到etherscan.io這個網站來進行確認。但初入區塊鏈的朋友們往往看到裡面一大堆的資訊,除了from和to外可能都是一頭霧水。想多了解些相關知識又不知如何下手才好,因此動念寫下一些本人初學區塊鏈時可能用來切入的一些工具,來和各位同好分享。
Thumbnail
為什麼某些公司值得高本益比?而某些卻不行?低本益比就表示物超所值?而高本益比就表示價格昂貴嗎?為什麼有時候買高本益比反而賺錢,而買低本益比反而套牢呢?以下分享我們對於本益比的看法。
Thumbnail
為什麼在台灣我們感受不到權貴的存在?有沒有一本書可以快速讓人建立「到底誰是誰?誰又是誰的誰?」的輪廓?一些天篷權貴的參考資料與大家共享。
Thumbnail
咖啡萃取示意圖 Hello,大家好我是蟹老闆。經過上一篇文章的介紹,我們對於研磨度有了初步的認知。研磨度影響著咖啡粉的表面積大小。而表面積的大小又影響著甚麼呢?咖啡的"萃取"。但好與不好的萃取定義是甚麼?咖啡師或沖煮老手們在沖煮時,又怎麼去理解萃取這件事情呢?我們一起看下去吧~
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
面對老年睡眠問題,了解自然生理變化至關重要。我們的視交叉上核隨年齡老化,影響晝夜節律。學習如何適應這些變化,以及提高褪黑激素水平,是維持健康睡眠模式的關鍵。探索有效策略,保證高齡生活中的良好睡眠質量。
Thumbnail
當我們身邊有人正與憂鬱症抗爭時,我們經常困惑於如何提供幫助。許多時候,即使心懷善意,我們的舉動也可能不但沒有幫助,反而造成更多困擾。這是為什麼呢?憂鬱症不僅僅是思想上的問題,而是一個複雜的心理狀態。當我們試圖強迫一個無力前行的人走路時,最終往往導致對方跌倒。
Thumbnail
在軟體開發領域,MVC(Model-View-Controller)是一種被廣泛使用的設計模式,它有助於將應用程序的不同部分進行組織、分離,以實現更好的可維護性和可擴展性。本文將深入介紹MVC模式的核心概念,以及如何在您的項目中應用這種強大的架構。
Thumbnail
今天我們會開始深入探討到EVM(以太坊虛擬機)上的一點點基礎知識。 若您是一個常常在etherscan.io上追蹤交易的朋友,一定對上面提到的各項資訊並不陌生。但我們觀察一筆交易時會發現,etherscan.io又額外提供了Internel Txns,這又是什麼呢?本篇將一同來探討。
Thumbnail
在前幾篇的文章中。有和各位分享了使用Ganache來模擬一個以太坊主網進行交易,也嘗試使用了web3.py來開發區塊鏈互動程式。相信對您而言,一定有更深入探索區塊鏈交易的需求。本篇想和各位介紹一個Ganache的神奇功能:解鎖任意帳號
Thumbnail
在上一篇文章中,有和您分享了使用Ganache模擬了一個以太坊主網。想必您已經迫不及待的想對它進行更深入的了解了。本篇預計會使用Python來和您建立好的模擬網路進行互動。在開發套件的細節上我們並不會很深入的地為您進行解釋,而會透過一些簡單的例子來引導一個學習的方向。
Thumbnail
關於乙太坊上的交易,通常我們都會到etherscan.io這個網站來進行確認。但初入區塊鏈的朋友們往往看到裡面一大堆的資訊,除了from和to外可能都是一頭霧水。想多了解些相關知識又不知如何下手才好,因此動念寫下一些本人初學區塊鏈時可能用來切入的一些工具,來和各位同好分享。
Thumbnail
為什麼某些公司值得高本益比?而某些卻不行?低本益比就表示物超所值?而高本益比就表示價格昂貴嗎?為什麼有時候買高本益比反而賺錢,而買低本益比反而套牢呢?以下分享我們對於本益比的看法。
Thumbnail
為什麼在台灣我們感受不到權貴的存在?有沒有一本書可以快速讓人建立「到底誰是誰?誰又是誰的誰?」的輪廓?一些天篷權貴的參考資料與大家共享。
Thumbnail
咖啡萃取示意圖 Hello,大家好我是蟹老闆。經過上一篇文章的介紹,我們對於研磨度有了初步的認知。研磨度影響著咖啡粉的表面積大小。而表面積的大小又影響著甚麼呢?咖啡的"萃取"。但好與不好的萃取定義是甚麼?咖啡師或沖煮老手們在沖煮時,又怎麼去理解萃取這件事情呢?我們一起看下去吧~