英文:
How to average values of an array of JSONs, then round to 1 decimal point
问题
我是你的中文翻译助手,以下是翻译好的内容:
我刚开始学习Go语言,无法弄清楚如何简单地读取和计算一个JSON数组的值的平均值。我还想将结果四舍五入到小数点后一位,但是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"}
]
我想获取价格并计算它们的平均值,四舍五入到小数点后一位。然而,我的代码超过了30行,而作为Ruby开发者,通常只需要3行。我该如何简化这个过程?我的代码接受两个参数,starttime
和endtime
,并调用一个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
英文:
I'm new to Go and can't figure out how to simply read and average the values of an array of JSONs. I also want to round my result to 1 decimal point, but Go has no Round()
function. Here's the data:
[
{"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"}
]
I want to get the prices and average them, rounding to 1 decimal point. Yet it took me over 30 lines of code, when (as a Ruby developer) it would usually take me 3 lines. How do I simplify this? My code takes in 2 parameters, starttime
and endtime
, and calls an 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
}
Edited, thanks to excellent help from @icza!
This applies to my question: https://golang.org/pkg/encoding/json/#Decoder.Decode
See my revised solution, with detailed comments: https://github.com/rayning0/griddy/blob/master/controllers/default.go
答案1
得分: 2
你的代码可以在几个地方简化,而且尽管你说了“四舍五入”,但最后你实际上是“截断”的,这是不一样的。
一个重要的事情是:如果遇到错误,你应该提前返回,而不是继续执行,因为这只会导致额外的错误或甚至运行时恐慌。请看最后的部分。
解析 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
。有关详细信息,请参阅此答案:https://stackoverflow.com/questions/39544571/golang-round-to-nearest-0-05/39544897#39544897
因此,一个正确的四舍五入函数,可以将正数和负数都正确地四舍五入到任意单位:
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
}
英文:
Your code can be simplified in several points, and while you said "rounding" you did "truncating" in the end which is not the same.
One important thing: if an error is encountered, you should return early and not continue, as that will only be the source of additional errors or even runtime panics. See at the end.
Unmarshaling JSON
Easier would be to use json.Decoder
, decoding right from the response body (which implements io.Reader
).
Also note to simplify parsing floats given as string
values in JSON, a better option would be to use json.Number
.
Parsing can be as simple as this:
var prices []map[string]json.Number
if err := json.NewDecoder(response.Body).Decode(&prices); err != nil {
fmt.Println(err)
return
}
Calculating sum
Calculating sum can also be simplified: there is no need to keep track of size
, as that is simply the length of the map:
sum := 0.0
for _, p := range prices {
f, _ := p["price"].Float64()
sum += f
}
Note that if you want to handle errors in a way to simply exclude it from the sum (and not return with an error), only then would you need to count valid numbers.
Rounding
Multiplying by 10 and then dividing by 10 is truncating and not rounding. For rounding, you should add 0.5
between those 2 operations. For details, see this answer: https://stackoverflow.com/questions/39544571/golang-round-to-nearest-0-05/39544897#39544897
So a correct rounding function that properly rounds both positive and negative numbers to arbitrary unit:
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
}
So the result:
avg := Round(sum/float64(len(prices)), 0.1)
The complete solution with error handling
Since your getAvgPrice()
can fail at multiple points, you should definitely add an error return value too.
This is the complete solution with proper error handling:
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论