Archive for June 2007

Will You Still Need Me

It’s time once again for the obligatory birthday post. Today, I’m 26 in hexadecimal. In the past, I’ve been =FACT(4)+PV(1,30,28)+MONTH(38166)+SIN(RADIANS(90))+LEN(”dicksblog”)+CHOOSE(3,10,14,22,36,88)+TRUE and 00100100. Last year was a birthday clip show with no fancy age representation.

I had trouble coming up with a decent “getting older” euphemism for my title. I settled on lyrics from the Beatles’ tune, When I’m 64. One of my birthday presents was tickets to a show called Yesterday and Today at the Omaha Community Playhouse. From what I hear, it’s a bunch of Beatles’ covers. I’m sure there’s more to it than just that, but I can’t find any information on the show. I’m quite sure it’s nothing like Movin’ Out, which I panned last year. Speaking of pans, I saw the advertisement for the show when I saw Peter Pan recently. Peter Pan was a good show, particularly the guy who played Captain Hook.

Once again I Played the West. It was an enjoyable trip of golf and some very nice cigars. The Diamond Crown Maximus is my new favorite cigar. When my humidor is emptied, it will be filled next with these. For Independence Day, I’ll be playing at Old Kinderhook, which I’m looking forward to.

This weekend I’ll be strolling down memory lane with my fellow alumni of Daniel J. Gross High School. Twenty years? Man, I’m old. Apparently I haven’t accomplished enough in my life to make the On This Day post at The Hodge Blog. Maybe next year.

Sending mail from Excel with CDO

Hi all

I update my CDO page today.
Code is working now in Excel 97-2007 or 2000-2007.
http://www.rondebruin.nl/cdo.htm

If you have problems let me know.

Ron de Bruin
http://www.rondebruin.nl/tips.htm

Update:

I can’t post comments to this blog because of the new spam filter on this blog that’s why i do it this way.

I add a GMail example to the example workbook on my CDO page today.
Let me know if it is working for you.

Solver Code Doesn’t Like Shapes

I was recently using the Solver Add-in through VBA, something I haven't done much of in the past. I definitely learned some things. I thought I would document those lessons even though I haven't fully tested them and I don't fully understand why they do what they do.

Lesson 1: Installing the add-in using Tools > Add-ins in Excel does not expose the Solver object model to my project. I still had to set a reference to SOLVER in the VBE (VBE: Tools > References).

Lesson 2: Solver can be really slow. I processed 50 scenarios with four constraints each and it took about eight minutes. Yikes. I made a few changes to how I setup the Solver code and I'm doing the same thing in about 17 seconds. I'm not sure which of the modifications reduced the time. The first thing I did was reset Solver before setting up the scenario using SolvReset. Next, I removed the constraints that kept cells above zero and used

SolvOptions AssumeNonNeg:=True

Finally, and as a consequence of reseting before each iteration, I re-added all of the constraints each time. In the code below, I'm looking for the Max. I needed to do the exact same scenario but look for the Min. When I just changed Max to Min and let the constraints carryover from the previous setup, it was considerably slower. I thought for sure it would have been faster not to wipe them out and re-add them, but that was not my experience.

Here's how the code ended up, roughly:

solvreset
SolvOk SetCell:=.Range("MyRange").Address, MaxMinVal:=1, _
    ByChange:=.Range("rngFirst").Address & "," & .Range("rngSecond").Address
SolvAdd CellRef:="$A$1", Relation:=2, FormulaText:="1" 'A1=1
SolvAdd CellRef:=.Range("rngFirst").Address, Relation:=1, FormulaText:="1.0" 'rngFirst <= 1
SolvAdd CellRef:=.Range("rngSecond").Address, Relation:=1, FormulaText:="1.0" 'rngSecond <=1
SOLVoptions , , , , , , , , , , , True 'Options - assumenonneg = true
SolvSolve True

Lesson3: The code ran from a Forms commandbutton on a worksheet. I wasn't getting the correct results. It was easy to see that the results were wrong because rngSecond was over 1 million when I had clearly constrained it to <=1. I interrupted the code right after SolvSolve and looked at Solver manually through the Excel UI. There were no constraints in there. None. I found on the InterWeb (sorry I don't recall where) that shapes on a worksheet can cause constraints not to be added.

I removed the commandbutton and put a hyperlink in its place. I set the hyperlink's target to the cell where it lived so that it essentially did nothing and I added code like this:

Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
   
    If Target.Range.Address = Me.Range("A2").Address Then
        Analyze
    End If
   
End Sub

Now the constraints are added and (I hope) the results are correct.

Missing VBE Toolbars

There are some days that I feel like I'm using Excel for the first time. Yesterday was one of them. The menu and toolbars were gone from the Visual Basic Editor. View > Toolbars didn't work. I couldn't right click on anything to Customize the toolbars. From the Immediate Window, I determined that the Height, Visible, and Enabled properties were all what they should be. I could not figure out where the darn toolbars went.

I closed the VBE, but did not close Excel. I reopened the VBE (Alt+F11) and all the toolbars were back. Strange.

CEILING Part 3

From KeepITCool

Function kicCeiling(ByVal vNum As Variant, ByVal vSig As Variant) As Variant
    Dim vRes
    Dim r&, c&
    Dim n&, nrL&, nrU&, ncL&, ncU&
    Dim s&, srL&, srU&, scL&, scU&
   
    On Error Resume Next
   
    If TypeName(vNum) = "Range" Then vNum = vNum.Value
    nrU = -1: ncU = -1
    nrL = LBound(vNum, 1): nrU = UBound(vNum, 1)
    ncL = LBound(vNum, 2): ncU = UBound(vNum, 2)
    If ncU> -1 Then n = 2 Else If nrU> -1 Then n = 1 Else n = 0
   
    If TypeName(vSig) = "Range" Then vSig = vSig.Value
    srU = -1: scU = -1
    srL = LBound(vSig, 1): srU = UBound(vSig, 1)
    scL = LBound(vSig, 2): scU = UBound(vSig, 2)
    If scU> -1 Then s = 2 Else If srU> -1 Then s = 1
       
    Select Case n & s
        Case "00"
            vRes = GetCeiling(vNum, vSig)
        Case "20"
            ReDim vRes(nrL To nrU, ncL To ncU)
            For r = nrL To nrU: For c = ncL To ncU: vRes(r, c) = GetCeiling(vNum(r, c), vSig): Next: Next
        Case "02"
            ReDim vRes(srL To srU, scL To scU)
            For r = srL To srU: For c = scL To scU: vRes(r, c) = GetCeiling(vNum, vSig(r, c)): Next: Next
        Case "22"
            Debug.Assert nrL = srL And ncL = scL
            If nrU = srU And ncU = scU Then
                ReDim vRes(nrL To nrU, ncL To ncU)
                For r = nrL To nrU: For c = ncL To ncU: vRes(r, c) = GetCeiling(vNum(r, c), vSig(r, c)): Next: Next
            ElseIf nrU = 1 Then
                ReDim vRes(srL To srU, ncL To ncU)
                For r = srL To srU: For c = ncL To ncU: vRes(r, c) = GetCeiling(vNum(1, c), vSig(r, 1)): Next: Next
            ElseIf srU = 1 Then
                ReDim vRes(nrL To nrU, scL To scU)
                For r = nrL To nrU: For c = scL To scU: vRes(r, c) = GetCeiling(vNum(r, 1), vSig(1, c)): Next: Next
            Else
                nrU = Application.Max(nrU, srU)
                ncU = Application.Max(ncU, scU)
                ReDim vRes(nrL To nrU, ncL To ncU)
                For r = nrL To nrU
                    If r <= UBound(vNum, 1) And r <= UBound(vSig, 1) Then
                        For c = ncL To ncU
                            If c <= UBound(vNum, 2) And c <= UBound(vSig, 2) Then
                                vRes(r, c) = GetCeiling(vNum(r, c), vSig(r, c))
                            Else
                                vRes(r, c) = CVErr(xlErrNA)
                            End If
                        Next
                    Else
                        For c = ncL To ncU
                            vRes(r, c) = CVErr(xlErrNA)
                        Next
                    End If
                Next
            End If
        Case "10"
            ReDim vRes(nrL To nrU)
            For r = nrL To nrU: vRes(r) = GetCeiling(vNum(r), vSig): Next
        Case "01"
            ReDim vRes(srL To srU)
            For r = srL To srU: vRes(r) = GetCeiling(vNum, vSig(r)): Next
        Case "11"
            Debug.Assert nrL = srL And nrU = srU
            ReDim vRes(nrL To nrU)
            For r = nrL To nrU: vRes(r) = GetCeiling(vNum(r), vSig(r)): Next
        Case "21"
            Debug.Assert ncU = 1
            ReDim vRes(nrL To nrU, srL To srU)
            For r = nrL To nrU: For c = srL To srU: vRes(r, c) = GetCeiling(vNum(r, 1), vSig(c)): Next: Next
        Case "12"
            Debug.Assert scU = 1
            ReDim vRes(srL To srU, nrL To nrU)
            For r = srL To srU: For c = nrL To nrU: vRes(r, c) = GetCeiling(vNum(c), vSig(r, 1)): Next: Next
    End Select
   
    kicCeiling = vRes
   
End Function
     
Private Function GetCeiling(ByVal number As Variant, ByVal significance As Variant) As Variant
    Dim dNum#, dSig#, dTmp#
    Dim vRes
   
    On Error GoTo errH
   
    Select Case VarType(number) '+vbArray will error out.
        Case vbError: vRes = number: GoTo endH
        Case vbBoolean: dNum = Abs(number)
        Case Else: dNum = number
    End Select
    Select Case VarType(significance)
        Case vbError: vRes = significance: GoTo endH
        Case vbBoolean: dSig = Abs(significance)
        Case Else: dSig = significance
    End Select
   
    If dNum = 0 Or dSig = 0 Then
        vRes = 0#
    ElseIf Sgn(dNum) <> Sgn(dSig) Then
        vRes = CVErr(xlErrNum)
    Else
        dTmp = dNum / dSig
        vRes = (Int(dTmp) + Abs(dTmp <> Int(dTmp))) * dSig
    End If
endH:
    GetCeiling = vRes
    Exit Function
errH:
    vRes = CVErr(xlErrValue)
    GoTo endH:
End Function

I get NA for the array formula

=SUM(kicceiling(A17:B18,E17:E18))

but I haven't had time to sort out why.

Parsing the Bible

An AOL-user asks J-Walk:

An AOL User: What five letter word appears in the KJV only four times? It was made before Adam and named by Adam. Had no legs, arms or soul. Was given a soul and it was taken back.

Using J-Walk's Bible in Excel, I listed all of the five letter words that appear only four times. Here's the code:

Sub FindWord()
   
    Dim sh As Worksheet
    Dim rCell As Range
    Dim colWords As Collection
    Dim vaWords As Variant
    Dim i As Long
    Dim sText As String
    Dim vaRemove As Variant
   
    Const lLEN As Long = 5
    Const lFREQ As Long = 4
   
    Set colWords = New Collection
   
    vaRemove = Array(",", ".", ":", ";", "!", "?", "(", ")")
   
    For Each sh In ThisWorkbook.Worksheets
        For Each rCell In Intersect(sh.Columns(2), sh.UsedRange).Cells
            sText = rCell.Text
            For i = LBound(vaRemove) To UBound(vaRemove)
                sText = Replace(sText, vaRemove(i), "")
            Next i
            vaWords = Split(sText, " ")
            For i = LBound(vaWords) To UBound(vaWords)
                If Len(vaWords(i)) = lLEN Then
                    On Error Resume Next
                        colWords.Add vaWords(i), CStr(vaWords(i))
                    On Error GoTo 0
                End If
            Next i
        Next rCell
    Next sh
   
    For i = 1 To colWords.Count
        Sheet1.Range("G2").Value = colWords(i)
        If Application.WorksheetFunction.Sum(Sheet1.Range("G5:G70")) = lFREQ Then
            Debug.Print colWords(i)
        End If
    Next i
   
End Sub

It uses the "Stats" page in the workbook to compute the number of times each word appears. This is not a good way to do it, because each word causes an expensive recalc. But I didn't want to spend time writing my own algorithm. I started the macro and came back 20 minutes later.