返回課程

剖析表達式

一個算術表達式包含 2 個數字和中間的一個運算子,例如

  • 1 + 2
  • 1.2 * 3.4
  • -3 / -6
  • -2 - 2

運算子為:"+""-""*""/"

在開頭、結尾或各部分之間可能會有額外的空格。

建立一個函式 parse(expr),它會接收一個表達式並傳回一個包含 3 個項目的陣列

  1. 第一個數字。
  2. 運算子。
  3. 第二個數字。

例如

let [a, op, b] = parse("1.2 * 3.4");

alert(a); // 1.2
alert(op); // *
alert(b); // 3.4

數字的正規表示式為:-?\d+(\.\d+)?。我們在先前的任務中建立過這個正規表示式。

運算子為 [-+*/]。連字號 - 在方括號中排第一,因為在中間會表示字元範圍,而我們只想要字元 -

斜線 / 應在 JavaScript regexp /.../ 中跳脫,我們稍後會執行此操作。

我們需要一個數字、一個運算子,然後再一個數字。它們之間可以有空格。

完整的正規表示式:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?

它有 3 個部分,中間有 \s*

  1. -?\d+(\.\d+)? – 第一個數字,
  2. [-+*/] – 運算子,
  3. -?\d+(\.\d+)? – 第二個數字。

若要讓這些部分中的每個部分成為結果陣列的個別元素,我們將它們括在括號中:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)

實際操作

let regexp = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;

alert( "1.2 + 12".match(regexp) );

結果包括

  • result[0] == "1.2 + 12" (完整比對)
  • result[1] == "1.2" (第一組 (-?\d+(\.\d+)?) – 第一個數字,包括小數部分)
  • result[2] == ".2" (第二組(\.\d+)? – 第一個小數部分)
  • result[3] == "+" (第三組 ([-+*\/]) – 運算子)
  • result[4] == "12" (第四組 (-?\d+(\.\d+)?) – 第二個數字)
  • result[5] == undefined (第五組 (\.\d+)? – 沒有最後一個小數部分,因此未定義)

我們只想要數字和運算子,不包括完整比對或小數部分,因此我們稍微「清理」一下結果。

完整比對(陣列的第一個項目)可透過移動陣列 result.shift() 來移除。

包含小數部分(數字 2 和 4)(.\d+) 的組可透過在開頭加上 ?: 來排除:(?:\.\d+)?

最終解決方案

function parse(expr) {
  let regexp = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;

  let result = expr.match(regexp);

  if (!result) return [];
  result.shift();

  return result;
}

alert( parse("-1.23 * 3.45") );  // -1.23, *, 3.45

除了使用非擷取 ?: 之外,我們也可以命名組,如下所示

function parse(expr) {
  let regexp = /(?<a>-?\d+(?:\.\d+)?)\s*(?<operator>[-+*\/])\s*(?<b>-?\d+(?:\.\d+)?)/;

  let result = expr.match(regexp);

  return [result.groups.a, result.groups.operator, result.groups.b];
}

alert( parse("-1.23 * 3.45") );  // -1.23, *, 3.45;