欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript實(shí)現(xiàn)計(jì)算器的四則運(yùn)算功能

 更新時(shí)間:2022年02月08日 16:46:12   作者:Ashy996  
這篇文章主要為大家介紹了如何通過(guò)JavaScript實(shí)現(xiàn)計(jì)算器的四則運(yùn)算功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起動(dòng)手試試

一、需求 + 最終實(shí)現(xiàn)

注:只是前端實(shí)現(xiàn)

1. 需求

需求來(lái)源是因?yàn)橛幸粋€(gè)做嵌入式 C/C++的基友做了一個(gè)遠(yuǎn)程計(jì)算器。 需求是要求支持輸入一個(gè)四則混合運(yùn)算公式的字符串,返回計(jì)算后的結(jié)果。

想看看用 C/C++封裝過(guò)的 JavaScript 如果要實(shí)現(xiàn)這樣一個(gè)功能最終效果(文章后我會(huì)討論這兩種實(shí)現(xiàn)思路,還望各位看官可以提出一些優(yōu)化方案以及建議之類的~)。

2. 說(shuō)明:利用了字符串(split、replace)和數(shù)組(splice)的方法。

主要是用到字符串切分為數(shù)組的方法 split、以及數(shù)組中插入刪除的方法 splice。 字符串正則 replace 方法是考慮到用戶的輸入習(xí)慣可能有所不同,例如 1+2*3/4 與 3 * 7 + 229。

支持:

  • 基礎(chǔ)四則運(yùn)算 3+6*5/6-3;
  • 小數(shù)四則運(yùn)算 3.14 + 6 * 5 / 6 - 3.5;
  • 高位四則運(yùn)算 99 * 94 - 6.35 + 100 / 1024;
  • 多次四則運(yùn)算 3 * 3 + 3 * 16 - 7 - 5 + 4 / 2 + 22;
  • 以上綜合

不支持:

  • 帶括號(hào)的運(yùn)算 1 * (2 - 3);
  • 其他數(shù)學(xué)運(yùn)算

3. 代碼實(shí)現(xiàn)

/**
 * js四則混合運(yùn)算計(jì)算器  功能實(shí)現(xiàn)(約20行+ 面條代碼)
 * @param {string} str 輸入的四則運(yùn)算字符串
 * @return {number} 輸出 結(jié)果
 */
const calculator = (str) => {
  // 定義添加字符函數(shù)
  const add = (arr, symbol) => {
    let length = arr.length;
    while (length > 1) {
      arr.splice(length - 1, 0, symbol); // 在每一項(xiàng)后面添加對(duì)應(yīng)的運(yùn)算符
      length--;
    }
    return arr; // 目的是得到一個(gè)改變長(zhǎng)度的數(shù)組
  }
  const array = add(str.replace(/\s*/g,"").split('+'), '+').map(it => add(it.split('-'), '-').map(it => add(it.split('*'), '*').map(it => add(it.split('/'), '/')))).flat(3);;
  // 先運(yùn)算乘除法
  ['*', '/'].map(it => {
    while (array.includes(it)) {
      const index = array.findIndex(o => o === it);
      index > 0 && it === '*' ? array.splice(index - 1, 3, (Number(array[index - 1]) * Number(array[index + 1]))) : array.splice(index - 1, 3, (Number(array[index - 1]) / Number(array[index + 1])));
    }
  })
  // 再執(zhí)行加減法,即從左至右的計(jì)算
  while (array.length > 1) {
    array[1] === '+' ? array.splice(0, 3, (Number(array[0]) + Number(array[2]))) : array.splice(0, 3, (Number(array[0]) - Number(array[2])));
  }
  return Number(array[0]).toFixed(2);
}

如果對(duì) ES6 語(yǔ)法還算熟悉的話,應(yīng)該可以輕松閱讀代碼的。 想必你也注意到了,這也是其中令我比較糾結(jié)的:在日常開發(fā)中,是否該經(jīng)常寫一些面條代碼呢?

二、實(shí)現(xiàn)步驟

(輕松理解的大佬可以直接跳到:步驟3)

1.實(shí)現(xiàn)最基礎(chǔ)的加減乘除

2.支持高位數(shù)的運(yùn)算

3.支持多次的運(yùn)算

4.支持...

如果是初學(xué)者,建議跟著敲一下過(guò)程(或者 f12 驗(yàn)證 + 調(diào)試),編程能力某種角度下一定是建立在代碼量之下的。

1. 版本一:實(shí)現(xiàn)基礎(chǔ)加減乘除

// 版本一
const calculator = ((str) => {
  // 定義最基礎(chǔ)的加減乘除
  const add = (a, b) => a + b;
  const sub = (a, b) => a - b;
  const mul = (a, b) => a * b;
  const div = (a, b) => a / b;
  // 將輸入的字符串處理為 數(shù)組
  const array = str.split('');
  // **【處理基本四則運(yùn)算】
  ['*', '/', '+', '-'].map(it => {
    const index = array.findIndex(o => o === it);
    if (index > 0) {
      switch (it) {
        case '*':
          array[index + 1] = mul(array[index - 1], array[index + 1]);
          break;
        case '/':
          array[index + 1] = div(array[index - 1], array[index + 1]);
          break;
        case '+':
          array[index + 1] = add(Number(array[index - 1]), Number(array[index + 1]));
          break;
        case '-':
          array[index + 1] = sub(Number(array[index - 1]), Number(array[index + 1]));
          break;
      }
      array.splice(index - 1, 2)
    }
  })
  // return array[0];
  console.log('返回值:', array[0]);
})('3+6*5/6-3')

// 返回值: 5

這樣就實(shí)現(xiàn)了一個(gè)四則混合運(yùn)算的計(jì)算器!

但是這個(gè)計(jì)算器很雞肋,只是一個(gè)最基礎(chǔ)的功能上的實(shí)現(xiàn)。即:只可以運(yùn)行一位數(shù)數(shù)字的加減乘除混合運(yùn)算。

其實(shí)第一步的想法是,利用數(shù)組的性質(zhì),通過(guò)操作數(shù)組來(lái)操作單次的四則運(yùn)算。其中數(shù)組的遍歷,我優(yōu)先 *, / 法,緊接著是 +,- 法。 這其實(shí)是有問題的,乘除法在實(shí)際運(yùn)算中的優(yōu)先級(jí)并不明顯,可以說(shuō)是不怎么影響運(yùn)算的結(jié)果(在文章最后一個(gè)版本實(shí)現(xiàn)涉及到性能上的討論時(shí)會(huì)詳談),但是加減法就會(huì)有影響了:必須是從左至右的實(shí)現(xiàn),否則影響運(yùn)算的結(jié)果(這里不多贅述)。

【處理基本四則運(yùn)算】

首先處理字符串為數(shù)組 const array = str.split('');這一步代碼舉例說(shuō)明:

(圖一)

  1. 在處理字符串的時(shí)候,可以看到 '3+6*5/6-3' 處理成了 ['3', '+', '6', '*', '5', '/', '6', '-', '3']。
  2. 然后在版本一代碼中,可以看到我處理運(yùn)算的執(zhí)行順序是 ['*', '/', '+', '-'],所以版本一只支持加減乘除一次運(yùn)算;
  3. const index = array.findIndex(o => o === it); 這一步找到步驟 2 中的符號(hào)所在數(shù)組中的位置(說(shuō)明一下,只用字符串的方法也可以實(shí)現(xiàn),即找到字符串的位置,然后操作也可,只是數(shù)組更常用,也更容易理解)
  4. 觀察處理后的數(shù)組,符號(hào)總是隔一位出現(xiàn)的,即便是優(yōu)先級(jí)較高的 *、/ 法,也是符號(hào)所在的位置的前一項(xiàng)與后一項(xiàng)的運(yùn)算結(jié)果。 array[index + 1] = mul(array[index - 1], array[index + 1]); 將符號(hào)所在的下一項(xiàng)的值為調(diào)用對(duì)應(yīng)的操作函數(shù)的運(yùn)算結(jié)果;
  5. 刪除符號(hào)位與第一項(xiàng):array.splice(index - 1, 2)
  6. 這時(shí)候可以看到最初定義的 array 數(shù)組一直在改變,以 node 環(huán)境下的打印結(jié)果為例(注意觀察運(yùn)算數(shù)組):

(圖二)

可以看到每次打印都會(huì)打印初始數(shù)組以及通過(guò) splice 方法處理之后的結(jié)果。

弊端:此版本不支持多次運(yùn)算,即四則混合運(yùn)算只能執(zhí)行一次。同時(shí),也不能夠支持高位運(yùn)算。

2. 版本二:實(shí)現(xiàn)高位數(shù)的運(yùn)算

在圖一中

如果是涉及高位(個(gè)位以上)的數(shù)值運(yùn)算字符串的話,單純的使用 split('') 方法會(huì)把兩位數(shù)數(shù)值,處理成數(shù)組的兩項(xiàng),即影響運(yùn)算結(jié)果。

所以我需要一個(gè)方法,在接收一個(gè)字符串以后,得到我想要的字符串:

(圖三)

如圖三所述, ary 即所需。

所以,圖三中由 str 到 ary 的過(guò)程就是本次版本所需要實(shí)現(xiàn)的:

/**
 * 實(shí)現(xiàn)字符串的數(shù)組化分割
 * @param {string} strs 輸入的字符串 : '12*33/3+9+10'
 * @returns 數(shù)組 ['12', '*', '33', '/',  '3', '+', '9',  '+', '10']
 */
const split = () => {
  const result = str.split('+')  // 遇到 + 處理為數(shù)組
    .map(it => {
      return it.split('-') // 遇到 - 處理為數(shù)組
        .map(it => {
          return it.split('*') // 遇到 * 處理為數(shù)組
            .map(it => {
              return it.split('/') // 遇到 / 處理為數(shù)組
            })
        })
    })
  return result.flat(3);
}

我在設(shè)計(jì)這個(gè)算法的時(shí)候,一時(shí)間也沒有太好的思路和想法,該函數(shù)處理字符串為一個(gè)多維數(shù)組,然后再將數(shù)組扁平化處理。如圖四所示:

(圖四)

圖四中,執(zhí)行該函數(shù),得到一個(gè)多維數(shù)組(其實(shí)最高也只有三維數(shù)組),返回值 result 打印出來(lái)的結(jié)果可以看到,基本滿足所需要的數(shù)組:['31', '+', '62', '*', '5', '/', '6', '-', '3'] 。

接下來(lái),為其帶上運(yùn)算符:

/**
 * 定義添加字符函數(shù)
 * @param {string[]} result 傳入的數(shù)組 ['31', '62*5/6-3']
 * @param {string} symbol 傳入的運(yùn)算符
 * @returns 數(shù)組 ['31', '+', '62*5/6-3']
 */
  const add = (result, symbol) => {
    let length = result.length;
    while (length !== 1) {
      result.splice(length - 1, 0, symbol); // 在每一項(xiàng)后面添加對(duì)應(yīng)的運(yùn)算符
      length--;
    }
    return result; // 目的是得到一個(gè)改變長(zhǎng)度的數(shù)組
  }

比如傳入 ['31', '62*5/6-3'] ,只需要在第一項(xiàng)之后補(bǔ) '+' 即可。

實(shí)現(xiàn)的目的是考慮到多次運(yùn)算的時(shí)候,為每一個(gè)因?yàn)?'+' 分割的數(shù)組中的項(xiàng)添加運(yùn)算符,所以這里用到了 while 循環(huán)語(yǔ)句,并且由一個(gè)變量 length 控制(也可以遍歷數(shù)組或者 for 循環(huán)數(shù)組實(shí)現(xiàn)這一步操作);

檢驗(yàn)結(jié)果,如圖五所示:

(圖五)

這樣就實(shí)現(xiàn)了這個(gè)任意長(zhǎng)度數(shù)值數(shù)組輸入時(shí),返回帶符號(hào)的數(shù)組。

【回顧一下】:

上面兩個(gè)函數(shù)的整體實(shí)現(xiàn)就是,實(shí)現(xiàn)了根據(jù)符號(hào)分割數(shù)組,根據(jù)傳入的數(shù)組與符號(hào)添加符號(hào):

結(jié)合兩個(gè)函數(shù),并且簡(jiǎn)化一下代碼(其實(shí)我個(gè)人還是喜歡寫面條代碼的,只是可能不利于閱讀,但是看起來(lái)舒服一些~):

  // 定義添加字符函數(shù)
  const add = (result, symbol) => {
    let length = result.length;
    while (length !== 1) {
      result.splice(length - 1, 0, symbol); // 在每一項(xiàng)后面添加對(duì)應(yīng)的運(yùn)算符
      length--;
    }
    return result; // 目的是得到一個(gè)改變長(zhǎng)度的數(shù)組
  }
  const array = (strs = str) =>
    add(strs.split('+'), '+').map(it =>
      add(it.split('-'), '-').map(it =>
        add(it.split('*'), '*').map(it =>
          add(it.split('/'), '/')
        )
      )
    ).flat(3);

即,任意運(yùn)算字符串的傳入都可以處理為所需數(shù)組如圖六所示:

(圖六)

array 函數(shù)在后面直接把內(nèi)部處理函數(shù)的返回值綁定了。

對(duì)于上述算法的設(shè)計(jì)如果有更好的實(shí)現(xiàn)還希望有朋友可以指出,大家互相之間可以學(xué)習(xí)一下。

3. 支持多次的運(yùn)算

回到版本一,目前的實(shí)現(xiàn)只支持一次的四則混合運(yùn)算,更合理的實(shí)現(xiàn)應(yīng)該是先運(yùn)算乘除法,再運(yùn)算加減法,而且先出現(xiàn)的先執(zhí)行。

完整運(yùn)算代碼:

const calculator = (str) => {
  const add = (result, symbol) => {
    let length = result.length;
    while (length > 1) {
      result.splice(length - 1, 0, symbol);
      length--;
    }
    return result;
  }
  const array = add(str.replace(/\s*/g, "").split('+'), '+').map(it => add(it.split('-'), '-').map(it => add(it.split('*'), '*').map(it => add(it.split('/'), '/')))).flat(3);;
  // 先運(yùn)算乘除法
  while (array.includes('*') || array.includes('/')) {
    const itSymbol = array.find(o => o === '*' || o === '/');
    const index = array.findIndex(o => o === '*' || o === '/');
    index > 0 && itSymbol === '*' ? array.splice(index - 1, 3, (Number(array[index - 1]) * Number(array[index + 1]))) : array.splice(index - 1, 3, (Number(array[index - 1]) / Number(array[index + 1])));
  }
  // 再執(zhí)行加減法,即從左至右的計(jì)算
  while (array.length > 1) {
    array[1] === '+' ? array.splice(0, 3, (Number(array[0]) + Number(array[2]))) : array.splice(0, 3, (Number(array[0]) - Number(array[2])));
  }
  return Number(array[0]).toFixed(2);
}

注:有必要說(shuō)明一下,因?yàn)閭€(gè)人習(xí)慣不同,所以輸入帶有空格情況,所以這里在處理字符串之前首先用到了一個(gè)正則表達(dá)式 str.replace(/\s*/g, "") (去除空格)。
等等,我剛剛想到了什么?

如果大家都在輸入的時(shí)候,自覺加一個(gè)空格隔開運(yùn)算符與數(shù)值的話~

是不是我之前版本二中的字符串處理就可以省一下啦??!

所以作為開發(fā)者,一定要 注意規(guī)范,注意規(guī)范,注意規(guī)范!

上面完整代碼中,

  1. 簡(jiǎn)化了調(diào)用加減乘除函數(shù),改而用 array.splice(index - 1, 3, 運(yùn)算) 運(yùn)算直接可以操作兩參數(shù)。
  2. 得到了可操作數(shù)組 array 后,先執(zhí)行乘除法,再執(zhí)行加減法。
  3. 乘除法里先判斷 是否存在 * 或 / 兩個(gè)符號(hào),如果存在,則找到符號(hào)的位置,運(yùn)算每一個(gè)乘除法,按數(shù)學(xué)的思維,誰(shuí)在前先運(yùn)算誰(shuí)(但我依然規(guī)定了先運(yùn)算所有的 *,再運(yùn)算所有的 / 這種方式作為最終實(shí)現(xiàn)并放到了文章最開始。因?yàn)檎嬲谶\(yùn)算的時(shí)候,乘除法的先后執(zhí)行順序得到的結(jié)果似乎并沒有什么關(guān)系,而與我而言,我感覺在這套實(shí)現(xiàn)中,includes 與 find 的多次執(zhí)行可能對(duì)性能上的損耗更大一些)
  4. 當(dāng)所有的乘除法執(zhí)行完畢后,就只剩下加減法了,這時(shí)候按順序執(zhí)行加減法即可。
  5. 最后保留兩位小數(shù)。

其實(shí)這段代碼更符合數(shù)學(xué)思維,先運(yùn)算乘除法(誰(shuí)在前先運(yùn)算誰(shuí)),再運(yùn)算加減法。

如果大家有一些其他的想法,可以一起討論一下~

三、思考

后端思維

1. 實(shí)現(xiàn)逆波蘭表達(dá)式

1+2*3 這是一個(gè)中綴表達(dá)式,人腦很容易計(jì)算,結(jié)果為7。當(dāng)然計(jì)算機(jī)也很容易處理這個(gè)表達(dá)式。

當(dāng)我們輸入1.2+(-1+3*1)*2,人腦需要思考一下,但計(jì)算機(jī)還是可以通過(guò)固定代碼快速計(jì)算出結(jié)果。

但是,當(dāng)我們隨機(jī)輸入中綴表達(dá)式 XXX 時(shí),人腦可以手動(dòng)計(jì)算出結(jié)果,計(jì)算機(jī)不可能一個(gè)表達(dá)式一個(gè)代碼塊,那么計(jì)算機(jī)怎么實(shí)現(xiàn)通用且快速的計(jì)算呢?答案就是后綴表達(dá)式。

中綴和后綴表達(dá)式在數(shù)據(jù)結(jié)構(gòu)里有涉及到,我就不講概念了,下面手動(dòng)模擬一下計(jì)算機(jī)計(jì)算字符串表達(dá)式的過(guò)程。

2. 中綴表達(dá)式 => 后綴表達(dá)式

計(jì)算機(jī)易于計(jì)算的其實(shí)是后綴表達(dá)式,整個(gè)過(guò)程就是將已知的中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式。

2.1 定義【操作數(shù)?!亢汀具\(yùn)算符?!浚?/p>

2.2 運(yùn)算符棧出棧,操作數(shù)棧入棧,上式即可成為: 123*+ 這就是一個(gè)簡(jiǎn)單的后綴表達(dá)式

2.3 計(jì)算機(jī)在運(yùn)算后綴表達(dá)式時(shí):運(yùn)算符棧讀取 *,操作數(shù)棧讀取 2,3 得到結(jié)果 6,;然后運(yùn)算 1 + 6 = 7。

3. 較復(fù)雜的表達(dá)式計(jì)算

入棧:

( 后的- 作為負(fù)數(shù)進(jìn)入操作數(shù)棧(如果作為符號(hào)位,后面計(jì)算會(huì)成1.1 - 30);

與上文一樣,只是不同之處在于運(yùn)算符棧,遇到 ( 以后先進(jìn)入運(yùn)算符棧;

直到遇到 )

3.1 使用 # 符號(hào)區(qū)分 負(fù)數(shù)、高位數(shù)、以及符號(hào)位

3.2 所以得到的后綴表達(dá)式為: #-1.1#3#10#*#+#2#/#;

出棧過(guò)程:

  • 計(jì)算沒有遇到符號(hào)位一 # 為準(zhǔn),依次取出
  • 直到遇到符號(hào)位之前,取出了三個(gè)數(shù) -1.1 3 10
  • 遇到符號(hào)位以后,在結(jié)果棧中出棧,與符號(hào)計(jì)算結(jié)果 => 結(jié)果棧變?yōu)?-1.1 30
  • 依此計(jì)算:

  • 出棧完成后,就實(shí)現(xiàn)了對(duì)逆波蘭表達(dá)式的求值運(yùn)算。

前端思維

我拿到【實(shí)現(xiàn)一個(gè)支持四則混合運(yùn)算的計(jì)算器】需求以后,首先想到的是字符串轉(zhuǎn)數(shù)組,然后去操作數(shù)組,然后由于高級(jí)語(yǔ)言的特性,很多方法已經(jīng)封裝完成,所以實(shí)現(xiàn)起來(lái)相對(duì)容易一些。

當(dāng)然,也可以采用前端的代碼,用著后端的思維去實(shí)現(xiàn)也是一個(gè)選擇。

結(jié)束

其實(shí)這個(gè)計(jì)算器與電腦中的常規(guī)計(jì)算器并無(wú)區(qū)別,后期可以考慮的升級(jí)方式

  • 實(shí)現(xiàn) () 的優(yōu)先級(jí)功能;
  • 其他數(shù)學(xué)計(jì)算等等...

總結(jié)一下就是,后端的實(shí)現(xiàn)在性能上無(wú)與倫比,尤其是代碼的執(zhí)行速度上,我這里沒有測(cè)試數(shù)據(jù),但是如果你有刷力扣的話,你可以看看同樣的算法,JS 的空間復(fù)雜度【內(nèi)存消耗】,是 C/C++ 等更底層的語(yǔ)言消耗數(shù)倍。

同樣的,如果用 C/C++ 底層語(yǔ)言 + 后端思維 去實(shí)現(xiàn)【開辟內(nèi)存】、將中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式所用定義的【操作數(shù)?!俊ⅰ具\(yùn)算符?!?;以及各種棧頂棧底的【指針操作】;外加如果交由用戶使用涉及到的設(shè)置【代理】,網(wǎng)絡(luò)協(xié)議封裝等等... (最終總代碼量數(shù)百行)

我將之稱為業(yè)務(wù)復(fù)雜度(hhh)對(duì)比前端 20行+ 的代碼實(shí)現(xiàn)~

更底層語(yǔ)言需要考慮的東西比較多,所以實(shí)現(xiàn)起來(lái)花費(fèi)的人力相對(duì)更多,同樣的收獲的對(duì)電腦性能消耗性價(jià)比也是前端 JS 不可比擬的

不當(dāng)之處還望各位指正~

以上就是JavaScript實(shí)現(xiàn)計(jì)算器的四則運(yùn)算功能的詳細(xì)內(nèi)容,更多關(guān)于JavaScript計(jì)算器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論