Monday, November 20, 2006

An Improved Median Function

After my test script found errors with my previous median function, I've gone ahead and made some improvements. Again, the goal here isn't to be thorough but is to demonstrate a boundary-testing technique that is easy to conceive, implement, and build-upon. Like I mentioned in my previous post, this single test script can be the foundation for a testing class. I would also go further by wrapping all of this into a table-driven engine, which would call the function, define its parameters, and identify its expected return value. With VFP9, structured error handling will also help.

I imagine that a revisit of this topic at some point will be in order. Until then, enjoy the much improved get_median function:


**************************************************************
* The following code is an improved get_median *
**************************************************************

FUNCTION get_median_clean
LPARAMETERS tnType, teName, teColDel

*-- boundary checking
IF !BETWEEN(tnType,1,3)
RETURN 0
ENDIF

DO CASE
CASE tnType = 1 .and. VARTYPE(teName)<>"C"
RETURN 0
CASE tnType = 1 .and. (!ISDIGIT(teName) .or.;
VARTYPE(teColDel)<>"C")
RETURN 0
CASE INLIST(tnType,2,3) .and. VARTYPE(teColDel)<>"N"
RETURN 0
CASE tnType = 2 .and. (!USED(teName) .or. ;
EMPTY(FIELD(teColDel,teName)))
RETURN 0
CASE tnType = 2 .and. ;
VARTYPE(EVALUATE(FIELD(teColDel,teName))) <> "N"
RETURN 0
CASE tnType = 3 .and. ;
VARTYPE(EVALUATE(teColDel[1,teColDel])) <> "N"
RETURN 0
ENDCASE

*-- local declerations
LOCAL lcSelCol AS String
LOCAL lnItems, lnHalf, lnInc AS Integer
LOCAL lnMedian AS Number

*-- create the aMedian Array
DO CASE
CASE tnType = 1 && use a delimted string
lnItems = ALINES( aMedian , STRTRAN(teName ,teColDel,CHR(13)) )
FOR lnInc = 1 TO lnItems
aMedian[lnInc] = VAL(aMedian[lnInc])
NEXT
CASE tnType = 2 && calculate using an alias
lcSelCol = FIELD(teColDel,teName)
lcSQL = "SELECT " + lcSelCol + " FROM " + teName + ;
" ORDER BY " + lcSelCol + " INTO ARRAY aMedian"
&lcSQL
lnItems= _TALLY
CASE tnType = 3 && calculate using an array
lnItems= ALEN(&teName,1)
DIMENSION aMedian[lnItems]
FOR lnInc = 1 TO lnItems
aMedian[lnInc] = &teName.[lnInc,teColDel]
NEXT
ENDCASE

*-- find the median
ASORT(aMedian)
lnHalf = CEILING( lnItems / 2)
IF MOD(lnHalf,2) = 0
lnMedian = ( aMedian[lnHalf] + aMedian[lnHalf+1] ) / 2
ELSE
lnMedian = aMedian[lnHalf]
ENDIF
RETURN lnMedian

ENDFUNC

No comments: