Skip to content

Commit b8c52d1

Browse files
Implement UNIQUE function
1 parent c83efc6 commit b8c52d1

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

calc.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ type formulaFuncs struct {
790790
// TYPE
791791
// UNICHAR
792792
// UNICODE
793+
// UNIQUE
793794
// UPPER
794795
// VALUE
795796
// VALUETOTEXT
@@ -14535,6 +14536,158 @@ func (fn *formulaFuncs) UNICODE(argsList *list.List) formulaArg {
1453514536
return fn.code("UNICODE", argsList)
1453614537
}
1453714538

14539+
// UNIQUE function returns a list of unique values in a list or range.
14540+
// For syntax refer to
14541+
// https://support.microsoft.com/en-us/office/unique-function-c5ab87fd-30a3-4ce9-9d1a-40204fb85e1e.
14542+
func (fn *formulaFuncs) UNIQUE(argsList *list.List) formulaArg {
14543+
args := getFormulaUniqueArgs(argsList)
14544+
if args.errArg != nil {
14545+
return *args.errArg
14546+
}
14547+
14548+
if args.byColumn {
14549+
args.cellRange, args.cols, args.rows = transposeFormulaArgsList(args.cellRange, args.cols, args.rows)
14550+
}
14551+
14552+
counts := map[string]int{}
14553+
14554+
for i := 0; i < len(args.cellRange); i += args.cols {
14555+
key := concatValues(args.cellRange[i : i+args.cols])
14556+
14557+
if _, ok := counts[key]; !ok {
14558+
counts[key] = 0
14559+
}
14560+
counts[key]++
14561+
}
14562+
14563+
uniqueAxes := [][]formulaArg{}
14564+
added := map[string]struct{}{}
14565+
14566+
for i := 0; i < len(args.cellRange); i += args.cols {
14567+
key := concatValues(args.cellRange[i : i+args.cols])
14568+
if _, ok := added[key]; ok {
14569+
continue
14570+
}
14571+
added[key] = struct{}{}
14572+
14573+
if (args.exactlyOnce && counts[key] == 1) || (!args.exactlyOnce && counts[key] >= 1) {
14574+
uniqueAxes = append(uniqueAxes, args.cellRange[i:i+args.cols])
14575+
}
14576+
}
14577+
14578+
if args.byColumn {
14579+
uniqueAxes = transposeFormulaArgsMatrix(uniqueAxes)
14580+
}
14581+
14582+
return newMatrixFormulaArg(uniqueAxes)
14583+
}
14584+
14585+
func transposeFormulaArgsMatrix(args [][]formulaArg) [][]formulaArg {
14586+
if len(args) == 0 {
14587+
return args
14588+
}
14589+
14590+
transposedArgs := make([][]formulaArg, len(args[0]))
14591+
14592+
for i := 0; i < len(args[0]); i++ {
14593+
transposedArgs[i] = make([]formulaArg, len(args))
14594+
}
14595+
14596+
for i := 0; i < len(args); i++ {
14597+
for j := 0; j < len(args[i]); j++ {
14598+
transposedArgs[j][i] = args[i][j]
14599+
}
14600+
}
14601+
14602+
return transposedArgs
14603+
}
14604+
14605+
func transposeFormulaArgsList(args []formulaArg, cols, rows int) ([]formulaArg, int, int) {
14606+
transposedArgs := make([]formulaArg, len(args))
14607+
14608+
for i := 0; i < rows; i++ {
14609+
for j := 0; j < cols; j++ {
14610+
transposedArgs[j*rows+i] = args[i*cols+j]
14611+
}
14612+
}
14613+
return transposedArgs, rows, cols
14614+
}
14615+
14616+
func concatValues(args []formulaArg) string {
14617+
val := ""
14618+
for _, arg := range args {
14619+
// Call to Value is cheap.
14620+
val += arg.Value()
14621+
}
14622+
return val
14623+
}
14624+
14625+
type formulaUniqueArgs struct {
14626+
cellRange []formulaArg
14627+
cols int
14628+
rows int
14629+
byColumn bool
14630+
exactlyOnce bool
14631+
errArg *formulaArg
14632+
}
14633+
14634+
func getFormulaUniqueArgs(argsList *list.List) formulaUniqueArgs {
14635+
argsLen := argsList.Len()
14636+
if argsLen == 0 {
14637+
errArg := newErrorFormulaArg(formulaErrorVALUE, "UNIQUE requires at least 1 argument")
14638+
return formulaUniqueArgs{errArg: &errArg}
14639+
}
14640+
14641+
argRange := argsList.Front().Value.(formulaArg).ToList()
14642+
if argRange == nil || argsLen > 3 {
14643+
msg := fmt.Sprintf("UNIQUE takes at most 3 arguments, received %d arguments", argsLen)
14644+
errArg := newErrorFormulaArg(formulaErrorVALUE, msg)
14645+
14646+
return formulaUniqueArgs{errArg: &errArg}
14647+
}
14648+
14649+
rmin, rmax := calcColsRowsMinMax(false, argsList)
14650+
cmin, cmax := calcColsRowsMinMax(true, argsList)
14651+
cols, rows := cmax-cmin+1, rmax-rmin+1
14652+
14653+
sndArg := argsList.Front().Next()
14654+
if sndArg == nil {
14655+
return formulaUniqueArgs{
14656+
cellRange: argRange,
14657+
cols: cols,
14658+
rows: rows,
14659+
}
14660+
}
14661+
14662+
argByColumn := sndArg.Value.(formulaArg).ToBool()
14663+
if argByColumn.Type == ArgError {
14664+
return formulaUniqueArgs{errArg: &argByColumn}
14665+
}
14666+
14667+
trdArg := argsList.Front().Next().Next()
14668+
if trdArg == nil {
14669+
return formulaUniqueArgs{
14670+
cellRange: argRange,
14671+
cols: cols,
14672+
rows: rows,
14673+
byColumn: argByColumn.Value() == "TRUE",
14674+
}
14675+
}
14676+
14677+
argExactlyOnce := trdArg.Value.(formulaArg).ToBool()
14678+
if argExactlyOnce.Type == ArgError {
14679+
return formulaUniqueArgs{errArg: &argExactlyOnce}
14680+
}
14681+
14682+
return formulaUniqueArgs{
14683+
cellRange: argRange,
14684+
cols: cols,
14685+
rows: rows,
14686+
byColumn: argByColumn.Value() == "TRUE",
14687+
exactlyOnce: argExactlyOnce.Value() == "TRUE",
14688+
}
14689+
}
14690+
1453814691
// UPPER converts all characters in a supplied text string to upper case. The
1453914692
// syntax of the function is:
1454014693
//

calc_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,11 @@ func TestCalcCellValue(t *testing.T) {
19141914
"UNICODE(\"alpha\")": "97",
19151915
"UNICODE(\"?\")": "63",
19161916
"UNICODE(\"3\")": "51",
1917+
// UNIQUE
1918+
"TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9))": "Jan,Feb",
1919+
"TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9, FALSE, FALSE))": "Jan,Feb",
1920+
"TEXTJOIN(\",\", TRUE, UNIQUE(E2:E9, FALSE, FALSE))": "North 1,North 2,South 1,South 2",
1921+
"TEXTJOIN(\",\", TRUE, UNIQUE(D2:D9, FALSE, TRUE))": "",
19171922
// UPPER
19181923
"UPPER(\"test\")": "TEST",
19191924
"UPPER(\"TEST\")": "TEST",
@@ -5132,6 +5137,82 @@ func TestCalcCOVAR(t *testing.T) {
51325137
}
51335138
}
51345139

5140+
func TestCalcUniqueExactlyOnce(t *testing.T) {
5141+
cellData := [][]interface{}{
5142+
{"Customer name"},
5143+
{"Fife, Grant"},
5144+
{"Pruitt, Barbara"},
5145+
{"Horn, Frances"},
5146+
{"Barrett, Alicia"},
5147+
{"Barrett, Alicia"},
5148+
{"Larson, Lynn"},
5149+
{"Pruitt, Barbara"},
5150+
{"Snook, Anthony"},
5151+
{"Snook, Anthony"},
5152+
{"Horn, Frances"},
5153+
{"Brown, Charity"},
5154+
}
5155+
f := prepareCalcData(cellData)
5156+
5157+
formulaList := map[string]string{
5158+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12))": "Fife, Grant:Pruitt, Barbara:Horn, Frances:Barrett, Alicia:Larson, Lynn:Snook, Anthony:Brown, Charity",
5159+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12,FALSE,TRUE))": "Fife, Grant:Larson, Lynn:Brown, Charity",
5160+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A12,FALSE,FALSE))": "Fife, Grant:Pruitt, Barbara:Horn, Frances:Barrett, Alicia:Larson, Lynn:Snook, Anthony:Brown, Charity",
5161+
}
5162+
for formula, expected := range formulaList {
5163+
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
5164+
result, err := f.CalcCellValue("Sheet1", "C1")
5165+
assert.NoError(t, err, formula)
5166+
assert.Equal(t, expected, result, formula)
5167+
}
5168+
}
5169+
5170+
func TestCalcUniqueMultiColumn(t *testing.T) {
5171+
cellData := [][]interface{}{
5172+
{"Player name", "Gender", "Nickname"},
5173+
{"Tom", "M", "Tom"},
5174+
{"Fred", "M", "Fred"},
5175+
{"Amy", "F", "Amy"},
5176+
{"John", "M", "John"},
5177+
{"Malicia", "F", "Malicia"},
5178+
{"Fred", "M", "Fred"},
5179+
}
5180+
f := prepareCalcData(cellData)
5181+
5182+
formulaList := map[string]string{
5183+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7))": "Tom:M:Tom:Fred:M:Fred:Amy:F:Amy:John:M:John:Malicia:F:Malicia",
5184+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7,TRUE))": "Tom:M:Fred:M:Amy:F:John:M:Malicia:F:Fred:M",
5185+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:C7,TRUE, TRUE))": "M:M:F:M:F:M",
5186+
}
5187+
for formula, expected := range formulaList {
5188+
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
5189+
result, err := f.CalcCellValue("Sheet1", "C1")
5190+
assert.NoError(t, err, formula)
5191+
assert.Equal(t, expected, result, formula)
5192+
}
5193+
}
5194+
5195+
func TestCalcUniqueErrors(t *testing.T) {
5196+
cellData := [][]interface{}{
5197+
{"Player name", "Gender", "Nickname"},
5198+
{"Tom", "M", "Tom"},
5199+
{"Fred", "M", "Fred"},
5200+
}
5201+
f := prepareCalcData(cellData)
5202+
formulaList := map[string]string{
5203+
"TEXTJOIN(\":\", TRUE, UNIQUE())": "#VALUE!",
5204+
"TEXTJOIN(\":\", TRUE, UNIQUE(1, 2, 3, 4))": "#VALUE!",
5205+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A3, \"Hello\"))": "#VALUE!",
5206+
"TEXTJOIN(\":\", TRUE, UNIQUE(A2:A3, TRUE, \"Hello\"))": "#VALUE!",
5207+
}
5208+
for formula, expected := range formulaList {
5209+
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
5210+
result, err := f.CalcCellValue("Sheet1", "C1")
5211+
assert.Error(t, err, formula)
5212+
assert.Equal(t, expected, result, formula)
5213+
}
5214+
}
5215+
51355216
func TestCalcDatabase(t *testing.T) {
51365217
cellData := [][]interface{}{
51375218
{"Tree", "Height", "Age", "Yield", "Profit", "Height"},

0 commit comments

Comments
 (0)