iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 3
0

今天的主題是 Arithmetic operators,就是讓我們來做一些數學運算吧!實作上是件很簡單的事,我們就只是把幾個變數拿來加減乘除並且印出結果,你一定想說阿不就那樣,但是如果你有 Follow 我先前的 day 0 跟 day 1,嘿嘿!你就會知道你會得到的絕對比你想像中的多啦!在這一篇,我們會深入地探討到底當你在這些語言寫出加減乘除的時候究竟背後是發生什麼事呢?讓我們看下去!


今日挑戰內容

相當容易,就是一些加減乘除,把主餐的價格加上一些是主餐比例的小費和稅囉!(淺入深出?)

Scala

import scala.io.StdIn._

object Solution {
    def main(args: Array[String]) = {
        val mealCost = readDouble
        val tipPercent = readInt
        val taxPercent = readInt
        val bill = mealCost * (1 + (tipPercent + taxPercent) / 100.toDouble)
        val totalCost = math.round(bill).toInt
        println(s"The total cost is $totalCost.")
    }
}
  • 今天的程式邏輯本身相信應該不難理解,就是把一些變數做加減乘除,其中我們來談談 100.toDouble 。首先先來說為什麼要 toDouble ,因為不這麼做, (tipPercent + taxPercent) / 100 會等於 0 (如果tipPercent + taxPercent 小於 100,那麼因為整數相除最後在 Scala 只會留下整數的部分,如果沒有特別指定型態的話。那為什麼 100 會有 toDouble 這個方法可以用呢?因為在 Scala,任何東西都是 Object,包含 100也是。 100Int 這個 Class 的 instance,我們可以在這裡 找到 toDouble 這個 method。而 Scala 又提供了語法糖讓我們可以不用寫 toDouble() 而可以直接寫 toDouble ,只要 method 本身沒有任何參數。
  • 這裡 我們可以看到 Int 本身還有很多 method,像是 + , - , * , / 等等,所以當我們如果想要把 ab 兩個整數相加,應該要寫成 a.+(b) ,但是這樣寫又跟平常我們人類的寫法好像有點出入,這時候就又再搭配 Scala 的一個語法糖:只要 method 只吃一個參數,那麼就可以用 Infix notation,也就是寫成 a + b 囉!最後我們看到在官方文件中,光 + 的定義就有好多,不同的是,參數的類型不同,這就是所謂的 method overloading 了。
    math 是 Scala 內置的library,裡面提供了許多跟數學運算相關的 method,像是本例子中的 round 就是方便我們進行四捨五入囉!其他還有很多可以參考這裡
  • 最後我們來看 s"..." ,這個叫做 String interpolation,能夠讓我們去 Create string。在這裡 $totalCost 就是去存取 totalCost 的值,所以假使 totalCost100,那麼這個 String 就會是 The total cost is 100 。更厲害的是,我們還可以直接在裡頭做運算,像是 s"1+1=${1+1}" ,只要用大括號括起來的地方,就可以放入表達式,最後表達式回傳的結果就會變成字串的一部分,而這個例子最後會得到 1+1=2 的字串。除了 s 之外,還有一些有趣的,像是 f 可以讓我們格式化字串,甚至你也可以自定義唷!有點複雜,有興趣的可以參考這裡囉!

Python 3

def solve(meal_cost, tip_percent, tax_percent):
    tip = (meal_cost * tip_percent) / 100
    tax = (meal_cost * tax_percent) / 100
    totalCost = int(round(meal_cost + tip + tax))
    print(totalCost)

meal_cost = float(input())
tip_percent = int(input())
tax_percent = int(input())
solve(meal_cost, tip_percent, tax_percent)
  • 跟之前一樣,我們透過 input() 並且 Cast 成相對應的 Type 後,丟到我們自定義的 Function solve 。在 Python 裡面,定義 Function 不像其他語言通常會有大括號。Python 是用縮排來代替大括號去闡述 Code block 的存在。
  • 不同於 Scala,在 Python 3 即便是整數相除後有小數點,也不會被省略掉。所以我們這邊就可以很放心的直接進行加減乘除的運算。而 round 是內建的函數,可以幫助我們對參數取四捨五入。
  • 既然都談到 Operator,最後讓我們來談談 Python 的 Operator overloading 吧!你有沒有想過,為什麼我讓兩個整數相加會得到整數,而對字串相加會得到字串的串接呢?這就要提到 Python 的每一個 Class,其實都有一些 Special methods,只要像是這個形式: __xxx__ ,前後都有雙底線的,通常都是默默幫我們把一些事情給做掉的 Special methods,才造就 Python 如此簡潔啦!那如果假設我們今天自定義了一個 Class,我們也想要讓這個 Class 產生的兩個 instance (例如 ab),可以這樣操作 a + b 呢?那我們就要在 Class 裡頭去定義 __add__ 這個 Function 啦!這樣我們就能夠自己決定,對於這個 Class 來說,所謂相加是做什麼事情。當然還有很多很多,例如 __sub__ (相減 -), __ge__ (大於 >)。有興趣可以參考這個列表 ,超多 magic 的啦!

Golang

package main

import (
	"fmt"
	"os"
	"bufio"
	"strconv"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	meal_cost, _ := strconv.ParseFloat(scanner.Text(), 64)
	scanner.Scan()
	tip_percent, _ := strconv.ParseInt(scanner.Text(), 10, 64)
	scanner.Scan()
	tax_percent, _ := strconv.ParseInt(scanner.Text(), 10, 64)

	var total_cost float64 = meal_cost + meal_cost * float64(tip_percent + tax_percent) / 100 
	fmt.Printf("The total cost is %.1f dollars.", total_cost)
}
  • Golang 的部分跟上一篇類似,我們已經講解了 Scanner , strconv 等等的內容,所以如果你還不清楚這些用法的話,可以回 Day 1 的文章,裡頭有詳細的說明。這裡要注意的是 meal_cost * float64(tip_percent + tax_percent) ,如果我們沒有用 float64(..) 做 casting 的話,Compiler 會不爽,因為這兩個 Type 的值是不能直接相加的 (int64float64)。
  • 至於 Golang 本身有沒有提供 Method/Operator overloading 呢?答案是沒有唷!可以參考這裡的解釋 ,簡單來說,Golang 覺得 Overloading 只是為了方便,卻會增加複雜度,因為同樣名稱的 Method 卻有不同的 Signature。好處是,通常看 Golang 的 Code 都滿好懂的,不會有同名的 Method 卻有不同行為的狀況。既然沒有 overloading,如果我們自定義了一個 Struct (Golang 沒有 Class),要把兩個 Struct 的 instance "相加",就得自定義 Method 例如 Add 來處理,而不能直接用 + 了。

Rust

use std::io::stdin;

fn solve(meal_cost: f64, tip_percent: i32, tax_percent: i32) -> f64 {
    let result = meal_cost * ( 1.0 + ( tip_percent as f64 / 100.0 ) + ( tax_percent as f64 / 100.0 ));
    result
}

fn main() {

    let mut meal_cost_input = String::new();
    let mut tip_percent_input = String::new();
    let mut tax_percent_input = String::new();

    stdin().read_line(&mut meal_cost_input)
      .expect("Failed to read your input");

    let meal_cost_input: f64 = match meal_cost_input.trim().parse(){
        Ok(num) => num,
        Err(_) => 0.0
    };

    stdin().read_line(&mut tip_percent_input)
        .expect("Failed to read your input");

    let tip_percent_input: i32 = match tip_percent_input.trim().parse(){
        Ok(num) => num,
        Err(_) => 0
    };
  
    stdin().read_line(&mut tax_percent_input)
        .expect("Failed to read your input");

    let tax_percent_input: i32 = match tax_percent_input.trim().parse(){
        Ok(num) => num,
        Err(_) => 0
    };
    
    let total_cost: f64 = solve(meal_cost_input, tip_percent_input, tax_percent_input);
    println!("{}", total_cost);
}
  • 把值讀進來的部分在先前都已經解釋過了,可以參考上一篇。我們先來看看我們定義的 Function solve ,可以看到這個 Function 的 Signature 最後有個 -> f64 ,這是在定義 Function 最後計算所得到的值 Type 是 f64 ,而這個 Function 的結果就是最後一行的 result 。我們在這裡沒有看到 return 這個關鍵字,因為 Rust 是 Expression-based language,每個表達式在計算完後都會產生一個值,而在 Function 內通常就是把最後一行當作結果,而且不需要加上分號。當然如果需要 Early return 的時候,例如遇到錯誤就先 Return,還是可以用上 return 這個關鍵字,但是其實在 Rust 的世界,幾乎是很少看到 Return 的。可以參考 Wiki 對於 Expression based language 的說明 (Scala 也是唷)
  • 再來我們看到 Function solve 裡頭有像 tip_percent as f64 ,這是 Rust 的 Explicit conversion,就是 Casting 的效果啦!(這裡是把 i32 cast 成 f64)
  • 最後我們來看看 Rust 可不可以做 Operator overloading 呢?答案是可以的唷!在 Rust 裡,有特定的 Operator 是可以被 Overloading 的,像是 + ,我們可以讓自定義的 Struct 也能夠去使用 + ,至於如何定義我們自定義的 Struct 的 + 的行為呢?以 "加" 來說,我們自定義的 Struct 必須要去實現 std::ops::Add 這個 Trait。什麼是 Trait?可以想像是 interface,定義了很多不含實作的方法。而我們自定義的 Struct 就要去 Implement Add 這個 Trait ,包含裡頭的 add 方法,這樣我們就可以使用 + 來 “相加” 我們自定義的 Struct 了,細節可以參考這裡,裡頭也定義了各式各樣可以拿來當作 Overloading 的 Trait 唷!

結語

今天主要是透過 Operator 的單元,探討了一下這些 Operator 在各語言被使用的背後發生了哪些事情,還有 Operator overloading 的部分。那麼我們明天見囉!


上一篇
[Day 1] 資料型態不無聊!
下一篇
[Day 3] 我的程式不失控!
系列文
30 天把自己榨好榨滿的四週四語言大挑戰!30

尚未有邦友留言

立即登入留言