You don't want to use Seq.nth
, it is an O(n) operation which makes the evaluation of bb
O(n^2). Try
1
let bb = ExcelData |> Seq.map (fun x -> List.nth (String.split ['\t'] x) 6) |> Seq.to_array
Wow!
The difference was huge. In playing around with it, though, it looks like the problem was more with my poor construction rather than Seq.nth. I tried exactly the same code lusing Seq.nth rather than List.nth and it worked fine. Maybe the list wasn't long enough?
When I put in List.nth instead, I did have to change your syntax to:
1
List.nth (String.split ['\t'] x) 6
.
Is Seq.nth O(n) while List.nth is faster?
Also, since it seemed to work fine with Seq.nth, I'm unclear as to what the difference is.
Anyway, thanks very much, the revised code made a huge difference.
Mitchell
Sorry, I should have been more precise in my explanation. The use of Seq.nth
in the code
1
Seq.nth(6) z
is ok. The performance problem with your code is in the line
1
let y = Seq.nth(i) x
. This use of nth
is bad because for <i>every element<i> in the output array, <c>f1</c> opens the data file again and reads through it from the beginning until the nth line. This is what introduces the quadratic performance in the number of lines in the file.
<c>Seq.nth</c> and <c>List.nth</c> are similar functions: they are both linear in the size of their collection as work analogously to iterating through a linked list to get to the nth item.
P.S. As you pointed out, I got the order of the parameters to <c>List.nth</c> wrong. I've fixed that now.
A related question, if I may:
I think I should be able to figure this out myself, but so far I'm having no luck. I'd like to set an array equal to 1 column and another array to another column (and eventually a few more later on). Anyway, I could repeat the code for each column, but then I'll have to go through the list twice, which doesn't seem great.
I was trying to figure out how to do it in one pass using Seq.map a few times. So far I haven't been able to get anything to survive a syntax check (evidence of my lack of knowledge). Is it sensible to look for this type of solution? If so, any hints?
I guess I could use a 2-dim array, but the code that follows would seem more mysterious, relying on that second index to indentify the meaning of the column. It just seemed like it would make more sense to keep the columns as separate arrays, with understandable names.
Thanks for any help!
Mitchell
If you want to have separate arrays then doing it as Robert says is good (you'll need to reverse the accumulators returned from loop
though). However given that this is data from a spreadsheet you'd usually want to keep data from individual rows grouped together. You could do this by producing a sequence of tuples.
1 2 3 4 5 6
let parseRow x = let cols = String.split ['\t'] x List.nth cols 6, List.nth cols 7 (* y : seq<string*string> *) let y = ExcelData |> Seq.map parseRow
Or you could create a record type for a row to give the columns names.
1 2 3 4 5
type Row = { column6 : string; column7 : string } let parseRow x = let cols = String.split ['\t'] x { column6 = List.nth cols 6; column7 = List.nth cols 7}
Hope this helps.
gneverov,
Thanks for both of your most recent responses. As I wrote to Robert I am very appreciative of the time that you've given me and am sort of amazed I get any responses at all!
With regard to the speed of finding an element of a List. Is there another commonly used structure in F#, more like a hash table that can quickly find the element you're looking for?
With regard to the solution you provided, that's more like what I had imagined, although I was quite far from getting there. Robert's solution also worked very well and for me and was terrific because I learned another way of looking at these things. An embarrassment of riches with regard to approaches.
At this point, since I'm experimenting (although with real data), I'm not sure whether I want separate arrays or one sequence of tuples. I'll think I'll take my next few steps and see how it goes.
Again, thanks very much for all of your help.
Mitchell
The conventional data structure for O(1) lookup is an array. But whether arrays or lists are better depends on the application: if you structure your code to be "stream-processing" style then you never need to do an nth element lookup on a sequence.
My asumption had been that you wanted to seperate list, if want to keep the data together gneverov solution is great. However if you're looking for a hash table like structure then you have plenty of choice in F#. You can either use a "Map" an functional imutable data structure, when new items are added to a Map a new version of the map is created. Alteratively you can use a .NET dictionary a sort of hash table with strong typing - thanks to generics. Both options are shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
let map = ExcelData |> Seq.fold (fun (acc:Map<string, string>) x -> let cols = x.Split([|'\t'|]) acc.Add(cols.[0], cols.[1])) Map.empty open System.Collections.Generic let dict = let dict = new Dictionary<string, string>() ExcelData |> Seq.iter (fun x -> let cols = x.Split([|'\t'|]) dict.Add(cols.[0], cols.[1]))
One word of warning - both implementations will fail if the "keys" (the first column) is not unique, it should be relatively easy to modify the solution to deal with repeated keys.
Cheers,
Rob
Rob,
I guess at this point I'm really not sure how I'll be approaching the problem I'm looking at. I want to analyze data that comes from one of our reporting systems. This is, really, experimental on my part. It's an attempt to learn F# in a way that may make it applicable to my job.
I do have a few questions about your latest post. Up till now I've been using String.split and supplying a character to split by. In your post your using x.Split – I didn't know you could do that, although I shouldn't be surprised. However you had to use |'\t'| instead of just '\t'. Could you explain that a little?
Also, what does the Map.empty do at the end? Does this somehow help with garbage collection as acc keeps getting replaced? Along those lines, are the "older" versions of acc no longer accessible so the memory can be released?
I haven't yet spent much time with your second implementation using Dictionary. I see that this uses Seq.iter instead of Seq.fold. I'm still a fuzzy on "iter" vs "map" vs "fold" (in some cases). Something I'll spend some time on.
Again, thanks for all of your help. Oh, have a great time at Tech Days in Paris – sounds like fun!
Mitchell
Hi Michell,
It’s quite important to understand the difference between map fold and iter. Here is a quick guide:
map – this transforms a list or sequence into a new list of sequence. It takes a function of type ‘a -> ‘b mean a function takes a value of type ‘a and returns a value of type ‘b. This function is that applied to every item in the sequence and the results of the function form the new sequence. Typical example of a map would be taking a list of integers and multiplying them all by 2 or transforming them into a list of strings.
iter – this applies a function to every item in the sequence. The function has type ‘a -> unit, meaning it takes a value of type ‘a but has no return result, so to be useful the function has to have a side effect. A typical example of iter would be to print a sequence of strings to the console.
fold – this is used to create summaries of lists. It takes a function of type ‘a -> ‘b -> ‘a, meaning a function with two parameters the first is of type ‘a and is the accumulator, the second of type ‘b is the value from the list, it must return a value of the same type as the accumulator. Folding functions also require an initial value for the accumulator (this is why we pass an empty map at the start of the end of the fold function). A typical example of using fold would be to calculate the total of a list of integers.
So we use fold to create the Map because we are creating a summary of the list, and because Map is a immutable data structure, when add items to it we don’t change it we create a new version with the extra items in place. Here’s a new version with extra comment:
1 2 3 4 5 6 7 8 9 10 11 12
let map = ExcelData |> Seq.fold // first parameter "acc" is the accumulator // the second parameter "x" is the item from the list (fun (acc:Map<string, string>) x -> let cols = x.Split([|'\t'|]) // create a new version of the map // with the item added acc.Add(cols.[0], cols.[1])) // pass in an empty map Map.empty
On the other hand diction is a mutable data structure so we can use iter, as adding items to it is a side effect.
Oh and concerning the vertical bars:
1 2
let l = [ 1; 2; 3; ] // this is a list let a = [| 1; 2; 3; |] // this is an array
but don’t worry about this too much for now.
Rob,
Thanks for the clear explanantion. At this point I'm still having trouble with the very terse descriptions in the documentation such as <i>'a -> unit<i>. Your descriptions, e.g., <i>"It takes a function of type ‘a -> ‘b -> ‘a, meaning a function with two parameters the first is of type ‘a and is the accumulator, the second of type ‘b is the value from the list, it must return a value of the same type as the accumulator."<i> are much easier for me to understand. Thanks very much! Mitchell
Sorry about the slow response, I completely over looked this post.
Basically you have to explicitly traverse the list and use two accumulators to hold the results:
1 2 3 4 5 6 7 8 9 10 11
let split l = let rec loop l acc1 acc2 = match l with | hd :: tail -> let col1 = List.nth (String.split ['\t'] hd) 1 let col2 = List.nth (String.split ['\t'] hd) 2 loop tail (col1 :: acc1) (col2 :: acc2) | [] -> acc1, acc2 loop l [] [] let bb = ExcelData |> List.of_seq |> split
Robert,
Thanks for the help. Actually I appreciated the 1 day pause, I was having trouble keeping up anyway. Seriously though, I am extremely appreciative of the help you and others have been giving me and hope that I'm not becoming too much of a pest.
Your suggestion worked very well. It did leave me with a string list * string list, which forced me to pay a little more attention to what I was working with.
I'm still thinking in more of an imperative way, but gradually I'm seeing this a little more clearly. I liked the idea of pulling off the head each time and working with that. Then it naturally leads to recursion.
Thanks again,
Mitchell
gneverov,
Okay, I think I understand do I have this right?
A sequence is a linked list, so that any searching is going to be O(n). If you eventually search for every element, I guess this becomes something like n/2 on average. In my original code I went into each element, one at a time, split the string and extracted the 7th element. This then got assembled, one by one into an array. So I have n elements times n/2 elements to be searched (approximately).
Your code goes into the sequence and Splits and extracts the data from the string one at a time, but without having to do any searching. Then there's the Seq.to_array.
Does the Seq.to_array place the extracted element into the array as the linked list is being processed, or is there some temporary sequence that gets created and then moved into an array?
In any case is seems that your vastly improved routine is O(n), because it still has to go through n elements. So this seems that my original routine will take something like 25000/2 longer than yours. Is that right?
Mitchell
Sorry for the slow response. Yes, you have a correct understanding of how your code was working.
A seq is like a linked list, but it is not necessarily one. In this case the ExcelData seq reads its elements from a file; it doesn't store anything in memory. So when it is searching it for the nth element, it is searching the file on disk and not an in-memory data structure.
Seq.to_array is an O(n) operation. It does create temporary data structures though. Check the fslib source if you <i>really<i> want to know what it does.
Topic tags
- f# × 3705
- websharper × 1897
- compiler × 286
- functional × 201
- ui next × 139
- c# × 121
- classes × 97
- web × 97
- .net × 84
- book × 84
- async × 76
- ui.next × 67
- bug × 54
- core × 49
- website × 49
- server × 45
- parallel × 43
- ui × 43
- enhancement × 41
- parsing × 41
- testing × 41
- trywebsharper × 41
- typescript × 37
- html × 35
- javascript × 35
- owin × 35
- asynchronous × 30
- monad × 28
- ocaml × 28
- tutorial × 27
- warp × 27
- haskell × 26
- sitelet × 25
- linq × 22
- workflows × 22
- wpf × 20
- fpish × 19
- introduction × 19
- silverlight × 19
- sitelets × 19
- monodevelop × 17
- rpc × 17
- suave × 17
- piglets × 16
- collections × 15
- feature request × 15
- jquery × 15
- templates × 15
- getting started × 14
- pipeline × 14
- kendoui × 13
- reactive × 12
- 4.1.0.171 × 11
- monads × 11
- opinion × 10
- 4.0.190.100-rc × 9
- deployment × 9
- fixed × 9
- formlets × 9
- in × 9
- json × 9
- plugin × 9
- proposal × 9
- scheme × 9
- solid × 9
- basics × 8
- concurrent × 8
- highcharts × 8
- how-to × 8
- python × 8
- 4.1.1.175 × 7
- complexity × 7
- documentation × 7
- visual studio × 7
- 4.1.2.178 × 6
- lisp × 6
- real-world × 6
- released in 4.0.192.103-rc × 6
- remoting × 6
- resources × 6
- scala × 6
- websharper ui.next × 6
- workshop × 6
- xaml × 6
- 4.0.193.110 × 5
- 4.2.3.236 × 5
- aspnetmvc × 5
- authentication × 5
- azure × 5
- bootstrap × 5
- conference × 5
- dsl × 5
- formlet × 5
- java × 5
- list × 5
- metaprogramming × 5
- ml × 5
- released in Zafir.4.0.188.91-beta10 × 5
- sql × 5
- visualstudio × 5
- websharper.forms × 5
- zafir × 5
- 4.0.192.106 × 4
- 4.0.195.127 × 4
- 4.1.0.38 × 4
- 4.2.1.86 × 4
- 4.2.6.118 × 4
- css × 4
- example × 4
- extensions × 4
- fsi × 4
- fsx × 4
- html5 × 4
- jqueryui × 4
- lift × 4
- reflection × 4
- remote × 4
- rest × 4
- spa × 4
- teaching × 4
- template × 4
- websocket × 4
- wontfix × 4
- 4.0.196.147 × 3
- 4.1.0.34 × 3
- 4.1.6.207 × 3
- 4.2.1.223-beta × 3
- 4.2.11.258 × 3
- 4.2.4.114 × 3
- 4.2.4.247 × 3
- 4.2.5.115 × 3
- 4.2.6.253 × 3
- 4.2.9.256 × 3
- ajax × 3
- alt.net × 3
- aml × 3
- asp.net mvc × 3
- canvas × 3
- cloudsharper × 3
- compilation × 3
- database × 3
- erlang × 3
- events × 3
- extension × 3
- file upload × 3
- forums × 3
- inline × 3
- issue × 3
- kendo × 3
- macro × 3
- mono × 3
- msbuild × 3
- mvc × 3
- pattern × 3
- piglet × 3
- released in Zafir.4.0.187.90-beta10 × 3
- svg × 3
- type provider × 3
- view × 3
- 4.1.1.64 × 2
- 4.1.5.203 × 2
- 4.1.7.232 × 2
- 4.2.10.257 × 2
- 4.2.3.111 × 2
- 4.2.5.249 × 2
- android × 2
- asp.net × 2
- beginner × 2
- blog × 2
- chart × 2
- client × 2
- client server app × 2
- clojure × 2
- computation expressions × 2
- constructor × 2
- corporate × 2
- courses × 2
- cufp × 2
- d3 × 2
- debugging × 2
- direct × 2
- discriminated union × 2
- docs × 2
- elm × 2
- endpoint × 2
- endpoints × 2
- enterprise × 2
- entity framework × 2
- event × 2
- f# interactive × 2
- fable × 2
- flowlet × 2
- formdata × 2
- forms × 2
- fsc × 2
- google maps × 2
- hosting × 2
- http × 2
- https × 2
- iis 8.0 × 2
- install × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- jquery mobile × 2
- keynote × 2
- lens × 2
- lenses × 2
- linux × 2
- listmodel × 2
- mac × 2
- numeric × 2
- oauth × 2
- obfuscation × 2
- offline × 2
- oop × 2
- osx × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- q&a × 2
- quotation × 2
- reference × 2
- released in Zafir.4.0.185.88-beta10 × 2
- rx × 2
- script × 2
- security × 2
- self host × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- tutorials × 2
- type × 2
- url × 2
- var × 2
- websharper.charting × 2
- websharper4 × 2
- websockets × 2
- wig × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- 4.0.194.126 × 1
- 4.1.3.184 × 1
- 4.1.4.189 × 1
- 4.2.0.214-beta × 1
- 4.2.12.259 × 1
- 4.2.2.231-beta × 1
- 4.2.8.255 × 1
- Canvas Sample Example × 1
- DynamicStyle Animated Style × 1
- Fixed in 4.0.190.100-rc × 1
- Released in Zafir.UI.Next.4.0.169.79-beta10 × 1
- SvgDynamicAttribute × 1
- WebComponent × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- actor × 1
- addin × 1
- agents × 1
- aggregation × 1
- agile × 1
- alter session × 1
- animation × 1
- anonymous object × 1
- apache × 1
- api × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net core × 1
- asp.net integration × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- attributes × 1
- authorization × 1
- b-tree × 1
- back button × 1
- badimageformatexception × 1
- bash script × 1
- batching × 1
- binding-vars × 1
- bistro × 1
- body × 1
- bundle × 1
- camtasia studio × 1
- cas protocol × 1
- charts × 1
- clarity × 1
- class × 1
- cli × 1
- clipboard × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- color zones × 1
- combinator × 1
- combinators × 1
- compile × 1
- compile code on server × 1
- config × 1
- confirm × 1
- content × 1
- context × 1
- context.usersession × 1
- continuation-passing style × 1
- coords × 1
- cordova × 1
- cors × 1
- coursera × 1
- cross-domain × 1
- csla × 1
- current_schema × 1
- custom content × 1
- data × 1
- data grid × 1
- datetime × 1
- debug × 1
- declarative × 1
- delete × 1
- devexpress × 1
- dhtmlx × 1
- dictionary × 1
- directattribute × 1
- disqus × 1
- distance × 1
- do binding × 1
- doc elt ui.next upgrade × 1
- docker × 1
- dojo × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- dynamic × 1
- eastern language × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- enums × 1
- error × 1
- etw × 1
- euclidean × 1
- eventhandlerlist × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- failed to translate × 1
- fake × 1
- fantomas × 1
- fear × 1
- float × 1
- form × 1
- form-data × 1
- forum × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- fsunit × 1
- function × 1
- functional style × 1
- game × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting-started × 1
- google × 1
- google.maps × 1
- grid × 1
- group × 1
- guide × 1
- hash × 1
- headers × 1
- hello world example × 1
- heroku × 1
- highchart × 1
- history × 1
- how to × 1
- html-templating × 1
- http405 × 1
- httpcontext × 1
- hubfs × 1
- i18n × 1
- ie 8 × 1
- if-doc × 1
- iis × 1
- image × 1
- images × 1
- inheritance × 1
- initialize × 1
- input × 1
- install "visual studio" × 1
- installer × 1
- int64 × 1
- interfaces × 1
- internet explorer × 1
- interop × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- iot × 1
- ipad × 1
- isomorphic × 1
- javascript optimization × 1
- javascript semanticui resources × 1
- jquery-plugin × 1
- jquery-ui × 1
- jquery-ui-datepicker × 1
- js × 1
- kendo datasource × 1
- kendochart × 1
- kendoui compiler × 1
- knockout × 1
- l10n × 1
- learning × 1
- library × 1
- libs × 1
- license × 1
- licensing × 1
- lineserieszonescfg × 1
- local setting × 1
- localization × 1
- logging × 1
- loop × 1
- macros × 1
- mailboxprocessor × 1
- mapping × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- mathjax × 1
- message × 1
- message passing × 1
- message-passing × 1
- meta × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- mix × 1
- mobile installation × 1
- mod_mono × 1
- modal × 1
- module × 1
- mouseevent × 1
- mouseposition × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- nunit × 1
- object relation mapper × 1
- object-oriented × 1
- om × 1
- onboarding × 1
- onclick × 1
- optimization × 1
- option × 1
- orm × 1
- os x × 1
- output-path × 1
- override × 1
- paper × 1
- parameter × 1
- persistence × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- post × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- privacy × 1
- private × 1
- profile × 1
- programming × 1
- project × 1
- project euler × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- proxy × 1
- ptvs × 1
- public × 1
- pure f# × 1
- purescript × 1
- qna × 1
- quant × 1
- query sitelet × 1
- question × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- ref × 1
- region × 1
- released in 4.0.190.100-rc × 1
- reporting × 1
- responsive design × 1
- rest api × 1
- rest sitelet × 1
- restful × 1
- round table × 1
- router × 1
- routing × 1
- rpc reverseproxy × 1
- runtime × 1
- sales × 1
- sample × 1
- sampleapp × 1
- scriptcs × 1
- scripting × 1
- search × 1
- self hosted × 1
- semanticui × 1
- sequence × 1
- serialisation × 1
- service × 1
- session-state × 1
- sharepoint × 1
- signals × 1
- sitelet website × 1
- sitelet.protect × 1
- sitlets × 1
- slickgrid × 1
- source code × 1
- sqlentityconnection × 1
- ssl × 1
- standards × 1
- static content × 1
- stickynotes × 1
- streamreader × 1
- stress × 1
- strong name × 1
- structures × 1
- submitbutton × 1
- subscribe × 1
- svg example html5 websharper.ui.next × 1
- sweetalert × 1
- system.datetime × 1
- system.reflection.targetinvocationexception × 1
- table storage × 1
- targets × 1
- tdd × 1
- templates ui.next × 1
- templating × 1
- text parsing × 1
- three.js × 1
- time travel × 1
- tls × 1
- tooltip × 1
- tracing × 1
- tsunamiide × 1
- turkish × 1
- twitter-bootstrap × 1
- type erasure × 1
- type inference × 1
- type providers × 1
- type-providers × 1
- typeprovider × 1
- ui next forms × 1
- ui-next × 1
- ui.next jqueryui × 1
- ui.next charting × 1
- ui.next formlets × 1
- ui.next forms × 1
- ui.next suave visualstudio × 1
- ui.next templating × 1
- unicode × 1
- unittest client × 1
- upload × 1
- usersession × 1
- validation × 1
- vb × 1
- vb.net × 1
- vector × 1
- view.map × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- vs2017 compiler zafir × 1
- vsix × 1
- web api × 1
- web-scraping × 1
- webapi × 1
- webcomponents × 1
- webforms × 1
- webgl × 1
- webrtc × 1
- webshaper × 1
- websharper async × 1
- websharper codemirror × 1
- websharper f# google × 1
- websharper forms × 1
- websharper reactive × 1
- websharper rpc × 1
- websharper sitelets routing × 1
- websharper warp × 1
- websharper-interface-generator × 1
- websharper.chartsjs × 1
- websharper.com × 1
- websharper.exe × 1
- websharper.owin × 1
- websharper.ui.next × 1
- websharper.ui.next jquery × 1
- websockets iis × 1
- why-websharper × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- www.grabbitmedia.com × 1
- xamarin × 1
- xml × 1
- yeoman × 1
- yield × 1
- zafir beta × 1
- zafir websharper4 × 1
- zarovizsga × 1
![]() |
Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy |
Built with WebSharper |
I've momentarily given up on trying to read Excel. I've copied an Excel file (25,000 rows) to a text file. I want to read the 7th column into an Array. Actually I just think this is what I want to do, but it should depend on how efficient various methods are.
Anyway, I've written the following code to accomplish this. It's really really slow. I didn't time it, but it probably takes about 15 minutes. This leads me to believe that I'm doing something really wrong. Could anyone let me know if there's a better way to do this, and what it is that I doing that is so stupid.
Oh, I copied the data reader stuff from a sample.
Thanks,
Mitchell