User:Klever: Difference between revisions

m
(→‎VBA Examples: look-and-say sequence)
 
(17 intermediate revisions by the same user not shown)
Line 1:
{{mylangbegin}}
{{mylang|Visual BasicVBA|Active (in VB for Applications)}}
{{mylang|BASIC|Somewhat Rusty}}
{{mylang|Fortran|Stuck in Fortran 77, WATFOR, WATFIV etc.}}
Line 12:
 
=VBA Examples=
Some nontrivial VBA Examples (untilto therebe is a separate VBA categorymoved).
 
In MS Office program (Word, Excel, Access...): open the Visual Basic window. Paste the code in a module. Execute it by typing a suitable command in the Immediate Window. Output will be directed to the Immediate Window unless stated otherwise...
 
==[[Look-and-sayDijkstra sequencealgorithm]]==
<lang vb>
'Dijkstra globals
Public Sub LookAndSay(Optional Niter As Integer = 10)
Const MaxGraph As Integer = 100 'max. number of nodes in graph
'generate "Niter" members of the look-and-say sequence
Const Infinity = 1E+308
'(argument is optional; default is 10)
Dim E(1 To MaxGraph, 1 To MaxGraph) As Double 'the edge costs (Infinity if no edge)
 
Dim sA(1 To MaxGraph) As StringDouble 'look-and-saythe distances numbercalculated
Dim newsP(1 To MaxGraph) As StringInteger 'next number in sequence 'the previous/path array
Dim curdigitQ(1 To MaxGraph) As StringBoolean 'current digit in s 'the queue
Dim newdigit As String 'next digit in s
Public Sub Dijkstra(n, start)
Dim curlength As Integer 'length of current run
'simple implementation of Dijkstra's algorithm
Dim p As Integer 'position in s
'n = number of nodes in graph
Dim L As Integer 'length of s
'start = index of start node
 
'init distances A
On Error GoTo Oops
For j = 1 To n
 
A(j) = Infinity
'start with "1"
Next j
s = "1"
A(start) = 0
For i = 1 To Niter
'init P (path) to "no paths" and Q = set of all nodes
'initialise
LFor j = Len(s)1 To n
p Q(j) = 1True
curdigit = Left$P(s, 1j) = 0
Next j
curlength = 1
news = ""
Do While True 'loop will exit! (see below)
For p = 2 To L
'find node u in Q with smallest distance to start
'check next digit in s
newdigitdist = Mid$(s, p, 1)Infinity
For i = 1 To n
If curdigit = newdigit Then 'extend current run
curlengthIf =Q(i) curlength + 1Then
Else ' "output" run andIf startA(i) new< rundist Then
news = news & CStr(curlength)dist &= curdigitA(i)
curdigit u = newdigiti
curlength = 1End If
End If
Next pi
If dist = Infinity Then Exit Do 'no more nodes available - done!
' "output" last run
'remove u from Q
news = news & CStr(curlength) & curdigit
Q(u) = False
Debug.Print news
'loop over neighbors of u that are in Q
s = news
For j = 1 To n
If Q(j) And E(u, j) <> Infinity Then
'check if path to neighbor j via u is shorter than current estimated distance to j
alt = A(u) + E(u, j)
If alt < A(j) Then
'yes, replace with new distance and remember "previous" hop on the path
A(j) = alt
P(j) = u
End If
End If
Next j
Loop
End Sub
Public Function GetPath(source, target) As String
'reconstruct shortest path from source to target
'by working backwards from target using the P(revious) array
Dim path As String
If P(target) = 0 Then
GetPath = "No path"
Else
path = ""
u = target
Do While P(u) > 0
path = Format$(u) & " " & path
u = P(u)
Loop
GetPath = Format$(source) & " " & path
End If
End Function
Public Sub DijkstraTest()
'main function to solve Dijkstra's algorithm and return shortest path between
'a node and every other node in a digraph
' define problem:
' number of nodes
n = 5
' reset connection/cost per edge
For i = 1 To n
For j = 1 To n
E(i, j) = Infinity
Next j
P(i) = 0
Next i
' fill in the edge costs
Exit Sub
E(1, 2) = 10
E(1, 3) = 50
E(1, 4) = 65
E(2, 3) = 30
E(2, 5) = 4
E(3, 4) = 20
E(3, 5) = 44
E(4, 2) = 70
E(4, 5) = 23
E(5, 1) = 6
'Solve it for every node
 
For v = 1 To n
Oops:
Dijkstra n, v
'Print solution
Debug.Print "From", "To", "Cost", "Path"
For j = 1 To n
If v <> j Then Debug.Print v, j, IIf(A(j) = Infinity, "---", A(j)), GetPath(v, j)
Next j
Debug.Print
Next v
If Err.Number = 6 Then 'overflow
Debug.Print "Oops - number too long!"
Else
Debug.Print "Error: "; Err.Number, Err.Description
End If
End Sub
</lang>
 
Output (using the same graph as in the Floyd-Warshall algorithm below):
Output:
 
<pre>
DijkstraTest
LookAndSay 7
From To Cost Path
11
1 2 10 1 2
21
1 3 40 1 2 3
1211
1 4 60 1 2 3 4
111221
1 5 14 1 2 5
312211
13112221
1113213211
</pre>
 
From To Cost Path
(Note: overflow occurs at 38th iteration!)
2 1 10 2 5 1
2 3 30 2 3
2 4 50 2 3 4
2 5 4 2 5
 
From To Cost Path
3 1 49 3 4 5 1
3 2 59 3 4 5 1 2
3 4 20 3 4
3 5 43 3 4 5
 
From To Cost Path
4 1 29 4 5 1
4 2 39 4 5 1 2
4 3 69 4 5 1 2 3
4 5 23 4 5
 
From To Cost Path
5 1 6 5 1
5 2 16 5 1 2
5 3 46 5 1 2 3
5 4 66 5 1 2 3 4
</pre>
 
==[[Floyd-Warshall algorithm]]==
[[File:FloydGraph.png|thumb|250px|Graph used in this and Dijkstra's algorithm]]
The [http://en.wikipedia.org/wiki/Floyd-Warshall_algorithm Floyd algorithm or Floyd-Warshall algorithm] finds the shortest path between all pairs of nodes in a weighted, directed graph. It is an example of dynamic programming.
 
Usage: fill in the number of nodes (n) and the non-zero edge distances or costs in sub Floyd or in sub FloydWithPaths.
Then run "Floyd" or "FloydWithPaths".
 
Line 94 ⟶ 173:
FloydWithPaths: this sub prints the lengths and the nodes along the paths
 
<lang vb>
Option Compare Database
 
'Floyd globals
Const MaxGraph As Integer = 100 'max. number of vertices in graph
Const Infinity = 1E+308 'very large number
Dim E(1 To MaxGraph, 1 To MaxGraph) As Double
Dim A(1 To MaxGraph, 1 To MaxGraph) As Double
Dim Nxt(1 To MaxGraph, 1 To MaxGraph) As Integer
 
Public Sub SolveFloyd(n)
'Floyd's algorithm: all-pairs shortest-paths cost
Line 110 ⟶ 191:
'inputs:
' n : number of vertices (maximum value is maxGraph)
' E(i,j) : cost (length,...) of edge from i to j or <=0"Infinity" if no edge between i and j
'output:
' A(i,j): minimal cost for path from i to j
'constant:
' Infinity : very large number (guaranteed to be larger than largest possible cost of any path)
For i = 1 To n
For j = 1 To n
If E(i, j) <> 0Infinity Then A(i, j) = E(i, j) Else A(i, j) = Infinity
Next j
A(i, i) = 0
Line 130 ⟶ 211:
Next k
End Sub
 
Public Sub SolveFloydWithPaths(n)
'cf. SolveFloyd, but here we
Line 136 ⟶ 217:
For i = 1 To n
For j = 1 To n
If E(i, j) <> 0Infinity Then A(i, j) = E(i, j) Else A(i, j) = Infinity
Next j
A(i, i) = 0
Line 151 ⟶ 232:
Next k
End Sub
 
Public Function GetPath(i, j) As String
'recursively reconstruct shortest path from i to j using A and Nxt
Line 165 ⟶ 246:
End If
End Function
 
Public Sub Floyd()
'main function to apply Floyd's algorithm
'see description in wp:en:Floyd-Warshall algorithm
 
' define problem:
' number of vertices?
Line 176 ⟶ 257:
For i = 1 To n
For j = 1 To n
E(i, j) = 0Infinity
Next j
Next i
Line 187 ⟶ 268:
E(3, 4) = 20
E(3, 5) = 44
E(4, 2) = 770
E(4, 5) = 1323
E(5, 1) = 6
 
'Solve it
SolveFloyd n
 
'Print solution
'note: for large graphs the output may be too large for the Immediate panel
Line 203 ⟶ 285:
Next i
End Sub
 
Public Sub FloydWithPaths()
'main function to solve Floyd's algorithm and return shortest path between
'any two vertices
 
' define problem:
' number of vertices?
Line 214 ⟶ 296:
For i = 1 To n
For j = 1 To n
E(i, j) = 0Infinity
Nxt(i, j) = 0
Next j
Line 226 ⟶ 308:
E(3, 4) = 20
E(3, 5) = 44
E(4, 2) = 770
E(4, 5) = 1323
E(5, 1) = 6
 
'Solve it
SolveFloydWithPaths n
 
'Print solution
'note: for large graphs the output may be too large for the Immediate panel
Line 242 ⟶ 325:
Next i
End Sub
</lang>
 
Output:
<pre>Floyd
Floyd
From To Cost
1 2 10
Line 252 ⟶ 334:
1 4 60
1 5 14
2 1 No path!10
2 3 30
2 4 50
2 5 4
3 1 No path!49
3 2 2759
3 4 20
3 5 3143
4 1 No path!29
4 2 739
4 3 3769
4 5 1123
5 1 No path!6
5 2 No path!16
5 3 No path!46
5 4 No path!66
 
 
FloydWithPaths
Line 276 ⟶ 357:
1 4 60 2 3
1 5 14 2
2 1 --- 10 No path! 5
2 3 30
2 4 50 3
2 5 4
3 1 --- 49 No path! 4 5
3 2 2759 4 5 1
3 4 20
3 5 3143 4 2
4 1 --- 29 No path! 5
4 2 739 5 1
4 3 3769 5 1 2
4 5 1123 2
5 1 --- 6 No path!
5 2 --- 16 No path! 1
5 3 --- 46 No path! 1 2
5 4 --- 66 No path! 1 2 3
</pre>
 
==[[SudokuKWIC index]]==
This is a version of the "brute force" approach as in the Fortran program
 
<lang vb>
'KWIC index
'assumptions:
' - all titles and catalog numbers can be held in an array in main memory
' - disregard punctuation in titles
' - the KWIC index itself may be too large for main memory - do not store it in memory
' - the KWIC index consists of one line per title/keyword combination and consists of:
' - the catalog number
' - the title with the keyword centered in a line of given length (e.g. 80 or 120)
' (constant-width font assumed)
' note: long titles may be truncated at the beginning or the end of the line
 
'globals
Dim grid(9, 9)
Const MAXKEYS = 20 'max. number of keywords in a title
Dim gridSolved(9, 9)
Const STOPWORDS = "a an and by for is it of on or the to with " 'that last space is needed!
Dim title() As String 'list of titles to be included in KWIC index
Dim catno() As Integer 'list of catalog numbers
Dim ntitle As Integer 'number of titles
Dim index() As Integer 'holds title number and position of keyword in title
Dim nkeys As Long 'total number of keywords found
 
Public Sub SolveReadTitles(i, j)
' read or - in this case - set the titles and catalog numbers
If i > 9 Then
ntitle = 10
'exit with gridSolved = Grid
ReDim For r = title(1 To 9ntitle)
ReDim catno(1 To ntitle)
For c = 1 To 9
title(1) = "Microsoft Visio 2003 User's Guide"
gridSolved(r, c) = grid(r, c)
title(2) = "Microsoft Office Excel 2003 Inside Out"
Next c
title(3) = "Mastering Excel 2003 Programming with VBA"
Next r
title(4) = "Excel 2003 Formulas"
Exit Sub
title(5) = "Excel for Scientists and Engineers"
End If
title(6) = "Excel 2003 VBA Programmer's Reference"
For n = 1 To 9
title(7) = "Automated Data Analysis Using Excel"
If isSafe(i, j, n) Then
title(8) = "Beginning Excel: What-if Data Analysis Tools"
nTmp = grid(i, j)
title(9) = "How to do Everything with Microsoft Office Excel 2003"
grid(i, j) = n
title(10) = "Data Analysis Using SQL and Excel"
If j = 9 Then
catno(1) = 10
Solve i + 1, 1
catno(2) = Else13
catno(3) = 3435
Solve i, j + 1
catno(4) = 987
End If
gridcatno(i, j5) = nTmp1010
catno(6) = End If1244
catno(7) = 709
Next n
catno(8) = 9088
catno(9) = 33
catno(10) = 7733
End Sub
 
Public Function isSafeIsStopword(i, j, naword) As Boolean
'search for aword in stopword list
Dim iMin As Integer
'add an extra space to avoid ambiguity
Dim jMin As Integer
IsStopword = InStr(STOPWORDS, LCase(aword) & " ") > 0
End Function
 
Sub ProcessTitles()
If grid(i, j) <> 0 Then
'find positions of keywords in titles, store in index array
isSafe = (grid(i, j) = n)
'Note: we cannot use Split here because that function doesn't return
Exit Function
'the positions of the words it finds
End If
nkeys = 0
 
For i = 1 To ntitle
'grid(i,j) is an empty cell. Check if n is OK
atitle = title(i) & " " 'add extra space as sentinel
'first check the row i
For c p1 = 1 To 9
IfDo grid(i,While c)p1 <= n ThenLen(atitle)
isSafe'find =next Falseword:
'a) find next non-space
Exit Function
While Mid$(atitle, p1, 1) = " ": p1 = p1 + 1: Wend
End If
'b) extend word
Next c
p2 = p1
 
While Mid$(atitle, p2, 1) <> " ": p2 = p2 + 1: Wend
'now check the column j
aword = Mid$(atitle, p1, p2 - p1)
For r = 1 To 9
'for now we assume there is no punctuation, i.e. no words
If grid(r, j) = n Then
'in parentheses, brackets or quotation marks
isSafe = False
If Not IsStopword(aword) Then
Exit Function
'remember position of this keyword
End If
'we probably should check for overflow (too many keywords) here!
Next r
nkeys = nkeys + 1
 
index(nkeys, 1) = i
'finally, check the 3x3 subsquare containing grid(i,j)
iMin = 1 + 3 * Intindex((inkeys, - 12) /= 3)p1
jMin = 1 + 3 * Int((j - 1) / 3)
For r = iMin To iMin + 2
For c = jMin To jMin + 2
If grid(r, c) = n Then
isSafe = False
Exit Function
End If
'continue searching
Next c
p1 = p2 + 1
Next r
Loop
Next i
End Sub
 
Function Shift(aString, pos)
'all tests were OK
'return shifted string (part beginning at position "pos" followed by part before it)
isSafe = True
Shift = Mid$(aString, pos) & " " & Left$(aString, pos - 1)
End Function
 
Public Sub SudokuSortTitles()
' sort the index() array to represent shifted titles in alphabetical order
'main routine
' more efficient sorting algorithms can be applied here...
'to use, fill in the grid and
switched = True
'type "Sudoku" in the Immediate panel of the Visual Basic for Applications window
Do While switched
 
'scan array for two shifted strings in the wrong order and swap
Dim s(9) As String
'(swap the index entries, not the strings)
 
'use case-insensitive compare
'initialise grid using 9 strings,one per row
switched = False
s(1) = "001005070"
For i = 1 To nkeys - 1
s(2) = "920600000"
string1 = LCase(Shift(title(index(i, 1)), index(i, 2)))
s(3) = "008000600"
string2 = LCase(Shift(title(index(i + 1, 1)), index(i + 1, 2)))
s(4) = "090020401"
If string2 < string1 Then 'swap
s(5) = "000000000"
For j = 1 To 2
s(6) = "304080090"
temp = index(i, j)
s(7) = "007000300"
index(i, j) = index(i + 1, j)
s(8) = "000007069"
index(i + 1, j) = temp
s(9) = "010800700"
For i = 1 To 9 Next
For j = 1 Toswitched = 9True
End If
grid(i, j) = Int(Val(Mid$(s(i), j, 1)))
Next ji
Next iLoop
'solve it!
Solve 1, 1
'print solution
Debug.Print "Solution:"
For i = 1 To 9
For j = 1 To 9
Debug.Print Format$(gridSolved(i, j)); " ";
Next j
Debug.Print
Next i
End Sub
</lang>
 
Output:
 
<pre>
Sudoku
Solution:
6 3 1 2 4 5 9 7 8
9 2 5 6 7 8 1 4 3
4 7 8 3 1 9 6 5 2
7 9 6 5 2 3 4 8 1
1 8 2 9 6 4 5 3 7
3 5 4 7 8 1 2 9 6
8 6 7 4 9 2 3 1 5
2 4 3 1 5 7 8 6 9
5 1 9 8 3 6 7 2 4
</pre>
 
 
==[[Greatest element of a list]]==
<lang>
Public Function ListMax(anArray())
'return the greatest element in array anArray whose length is unknown to this function
n0 = LBound(anArray)
n = UBound(anArray)
theMax = anArray(n0)
For i = (n0 + 1) To n
If anArray(i) > theMax Then theMax = anArray(i)
Next
ListMax = theMax
End Function
 
 
Sub PrintKWIC(linelength)
Public Sub ListMaxTest()
'print the KWIC index
Dim b()
spaces = Space(linelength / 2)
'test function ListMax
Debug.Print "Cat. number", "|"; Space((linelength - 10) / 2); "KWIC string"
'fill array b with some numbers:
Debug.Print String(linelength + 15, "-")
b = Array(5992424433449#, 4534344439984#, 551344678, 99800000#)
For i = 1 To nkeys
'print the greatest element
atitle = title(index(i, 1))
Debug.Print "Greatest element is"; ListMax(b())
pos = index(i, 2)
'create shifted string so that keyword is centered in the line
part2 = Mid$(atitle, pos)
part1 = Right$(spaces & Left$(atitle, pos - 1), linelength / 2)
kwicstring = Right$(part1, linelength / 2) & Left$(part2, linelength / 2)
Debug.Print catno(index(i, 1)), "|"; kwicstring
Next
End Sub
</lang>
 
Sub KWIC()
Result:
'main program for KWIC index
<pre>
ReadTitles
ListMaxTest
'set array
Greatest element is 5992424433449
ReDim index(ntitle * MAXKEYS, 2)
</pre>
'index(.,1) is title nr.
 
'index(.,2) is keyword position in title
==[[Reverse a string]]==
ProcessTitles
===Non-recursive version===
SortTitles
<lang>
PrintKWIC 80 'argument is the length of the KWIC lines (excluding catalog numbers)
Public Function Reverse(aString as String) as String
' returns the reversed string
dim L as integer 'length of string
dim newString as string
 
newString = ""
L = len(aString)
for i = L to 1 step -1
newString = newString & mid$(aString, i, 1)
next
Reverse = newString
End Function
</lang>
 
===Recursive version===
<lang>
Public Function RReverse(aString As String) As String
'returns the reversed string
'do it recursively: cut the sring in two, reverse these fragments and put them back together in reverse order
Dim L As Integer 'length of string
Dim M As Integer 'cut point
 
L = Len(aString)
If L <= 1 Then 'no need to reverse
RReverse = aString
Else
M = Int(L / 2)
RReverse = RReverse(Right$(aString, L - M)) & RReverse(Left$(aString, M))
End If
End Function
</lang>
 
===Example dialogue===
<pre>
print Reverse("Public Function Reverse(aString As String) As String")
gnirtS sA )gnirtS sA gnirtSa(esreveR noitcnuF cilbuP
 
print RReverse("Sunday Monday Tuesday Wednesday Thursday Friday Saturday Love")
evoL yadrutaS yadirF yadsruhT yadsendeW yadseuT yadnoM yadnuS
 
print RReverse(Reverse("I know what you did last summer"))
I know what you did last summer
</pre>
 
==[[Ordered words]]==
<lang>
Public Sub orderedwords(fname As String)
' find ordered words in dict file that have the longest word length
' fname is the name of the input file
' the words are printed in the immediate window
' this subroutine uses boolean function IsOrdered
Dim word As String 'word to be tested
Dim l As Integer 'length of word
Dim wordlength As Integer 'current longest word length
Dim orderedword() As String 'dynamic array holding the ordered words with the current longest word length
Dim wordsfound As Integer 'length of the array orderedword()
 
On Error GoTo NotFound 'catch incorrect/missing file name
Open fname For Input As #1
On Error GoTo 0
 
'initialize
wordsfound = 0
wordlength = 0
 
'process file line per line
While Not EOF(1)
Line Input #1, word
If IsOrdered(word) Then 'found one, is it equal to or longer than current word length?
l = Len(word)
If l >= wordlength Then 'yes, so add to list or start a new list
If l > wordlength Then 'it's longer, we must start a new list
wordsfound = 1
wordlength = l
Else 'equal length, increase the list size
wordsfound = wordsfound + 1
End If
'add the word to the list
ReDim Preserve orderedword(wordsfound)
orderedword(wordsfound) = word
End If
End If
Wend
Close #1
 
'print the list
Debug.Print "Found"; wordsfound; "ordered words of length"; wordlength
For i = 1 To wordsfound
Debug.Print orderedword(i)
Next
Exit Sub
 
NotFound:
debug.print "Error: Cannot find or open file """ & fname & """!"
End Sub
 
 
 
Public Function IsOrdered(someWord As String) As Boolean
'true if letters in word are in ascending (ascii) sequence
 
Dim l As Integer 'length of someWord
Dim wordLcase As String 'the word in lower case
Dim ascStart As Integer 'ascii code of first char
Dim asc2 As Integer 'ascii code of next char
 
wordLcase = LCase(someWord) 'convert to lower case
l = Len(someWord)
IsOrdered = True
If l > 0 Then 'this skips empty string - it is considered ordered...
ascStart = Asc(Left$(wordLcase, 1))
For i = 2 To l
asc2 = Asc(Mid$(wordLcase, i, 1))
If asc2 < ascStart Then 'failure!
IsOrdered = False
Exit Function
End If
ascStart = asc2
Next i
End If
End Function
</lang>
 
Output (note that some titles are truncated at the start or the end. An improvement could be to wrap these titles around if there is room on the other end):
Results:
<pre>
kwic
OrderedWords("unixdict.txt")
Cat. number | KWIC string
Found 16 ordered words of length 6
-----------------------------------------------------------------------------------------------
abbott
987 | Excel 2003 Formulas
accent
33 | Everything with Microsoft Office Excel 2003
accept
13 | Microsoft Office Excel 2003 Inside Out
access
3435 | Mastering Excel 2003 Programming with VBA
accost
10 | Microsoft Visio 2003 User's Guide
almost
1244 | Excel 2003 VBA Programmer's Reference
bellow
9088 | Beginning Excel: What-if Data Analysis Tools
billow
709 | Automated Data Analysis Using Excel
biopsy
7733 | Data Analysis Using SQL and Excel
chilly
709 | Automated Data Analysis Using Excel
choosy
9088 | Beginning Excel: What-if Data Analysis T
choppy
9088 | Beginning Excel: What-if Data Analysis Tools
effort
709 | Automated Data Analysis Using Excel
floppy
7733 | Data Analysis Using SQL and Excel
glossy
33 | How to do Everything with Microsoft Office Exce
knotty
1010 | Excel for Scientists and Engineers
33 | How to do Everything with Microsoft Office Excel 2
987 | Excel 2003 Formulas
33 | to do Everything with Microsoft Office Excel 2003
13 | Microsoft Office Excel 2003 Inside Out
3435 | Mastering Excel 2003 Programming with VBA
1244 | Excel 2003 VBA Programmer's Reference
709 | Automated Data Analysis Using Excel
7733 | Data Analysis Using SQL and Excel
1010 | Excel for Scientists and Engineers
9088 | Beginning Excel: What-if Data Analysis Tools
987 | Excel 2003 Formulas
10 | Microsoft Visio 2003 User's Guide
33 | How to do Everything with Microsoft Offi
13 | Microsoft Office Excel 2003 Inside Out
3435 | Mastering Excel 2003 Programming with VB
33 | How to do Everything with Microsoft Office Excel 2003
13 | Microsoft Office Excel 2003 Inside Out
10 | Microsoft Visio 2003 User's Guide
33 | How to do Everything with Microsoft Office Excel 2003
13 | Microsoft Office Excel 2003 Inside Out
13 | Microsoft Office Excel 2003 Inside Out
1244 | Excel 2003 VBA Programmer's Reference
3435 | Mastering Excel 2003 Programming with VBA
1244 | Excel 2003 VBA Programmer's Reference
1010 | Excel for Scientists and Engineers
7733 | Data Analysis Using SQL and Excel
9088 | Beginning Excel: What-if Data Analysis Tools
10 | Microsoft Visio 2003 User's Guide
709 | Automated Data Analysis Using Excel
7733 | Data Analysis Using SQL and Excel
3435 | Mastering Excel 2003 Programming with VBA
1244 | Excel 2003 VBA Programmer's Reference
10 | Microsoft Visio 2003 User's Guide
9088 | Beginning Excel: What-if Data Analysis Tools
</pre>
 
Anonymous user