@@ -223,6 +223,7 @@ var tokenPriority = map[string]int{
223
223
// FLOOR.MATH
224
224
// FLOOR.PRECISE
225
225
// GCD
226
+ // HLOOKUP
226
227
// IF
227
228
// INT
228
229
// ISBLANK
@@ -239,6 +240,7 @@ var tokenPriority = map[string]int{
239
240
// LN
240
241
// LOG
241
242
// LOG10
243
+ // LOOKUP
242
244
// LOWER
243
245
// MDETERM
244
246
// MEDIAN
@@ -275,6 +277,7 @@ var tokenPriority = map[string]int{
275
277
// TRIM
276
278
// TRUNC
277
279
// UPPER
280
+ // VLOOKUP
278
281
//
279
282
func (f * File ) CalcCellValue (sheet , cell string ) (result string , err error ) {
280
283
var (
@@ -2335,8 +2338,8 @@ func (fn *formulaFuncs) MUNIT(argsList *list.List) (result formulaArg) {
2335
2338
return newErrorFormulaArg (formulaErrorVALUE , "MUNIT requires 1 numeric argument" )
2336
2339
}
2337
2340
dimension := argsList .Back ().Value .(formulaArg ).ToNumber ()
2338
- if dimension .Type == ArgError {
2339
- return dimension
2341
+ if dimension .Type == ArgError || dimension . Number < 0 {
2342
+ return newErrorFormulaArg ( formulaErrorVALUE , dimension . Error )
2340
2343
}
2341
2344
matrix := make ([][]formulaArg , 0 , int (dimension .Number ))
2342
2345
for i := 0 ; i < int (dimension .Number ); i ++ {
@@ -3607,8 +3610,7 @@ func matchPattern(pattern, name string) (matched bool) {
3607
3610
if pattern == "*" {
3608
3611
return true
3609
3612
}
3610
- rname := make ([]rune , 0 , len (name ))
3611
- rpattern := make ([]rune , 0 , len (pattern ))
3613
+ rname , rpattern := make ([]rune , 0 , len (name )), make ([]rune , 0 , len (pattern ))
3612
3614
for _ , r := range name {
3613
3615
rname = append (rname , r )
3614
3616
}
@@ -3636,11 +3638,9 @@ func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte
3636
3638
}
3637
3639
return criteriaG
3638
3640
case ArgString :
3639
- ls := lhs .String
3640
- rs := rhs .String
3641
+ ls , rs := lhs .String , rhs .String
3641
3642
if ! caseSensitive {
3642
- ls = strings .ToLower (ls )
3643
- rs = strings .ToLower (rs )
3643
+ ls , rs = strings .ToLower (ls ), strings .ToLower (rs )
3644
3644
}
3645
3645
if exactMatch {
3646
3646
match := matchPattern (rs , ls )
@@ -3649,7 +3649,15 @@ func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte
3649
3649
}
3650
3650
return criteriaG
3651
3651
}
3652
- return byte (strings .Compare (ls , rs ))
3652
+ switch strings .Compare (ls , rs ) {
3653
+ case 1 :
3654
+ return criteriaG
3655
+ case - 1 :
3656
+ return criteriaL
3657
+ case 0 :
3658
+ return criteriaEq
3659
+ }
3660
+ return criteriaErr
3653
3661
case ArgEmpty :
3654
3662
return criteriaEq
3655
3663
case ArgList :
@@ -3739,16 +3747,29 @@ func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg {
3739
3747
}
3740
3748
}
3741
3749
row := tableArray .Matrix [0 ]
3742
- start:
3743
- for idx , mtx := range row {
3744
- switch compareFormulaArg (mtx , lookupValue , false , exactMatch ) {
3745
- case criteriaL :
3746
- matchIdx = idx
3747
- case criteriaEq :
3748
- matchIdx = idx
3749
- wasExact = true
3750
- break start
3750
+ if exactMatch || len (tableArray .Matrix ) == TotalRows {
3751
+ start:
3752
+ for idx , mtx := range row {
3753
+ lhs := mtx
3754
+ switch lookupValue .Type {
3755
+ case ArgNumber :
3756
+ if ! lookupValue .Boolean {
3757
+ lhs = mtx .ToNumber ()
3758
+ if lhs .Type == ArgError {
3759
+ lhs = mtx
3760
+ }
3761
+ }
3762
+ case ArgMatrix :
3763
+ lhs = tableArray
3764
+ }
3765
+ if compareFormulaArg (lhs , lookupValue , false , exactMatch ) == criteriaEq {
3766
+ matchIdx = idx
3767
+ wasExact = true
3768
+ break start
3769
+ }
3751
3770
}
3771
+ } else {
3772
+ matchIdx , wasExact = hlookupBinarySearch (row , lookupValue )
3752
3773
}
3753
3774
if matchIdx == - 1 {
3754
3775
return newErrorFormulaArg (formulaErrorNA , "HLOOKUP no result found" )
@@ -3795,11 +3816,51 @@ func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
3795
3816
exactMatch = true
3796
3817
}
3797
3818
}
3798
- start:
3799
- for idx , mtx := range tableArray .Matrix {
3800
- if len (mtx ) == 0 {
3801
- continue
3819
+ if exactMatch || len (tableArray .Matrix ) == TotalRows {
3820
+ start:
3821
+ for idx , mtx := range tableArray .Matrix {
3822
+ lhs := mtx [0 ]
3823
+ switch lookupValue .Type {
3824
+ case ArgNumber :
3825
+ if ! lookupValue .Boolean {
3826
+ lhs = mtx [0 ].ToNumber ()
3827
+ if lhs .Type == ArgError {
3828
+ lhs = mtx [0 ]
3829
+ }
3830
+ }
3831
+ case ArgMatrix :
3832
+ lhs = tableArray
3833
+ }
3834
+ if compareFormulaArg (lhs , lookupValue , false , exactMatch ) == criteriaEq {
3835
+ matchIdx = idx
3836
+ wasExact = true
3837
+ break start
3838
+ }
3802
3839
}
3840
+ } else {
3841
+ matchIdx , wasExact = vlookupBinarySearch (tableArray , lookupValue )
3842
+ }
3843
+ if matchIdx == - 1 {
3844
+ return newErrorFormulaArg (formulaErrorNA , "VLOOKUP no result found" )
3845
+ }
3846
+ mtx := tableArray .Matrix [matchIdx ]
3847
+ if col < 0 || col >= len (mtx ) {
3848
+ return newErrorFormulaArg (formulaErrorNA , "VLOOKUP has invalid column index" )
3849
+ }
3850
+ if wasExact || ! exactMatch {
3851
+ return mtx [col ]
3852
+ }
3853
+ return newErrorFormulaArg (formulaErrorNA , "VLOOKUP no result found" )
3854
+ }
3855
+
3856
+ // vlookupBinarySearch finds the position of a target value when range lookup
3857
+ // is TRUE, if the data of table array can't guarantee be sorted, it will
3858
+ // return wrong result.
3859
+ func vlookupBinarySearch (tableArray , lookupValue formulaArg ) (matchIdx int , wasExact bool ) {
3860
+ var low , high , lastMatchIdx int = 0 , len (tableArray .Matrix ) - 1 , - 1
3861
+ for low <= high {
3862
+ var mid int = low + (high - low )/ 2
3863
+ mtx := tableArray .Matrix [mid ]
3803
3864
lhs := mtx [0 ]
3804
3865
switch lookupValue .Type {
3805
3866
case ArgNumber :
@@ -3812,24 +3873,106 @@ start:
3812
3873
case ArgMatrix :
3813
3874
lhs = tableArray
3814
3875
}
3815
- switch compareFormulaArg (lhs , lookupValue , false , exactMatch ) {
3816
- case criteriaL :
3817
- matchIdx = idx
3818
- case criteriaEq :
3876
+ result := compareFormulaArg (lhs , lookupValue , false , false )
3877
+ if result == criteriaEq {
3878
+ matchIdx , wasExact = mid , true
3879
+ return
3880
+ } else if result == criteriaG {
3881
+ high = mid - 1
3882
+ } else if result == criteriaL {
3883
+ matchIdx , low = mid , mid + 1
3884
+ if lhs .Value () != "" {
3885
+ lastMatchIdx = matchIdx
3886
+ }
3887
+ } else {
3888
+ return - 1 , false
3889
+ }
3890
+ }
3891
+ matchIdx , wasExact = lastMatchIdx , true
3892
+ return
3893
+ }
3894
+
3895
+ // vlookupBinarySearch finds the position of a target value when range lookup
3896
+ // is TRUE, if the data of table array can't guarantee be sorted, it will
3897
+ // return wrong result.
3898
+ func hlookupBinarySearch (row []formulaArg , lookupValue formulaArg ) (matchIdx int , wasExact bool ) {
3899
+ var low , high , lastMatchIdx int = 0 , len (row ) - 1 , - 1
3900
+ for low <= high {
3901
+ var mid int = low + (high - low )/ 2
3902
+ mtx := row [mid ]
3903
+ result := compareFormulaArg (mtx , lookupValue , false , false )
3904
+ if result == criteriaEq {
3905
+ matchIdx , wasExact = mid , true
3906
+ return
3907
+ } else if result == criteriaG {
3908
+ high = mid - 1
3909
+ } else if result == criteriaL {
3910
+ low , lastMatchIdx = mid + 1 , mid
3911
+ } else {
3912
+ return - 1 , false
3913
+ }
3914
+ }
3915
+ matchIdx , wasExact = lastMatchIdx , true
3916
+ return
3917
+ }
3918
+
3919
+ // LOOKUP function performs an approximate match lookup in a one-column or
3920
+ // one-row range, and returns the corresponding value from another one-column
3921
+ // or one-row range. The syntax of the function is:
3922
+ //
3923
+ // LOOKUP(lookup_value,lookup_vector,[result_vector])
3924
+ //
3925
+ func (fn * formulaFuncs ) LOOKUP (argsList * list.List ) formulaArg {
3926
+ if argsList .Len () < 2 {
3927
+ return newErrorFormulaArg (formulaErrorVALUE , "LOOKUP requires at least 2 arguments" )
3928
+ }
3929
+ if argsList .Len () > 3 {
3930
+ return newErrorFormulaArg (formulaErrorVALUE , "LOOKUP requires at most 3 arguments" )
3931
+ }
3932
+ lookupValue := argsList .Front ().Value .(formulaArg )
3933
+ lookupVector := argsList .Front ().Next ().Value .(formulaArg )
3934
+ if lookupVector .Type != ArgMatrix && lookupVector .Type != ArgList {
3935
+ return newErrorFormulaArg (formulaErrorVALUE , "LOOKUP requires second argument of table array" )
3936
+ }
3937
+ cols , matchIdx := lookupCol (lookupVector ), - 1
3938
+ for idx , col := range cols {
3939
+ lhs := lookupValue
3940
+ switch col .Type {
3941
+ case ArgNumber :
3942
+ lhs = lhs .ToNumber ()
3943
+ if ! col .Boolean {
3944
+ if lhs .Type == ArgError {
3945
+ lhs = lookupValue
3946
+ }
3947
+ }
3948
+ }
3949
+ if compareFormulaArg (lhs , col , false , false ) == criteriaEq {
3819
3950
matchIdx = idx
3820
- wasExact = true
3821
- break start
3951
+ break
3822
3952
}
3823
3953
}
3824
- if matchIdx == - 1 {
3825
- return newErrorFormulaArg (formulaErrorNA , "VLOOKUP no result found" )
3954
+ column := cols
3955
+ if argsList .Len () == 3 {
3956
+ column = lookupCol (argsList .Back ().Value .(formulaArg ))
3826
3957
}
3827
- mtx := tableArray .Matrix [matchIdx ]
3828
- if col < 0 || col >= len (mtx ) {
3829
- return newErrorFormulaArg (formulaErrorNA , "VLOOKUP has invalid column index" )
3958
+ if matchIdx < 0 || matchIdx >= len (column ) {
3959
+ return newErrorFormulaArg (formulaErrorNA , "LOOKUP no result found" )
3830
3960
}
3831
- if wasExact || ! exactMatch {
3832
- return mtx [col ]
3961
+ return column [matchIdx ]
3962
+ }
3963
+
3964
+ // lookupCol extract columns for LOOKUP.
3965
+ func lookupCol (arr formulaArg ) []formulaArg {
3966
+ col := arr .List
3967
+ if arr .Type == ArgMatrix {
3968
+ col = nil
3969
+ for _ , r := range arr .Matrix {
3970
+ if len (r ) > 0 {
3971
+ col = append (col , r [0 ])
3972
+ continue
3973
+ }
3974
+ col = append (col , newEmptyFormulaArg ())
3975
+ }
3833
3976
}
3834
- return newErrorFormulaArg ( formulaErrorNA , "VLOOKUP no result found" )
3977
+ return col
3835
3978
}
0 commit comments