如何平均JSON数组的值,然后舍入到1个小数点

时间:2017-06-03 11:15:31

标签: json http go rounding

我是Go的新手,无法弄清楚如何简单地读取和平均JSON数组的值。我还想将结果舍入到1个小数点,但Go没有Round()函数。这是数据:

[
    {"millisUTC":"1496424000000","price":"7.6"},
    {"millisUTC":"1496423700000","price":"7.5"},
    {"millisUTC":"1496423400000","price":"9.1"},
    {"millisUTC":"1496423100000","price":"9.2"},
    {"millisUTC":"1496422800000","price":"10.0"}
]

我想得到价格并将它们平均,四舍五入到小数点后1位。然而,当我(作为Ruby开发人员)通常需要3行代码时,我花了30多行代码。我该如何简化这个?我的代码包含2个参数,starttimeendtime,并调用API:https://github.com/rayning0/griddy/blob/master/controllers/default.go

type arrayOfMaps []map[string]string

func getAvgPrice(starttime, endtime string) float64 {
    response, err := http.Get("https://hourlypricing.comed.com/api?type=5minutefeed&datestart=" + starttime + "&dateend=" + endtime)

   if err != nil {
        fmt.Println(err)
    }
    defer response.Body.Close()

    energyJSON, err := ioutil.ReadAll(response.Body)
    if err != nil {
        fmt.Println(err)
    }

    var energyPrices arrayOfMaps
    err = json.Unmarshal(energyJSON, &energyPrices)

    fmt.Println("Energy prices between", starttime, "and", endtime)
    fmt.Println(energyPrices)

    var sum float64
    var size int
    for _, p := range energyPrices {
        price, _ := strconv.ParseFloat(p["price"], 64)
        sum += price
        size++
    }
    avg := Truncate(sum / float64(size))
    fmt.Println("Average price:", avg)
    return avg
}

//Truncate a float to 1 level of precision
func Truncate(some float64) float64 {
    return float64(int(some*10)) / 10
}

编辑,感谢@icza的出色帮助!

这适用于我的问题:https://golang.org/pkg/encoding/json/#Decoder.Decode

请参阅我的修订解决方案,并附上详细评论:https://github.com/rayning0/griddy/blob/master/controllers/default.go

1 个答案:

答案 0 :(得分:2)

你的代码可以在几个方面简化,当你说“四舍五入”时你最后“截断”了不一样。

一件重要的事情:如果遇到错误,您应该提前返回而不是继续,因为这只会是其他错误的来源,甚至是运行时恐慌。见最后。

Unmarshaling JSON

更容易使用json.Decoder,直接从响应正文解码(实现io.Reader)。

另请注意,要简化解析JSON中string值给出的浮点数,更好的选择是使用json.Number

解析可以这么简单:

var prices []map[string]json.Number
if err := json.NewDecoder(response.Body).Decode(&prices); err != nil {
    fmt.Println(err)
    return
}

计算总和

计算总和也可以简化:无需跟踪size,因为这只是地图的长度:

sum := 0.0
for _, p := range prices {
    f, _ := p["price"].Float64()
    sum += f
}

请注意,如果您想以一种简单的方式将错误从总和中排除(并且不会返回错误),那么您才需要计算有效数字。

舍入

乘以10然后除以10是截断而不是舍入。对于舍入,您应该在这两个操作之间添加0.5。有关详细信息,请参阅此答案:Golang Round to Nearest 0.05

这是一个正确的舍入函数,可以将正数和负数正确地舍入到任意单位:

func Round(x, unit float64) float64 {
    if x > 0 {
        return float64(int64(x/unit+0.5)) * unit
    }
    return float64(int64(x/unit-0.5)) * unit
}

结果是:

avg := Round(sum/float64(len(prices)), 0.1)

具有错误处理的完整解决方案

由于您的getAvgPrice()可能会在多个点失败,因此您也一定要添加错误返回值。

这是具有正确错误处理的完整解决方案:

func getAvgPrice(starttime, endtime string) (float64, error) {
    response, err := http.Get("https://hourlypricing.comed.com/api?type=5minutefeed&datestart=" + starttime + "&dateend=" + endtime)
    if err != nil {
        return 0, err
    }
    defer response.Body.Close()

    var prices []map[string]json.Number
    if err := json.NewDecoder(response.Body).Decode(&prices); err != nil {
        return 0, err
    }

    sum := 0.0
    for _, p := range prices {
        f, err := p["price"].Float64()
        if err != nil {
            return 0, err
        }
        sum += f
    }

    return Round(sum/float64(len(prices)), 0.1), nil
}