Skip to content

Commit 827094a

Browse files
authored
Correct FREQUENCY output and simplify implementation (#2286)
- Removed the incorrect `c[1], c[2] = c[2], c[1]` swapping logic so results match Excel - Use row-major traversal and ensure output order - Replaced the redundant allocation and assignment loops with a single `make` allocation for the output matrix. - Added comprehensive test cases in `calc_test.go` using `INDEX` and array constants to verify the correct distribution of counts across all buckets, ensuring the removal of the swap logic maintains accurate behavior - Update dependencies modules and documentation
1 parent 2694a36 commit 827094a

5 files changed

Lines changed: 58 additions & 50 deletions

File tree

calc.go

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8255,29 +8255,35 @@ func (fn *formulaFuncs) FORECASTdotLINEAR(argsList *list.List) formulaArg {
82558255
return fn.pearsonProduct("FORECAST.LINEAR", 3, argsList)
82568256
}
82578257

8258-
// matrixToSortedColumnList convert matrix formula arguments to a ascending
8259-
// order list by column.
8260-
func matrixToSortedColumnList(arg formulaArg) formulaArg {
8258+
type sortedItem struct {
8259+
idx int
8260+
number float64
8261+
}
8262+
8263+
// matrixToSortedColumnList flattens matrix formula arguments into a slice of
8264+
// numeric values, then sorts that slice in ascending order by number. Matrix
8265+
// traversal is row-major.
8266+
func matrixToSortedColumnList(arg formulaArg) ([]sortedItem, *formulaArg) {
82618267
var (
8262-
mtx []formulaArg
8268+
mtx []sortedItem
82638269
cols = len(arg.Matrix[0])
82648270
)
8265-
for colIdx := 0; colIdx < cols; colIdx++ {
8266-
for _, row := range arg.Matrix {
8271+
for _, row := range arg.Matrix {
8272+
for colIdx := 0; colIdx < cols; colIdx++ {
82678273
cell := row[colIdx]
82688274
if cell.Type == ArgError {
8269-
return cell
8275+
return nil, &cell
82708276
}
82718277
if cell.Type == ArgNumber {
8272-
mtx = append(mtx, cell)
8278+
mtx = append(mtx, sortedItem{idx: len(mtx), number: cell.Number})
82738279
}
82748280
}
82758281
}
8276-
argsList := newListFormulaArg(mtx)
8277-
sort.Slice(argsList.List, func(i, j int) bool {
8278-
return argsList.List[i].Number < argsList.List[j].Number
8282+
8283+
sort.Slice(mtx, func(i, j int) bool {
8284+
return mtx[i].number < mtx[j].number
82798285
})
8280-
return argsList
8286+
return mtx, nil
82818287
}
82828288

82838289
// FREQUENCY function to count how many children fall into different age
@@ -8295,37 +8301,27 @@ func (fn *formulaFuncs) FREQUENCY(argsList *list.List) formulaArg {
82958301
if len(bins.Matrix) == 0 {
82968302
bins.Matrix = [][]formulaArg{{bins}}
82978303
}
8298-
var (
8299-
dataMtx, binsMtx formulaArg
8300-
c [][]formulaArg
8301-
i, j int
8302-
)
8303-
if dataMtx = matrixToSortedColumnList(data); dataMtx.Type != ArgList {
8304-
return dataMtx
8305-
}
8306-
if binsMtx = matrixToSortedColumnList(bins); binsMtx.Type != ArgList {
8307-
return binsMtx
8304+
8305+
dataMtx, argErr := matrixToSortedColumnList(data)
8306+
if argErr != nil {
8307+
return *argErr
83088308
}
8309-
for row := 0; row < len(binsMtx.List)+1; row++ {
8310-
var rows []formulaArg
8311-
for col := 0; col < 1; col++ {
8312-
rows = append(rows, newNumberFormulaArg(0))
8313-
}
8314-
c = append(c, rows)
8309+
binsMtx, argErr := matrixToSortedColumnList(bins)
8310+
if argErr != nil {
8311+
return *argErr
83158312
}
8316-
for j = 0; j < len(binsMtx.List); j++ {
8313+
out := make([][]formulaArg, len(binsMtx)+1)
8314+
var i int
8315+
for j := range len(binsMtx) {
83178316
n := 0.0
8318-
for i < len(dataMtx.List) && dataMtx.List[i].Number <= binsMtx.List[j].Number {
8317+
for i < len(dataMtx) && dataMtx[i].number <= binsMtx[j].number {
83198318
n++
83208319
i++
83218320
}
8322-
c[j] = []formulaArg{newNumberFormulaArg(n)}
8323-
}
8324-
c[j] = []formulaArg{newNumberFormulaArg(float64(len(dataMtx.List) - i))}
8325-
if len(c) > 2 {
8326-
c[1], c[2] = c[2], c[1]
8321+
out[binsMtx[j].idx] = []formulaArg{newNumberFormulaArg(n)}
83278322
}
8328-
return newMatrixFormulaArg(c)
8323+
out[len(binsMtx)] = []formulaArg{newNumberFormulaArg(float64(len(dataMtx) - i))}
8324+
return newMatrixFormulaArg(out)
83298325
}
83308326

83318327
// GAMMA function returns the value of the Gamma Function, Γ(n), for a

calc_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ func prepareCalcData(cellData [][]interface{}) *File {
2525
func TestCalcCellValue(t *testing.T) {
2626
cellData := [][]interface{}{
2727
{1, 4, nil, "Month", "Team", "Sales"},
28-
{2, 5, nil, "Jan", "North 1", 36693},
29-
{3, nil, nil, "Jan", "North 2", 22100},
28+
{2, 5, nil, "Jan", "North 1", 36693, 4},
29+
{3, nil, nil, "Jan", "North 2", 22100, 2},
3030
{0, nil, nil, "Jan", "South 1", 53321},
3131
{nil, nil, nil, "Jan", "South 2", 34440},
3232
{nil, nil, nil, "Feb", "North 1", 29889},
@@ -1110,8 +1110,20 @@ func TestCalcCellValue(t *testing.T) {
11101110
// FORECAST.LINEAR
11111111
"FORECAST.LINEAR(7,A1:A7,B1:B7)": "4",
11121112
// FREQUENCY
1113-
"SUM(FREQUENCY(A2,B2))": "1",
1114-
"SUM(FREQUENCY(A1:A5,B1:B2))": "4",
1113+
"SUM(FREQUENCY(A2,B2))": "1",
1114+
"SUM(FREQUENCY(A1:A5,B1:B2))": "4",
1115+
"INDEX(FREQUENCY(A1:A4,G2:G3),1,1)": "1",
1116+
"INDEX(FREQUENCY(A1:A4,G2:G3),2,1)": "3",
1117+
"INDEX(FREQUENCY(A1:A4,G2:G3),3,1)": "0",
1118+
"INDEX(FREQUENCY(A1:B4,F2:G3),1,1)": "0",
1119+
"INDEX(FREQUENCY(A1:B4,F2:G3),2,1)": "2",
1120+
"INDEX(FREQUENCY(A1:B4,F2:G3),3,1)": "1",
1121+
"INDEX(FREQUENCY(A1:B4,F2:G3),4,1)": "3",
1122+
"INDEX(FREQUENCY(A1:B4,F2:G3),5,1)": "0",
1123+
"INDEX(FREQUENCY({0,1,4,5,11,10,6},{2,4,10}),1,1)": "2",
1124+
"INDEX(FREQUENCY({0,1,4,5,11,10,6},{2,4,10}),2,1)": "1",
1125+
"INDEX(FREQUENCY({0,1,4,5,11,10,6},{2,4,10}),3,1)": "3",
1126+
"INDEX(FREQUENCY({0,1,4,5,11,10,6},{2,4,10}),4,1)": "1",
11151127
// GAMMA
11161128
"GAMMA(0.1)": "9.51350769866873",
11171129
"GAMMA(INT(1))": "1",

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ require (
88
github.com/tiendc/go-deepcopy v1.7.2
99
github.com/xuri/efp v0.0.1
1010
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9
11-
golang.org/x/crypto v0.49.0
11+
golang.org/x/crypto v0.50.0
1212
golang.org/x/image v0.38.0
13-
golang.org/x/net v0.52.0
14-
golang.org/x/text v0.35.0
13+
golang.org/x/net v0.53.0
14+
golang.org/x/text v0.36.0
1515
)
1616

1717
require (

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
1414
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
1515
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
1616
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
17-
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
18-
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
17+
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
18+
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
1919
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
2020
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
21-
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
22-
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
23-
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
24-
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
21+
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
22+
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
23+
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
24+
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
2525
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2626
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2727
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

pivotTable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ type PivotTableField struct {
111111
// options. Note that the same fields can not in Columns, Rows and Filter
112112
// fields at the same time.
113113
//
114-
// For example, create a pivot table on the range reference Sheet1!G4:M31 with
114+
// For example, create a pivot table on the range reference Sheet1!G4:M30 with
115115
// the range reference Sheet1!A1:E31 as the data source, summarize by sum for
116116
// revenue:
117117
//

0 commit comments

Comments
 (0)