方便的F#片段

已经有两个有关F#/function片段的问题。

然而,我在这里寻找的是有用的片段,可以重复使用的小“辅助”function。 或者模糊,但漂亮的模式,你永远不会记得。

就像是:

open System.IO let rec visitor dir filter= seq { yield! Directory.GetFiles(dir, filter) for subdir in Directory.GetDirectories(dir) do yield! visitor subdir filter} 

我想使这是一个方便的参考页面。 因此,没有正确的答案,但希望有很多好的答案。

编辑 Tomas Petricek创build了一个专门为F#片段http://fssnip.net/网站 。

Perl风格的正则expression式匹配

 let (=~) input pattern = System.Text.RegularExpressions.Regex.IsMatch(input, pattern) 

它可以让你使用let test = "monkey" =~ "monk.+"符号来匹配文本。

多行string

这是相当微不足道的,但它似乎是F#string的一个function,这是广为人知的。

 let sql = "select a,b,c \ from table \ where a = 1" 

这产生:

 val sql : string = "select a,b,c from table where a = 1" 

当F#编译器在string文本中看到一个反斜杠后接回车符时,它将删除反斜杠到下一行的第一个非空格字符的所有内容。 这允许你有多行string文字排列,而不使用一堆string连接。

中缀操作员

我从http://sandersn.com/blog//index.php/2009/10/22/infix-function-trick-for-f得到这个更详细的信息。;

如果你知道Haskell,你可能会发现自己在F#中缺less中缀糖:

 // standard Haskell call has function first, then args just like F#. So obviously // here there is a function that takes two strings: string -> string -> string startsWith "kevin" "k" //Haskell infix operator via backQuotes. Sometimes makes a function read better. "kevin" `startsWith` "K" 

虽然F#没有一个真正的“中缀”操作符,但是同样的事情可以通过一个pipe道和一个“回streampipe道”(谁知道这样的事情?

 // F# 'infix' trick via pipelines "kevin" |> startsWith <| "K" 

一般的memoization ,礼貌的人自己

 let memoize f = let cache = System.Collections.Generic.Dictionary<_,_>(HashIdentity.Structural) fun x -> let ok, res = cache.TryGetValue(x) if ok then res else let res = fx cache.[x] <- res res 

使用这个,你可以像这样做一个caching的阅读器:

 let cachedReader = memoize reader 

对于性能密集的东西,你需要检查null

 let inline isNull o = System.Object.ReferenceEquals(o, null) if isNull o then ... else ... 

那么快20倍左右

 if o = null then ... else ... 

简单的读写文本文件

这些是微不足道的,但使文件访问pipeable:

 open System.IO let fileread f = File.ReadAllText(f) let filewrite fs = File.WriteAllText(f, s) let filereadlines f = File.ReadAllLines(f) let filewritelines f ar = File.WriteAllLines(f, ar) 

所以

 let replace f (r:string) (s:string) = s.Replace(f, r) "C:\\Test.txt" |> fileread |> replace "teh" "the" |> filewrite "C:\\Test.txt" 

结合这个问题中引用的访问者:

 let filereplace find repl path = path |> fileread |> replace find repl |> filewrite path let recurseReplace root filter find repl = visitor root filter |> Seq.iter (filereplace find repl) 

如果你想能够读取“locking”文件(例如,已经在Excel中打开的csv文件…),请稍微改进:

 let safereadall f = use fs = new FileStream(f, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) use sr = new StreamReader(fs, System.Text.Encoding.Default) sr.ReadToEnd() let split sep (s:string) = System.Text.RegularExpressions.Regex.Split(s, sep) let fileread f = safereadall f let filereadlines f = f |> safereadall |> split System.Environment.NewLine 

主动模式 ,又名“香蕉分割”,是一个非常方便的结构,让一个匹配多个正则expression式模式。 这很像AWK ,但没有DFA的高性能,因为这些模式是按顺序匹配的,直到成功。

 #light open System open System.Text.RegularExpressions let (|Test|_|) pat s = if (new Regex(pat)).IsMatch(s) then Some() else None let (|Match|_|) pat s = let opt = RegexOptions.None let re = new Regex(pat,opt) let m = re.Match(s) if m.Success then Some(m.Groups) else None 

一些使用的例子:

 let HasIndefiniteArticle = function | Test "(?: |^)(a|an)(?: |$)" _ -> true | _ -> false type Ast = | IntVal of string * int | StringVal of string * string | LineNo of int | Goto of int let Parse = function | Match "^LET\s+([AZ])\s*=\s*(\d+)$" g -> IntVal( g.[1].Value, Int32.Parse(g.[2].Value) ) | Match "^LET\s+([AZ]\$)\s*=\s*(.*)$" g -> StringVal( g.[1].Value, g.[2].Value ) | Match "^(\d+)\s*:$" g -> LineNo( Int32.Parse(g.[1].Value) ) | Match "^GOTO \s*(\d+)$" g -> Goto( Int32.Parse(g.[1].Value) ) | s -> failwithf "Unexpected statement: %s" s 

也许monad

 type maybeBuilder() = member this.Bind(v, f) = match v with | None -> None | Some(x) -> fx member this.Delay(f) = f() member this.Return(v) = Some v let maybe = maybeBuilder() 

这是一个简单介绍monads的外行。

选项 – 合并操作符

我想要一个更接近于C#空合并运算符的语法的defaultArg函数版本?? 。 这让我从一个选项中获取值,同时提供一个默认值,使用一个非常简洁的语法。

 /// Option-coalescing operator - this is like the C# ?? operator, but works with /// the Option type. /// Warning: Unlike the C# ?? operator, the second parameter will always be /// evaluated. /// Example: let foo = someOption |? default let inline (|?) value defaultValue = defaultArg value defaultValue /// Option-coalescing operator with delayed evaluation. The other version of /// this operator always evaluates the default value expression. If you only /// want to create the default value when needed, use this operator and pass /// in a function that creates the default. /// Example: let foo = someOption |?! (fun () -> new Default()) let inline (|?!) value f = match value with Some x -> x | None -> f() 

“单位化”不处理单位的函数使用FloatWithMeasure函数http://msdn.microsoft.com/en-us/library/ee806527(VS.100).aspx 。

 let unitize (f:float -> float) (v:float<'u>) = LanguagePrimitives.FloatWithMeasure<'u> (f (float v)) 

例:

 [<Measure>] type m [<Measure>] type kg let unitize (f:float -> float) (v:float<'u>) = LanguagePrimitives.FloatWithMeasure<'u> (f (float v)) //this function doesn't take units let badinc a = a + 1. //this one does! let goodinc v = unitize badinc v goodinc 3.<m> goodinc 3.<kg> 

旧版本

 let unitize (f:float -> float) (v:float<'u>) = let unit = box 1. :?> float<'u> unit * (f (v/unit)) 

荣誉kvb

比例/比率函数生成器

再次,微不足道,但方便。

 //returns a function which will convert from a1-a2 range to b1-b2 range let scale (a1:float<'u>, a2:float<'u>) (b1:float<'v>,b2:float<'v>) = let m = (b2 - b1)/(a2 - a1) //gradient of line (evaluated once only..) (fun a -> b1 + m * (a - a1)) 

例:

 [<Measure>] type m [<Measure>] type px let screenSize = (0.<px>, 300.<px>) let displayRange = (100.<m>, 200.<m>) let scaleToScreen = scale displayRange screenSize scaleToScreen 120.<m> //-> 60.<px> 

转换列表 (在Jomo Fisher的博客上看到)

 ///Given list of 'rows', returns list of 'columns' let rec transpose lst = match lst with | (_::_)::_ -> List.map List.head lst :: transpose (List.map List.tail lst) | _ -> [] transpose [[1;2;3];[4;5;6];[7;8;9]] // returns [[1;4;7];[2;5;8];[3;6;9]] 

这里是一个尾recursion的版本(从我粗略的分析)稍微慢一些,但是当内部列表超过10000个元素(在我的机器上)时,不会引发堆栈溢出:

 let transposeTR lst = let rec inner acc lst = match lst with | (_::_)::_ -> inner (List.map List.head lst :: acc) (List.map List.tail lst) | _ -> List.rev acc inner [] lst 

如果我很聪明,我会尝试并行与asynchronous…

F#Map < – > C#字典

(我知道,我知道,System.Collections.Generic.Dictionary不是一个真正的'C#'字典)

C#到F#

 (dic :> seq<_>) //cast to seq of KeyValuePair |> Seq.map (|KeyValue|) //convert KeyValuePairs to tuples |> Map.ofSeq //convert to Map 

(来自Brian, 在这里 ,Mauricio在下面的注释中提出了一些改进: (|KeyValue|)是一个用于匹配KeyValuePair – 从FSharp.Core – 等同于(fun kvp -> kvp.Key, kvp.Value)的主动模式。

有趣的select

为了获得所有不变的好处,但是用O(1)Dictionary的查找速度,你可以使用dict运算符,它返回一个不可变的IDictionary(见这个问题 )。

我目前看不到使用这种方法直接转换字典的方法,除了

 (dic :> seq<_>) //cast to seq of KeyValuePair |> (fun kvp -> kvp.Key, kvp.Value) //convert KeyValuePairs to tuples |> dict //convert to immutable IDictionary 

F#到C#

 let dic = Dictionary() map |> Map.iter (fun kt -> dic.Add(k, t)) dic 

这里有什么奇怪的是,FSI会报告types(例如):

 val it : Dictionary<string,int> = dict [("a",1);("b",2)] 

但是如果你把dict [("a",1);("b",2)]喂回来,FSI报告

 IDictionary<string,int> = seq[[a,1] {Key = "a"; Value = 1; } ... 

树sorting/将一个树展平成一个列表

我有以下二叉树:

  ___ 77 _ / \ ______ 47 __ 99 / \ 21 _ 54 \ / \ 43 53 74 / 39 / 32 

其中代表如下:

 type 'a tree = | Node of 'a tree * 'a * 'a tree | Nil let myTree = Node (Node (Node (Nil,21,Node (Node (Node (Nil,32,Nil),39,Nil),43,Nil)),47, Node (Node (Nil,53,Nil),54,Node (Nil,74,Nil))),77,Node (Nil,99,Nil)) 

一个简单的方法来扁平化树是:

 let rec flatten = function | Nil -> [] | Node(l, a, r) -> flatten l @ a::flatten r 

这不是尾recursion的,我相信@操作符会使它成为O(n log n)或O(n ^ 2)与不平衡的二叉树。 稍微调整一下,我想出了这个尾recursion的O(n)版本:

 let flatten2 t = let rec loop acc c = function | Nil -> c acc | Node(l, a, r) -> loop acc (fun acc' -> loop (a::acc') cl) r loop [] (fun x -> x) t 

以下是fsi中的输出:

 > flatten2 myTree;; val it : int list = [21; 32; 39; 43; 47; 53; 54; 74; 77; 99] 

LINQ到XML的帮手

 namespace System.Xml.Linq // hide warning about op_Explicit #nowarn "77" [<AutoOpen>] module XmlUtils = /// Converts a string to an XName. let xn = XName.op_Implicit /// Converts a string to an XNamespace. let xmlns = XNamespace.op_Implicit /// Gets the string value of any XObject subclass that has a Value property. let inline xstr (x : ^a when ^a :> XObject) = (^a : (member get_Value : unit -> string) x) /// Gets a strongly-typed value from any XObject subclass, provided that /// an explicit conversion to the output type has been defined. /// (Many explicit conversions are defined on XElement and XAttribute) /// Example: let value:int = xval foo let inline xval (x : ^a when ^a :> XObject) : ^b = ((^a or ^b) : (static member op_Explicit : ^a -> ^b) x) /// Dynamic lookup operator for getting an attribute value from an XElement. /// Returns a string option, set to None if the attribute was not present. /// Example: let value = foo?href /// Example with default: let value = defaultArg foo?Name "<Unknown>" let (?) (el:XElement) (name:string) = match el.Attribute(xn name) with | null -> None | att -> Some(att.Value) /// Dynamic operator for setting an attribute on an XElement. /// Example: foo?href <- "http://www.foo.com/" let (?<-) (el:XElement) (name:string) (value:obj) = el.SetAttributeValue(xn name, value) 

数组的加权和

根据权重的[k-array]计算数字的[k-arrays]的加权[n-array]和

(从这个问题复制,和KVB的答案 )

给定这些数组

 let weights = [|0.6;0.3;0.1|] let arrs = [| [|0.0453;0.065345;0.07566;1.562;356.6|] ; [|0.0873;0.075565;0.07666;1.562222;3.66|] ; [|0.06753;0.075675;0.04566;1.452;3.4556|] |] 

考虑到数组的两个维都是可变的,我们需要一个加权和(按列)。

 Array.map2 (fun w -> Array.map ((*) w)) weights arrs |> Array.reduce (Array.map2 (+)) 

第一行 :将第一个Array.map2函数部分应用于权重产生一个新的函数(Array.map((*)权重),它被应用于(对于每个权重)arr中的每个数组。

第二行 :Array.reduce就像fold,除了它从第二个值开始,并使用第一个作为初始“状态”。 在这种情况下,每个值都是我们数组数组的一个“行”。 因此,在前两行应用Array.map2(+)意味着我们总结前两个数组,这留给我们一个新的数组,然后我们(Array.reduce)再次总结到下一个(在这种情况下最后)arrays。

结果:

 [|0.060123; 0.069444; 0.07296; 1.5510666; 215.40356|] 

性能testing

(在这里find并更新最新版本的F#)

 open System open System.Diagnostics module PerformanceTesting = let Time func = let stopwatch = new Stopwatch() stopwatch.Start() func() stopwatch.Stop() stopwatch.Elapsed.TotalMilliseconds let GetAverageTime timesToRun func = Seq.initInfinite (fun _ -> (Time func)) |> Seq.take timesToRun |> Seq.average let TimeOperation timesToRun = GC.Collect() GetAverageTime timesToRun let TimeOperations funcsWithName = let randomizer = new Random(int DateTime.Now.Ticks) funcsWithName |> Seq.sortBy (fun _ -> randomizer.Next()) |> Seq.map (fun (name, func) -> name, (TimeOperation 100000 func)) let TimeOperationsAFewTimes funcsWithName = Seq.initInfinite (fun _ -> (TimeOperations funcsWithName)) |> Seq.take 50 |> Seq.concat |> Seq.groupBy fst |> Seq.map (fun (name, individualResults) -> name, (individualResults |> Seq.map snd |> Seq.average)) 

好吧,这与片段无关,但我一直在忘记这一点:

如果您在交互式窗口中, 则按F7跳回到代码窗口(不会取消您刚刚运行的代码…)

从代码窗口到F#窗口(也打开F#窗口)是Ctrl Alt F

(除非CodeRush盗取了你的绑定…)

DataSetExtensions for F#,DataReaders

System.Data.DataSetExtensions.dll添加了将DataTable作为IEnumerable<DataRow>处理的能力,并且通过支持System.Nullable以优雅地处理DBNull的方式拆箱单个单元格的值。 例如,在C#中,我们可以得到包含空值的整数列的值,并且使用非常简洁的语法指定DBNull应该默认为零:

 var total = myDataTable.AsEnumerable() .Select(row => row.Field<int?>("MyColumn") ?? 0) .Sum(); 

然而,有两个DataSetExtensions缺乏的地方。 首先,它不支持IDataReader ,其次,它不支持F# optiontypes。 下面的代码可以同时实现 – 它允许将IDataReader视为seq<IDataRecord> ,并且可以从读取器或数据集取消装箱值,同时支持F#选项或System.Nullable。 在另一个答案中与选项合并运算符相结合,在使用DataReader时,它允许使用如下代码:

 let total = myReader.AsSeq |> Seq.map (fun row -> row.Field<int option>("MyColumn") |? 0) |> Seq.sum 

也许更常用的F#忽略数据库空值的方式是…

 let total = myReader.AsSeq |> Seq.choose (fun row -> row.Field<int option>("MyColumn")) |> Seq.sum 

此外,下面定义的扩展方法可以从F#和C#/ VB中使用。

 open System open System.Data open System.Reflection open System.Runtime.CompilerServices open Microsoft.FSharp.Collections /// Ported from System.Data.DatasetExtensions.dll to add support for the Option type. [<AbstractClass; Sealed>] type private UnboxT<'a> private () = // This class generates a converter function based on the desired output type, // and then re-uses the converter function forever. Because the class itself is generic, // different output types get different cached converter functions. static let referenceField (value:obj) = if value = null || DBNull.Value.Equals(value) then Unchecked.defaultof<'a> else unbox value static let valueField (value:obj) = if value = null || DBNull.Value.Equals(value) then raise <| InvalidCastException("Null cannot be converted to " + typeof<'a>.Name) else unbox value static let makeConverter (target:Type) methodName = Delegate.CreateDelegate(typeof<Converter<obj,'a>>, typeof<UnboxT<'a>> .GetMethod(methodName, BindingFlags.NonPublic 

BindingFlags.Static) .MakeGenericMethod([| target.GetGenericArguments().[0] |])) |> unbox<Converter<obj,'a>> |> FSharpFunc.FromConverter static let unboxFn = let theType = typeof<'a> if theType.IsGenericType && not theType.IsGenericTypeDefinition then let genericType = theType.GetGenericTypeDefinition() if typedefof<Nullable<_>> = genericType then makeConverter theType "NullableField" elif typedefof<option<_>> = genericType then makeConverter theType "OptionField" else invalidOp "The only generic types supported are Option<T> and Nullable<T>." elif theType.IsValueType then valueField else referenceField static member private NullableField<'b when 'b : struct and 'b :> ValueType and 'b:(new:unit -> 'b)> (value:obj) = if value = null || DBNull.Value.Equals(value) then Nullable<_>() else Nullable<_>(unbox<'b> value) static member private OptionField<'b> (value:obj) = if value = null || DBNull.Value.Equals(value) then None else Some(unbox<'b> value) static member inline Unbox = unboxFn /// F# data-related extension methods. [<AutoOpen>] module FsDataEx = type System.Data.IDataReader with /// Exposes a reader's current result set as seq<IDataRecord>. /// Reader is closed when sequence is fully enumerated. member this.AsSeq = seq { use reader = this while reader.Read() do yield reader :> IDataRecord } /// Exposes all result sets in a reader as seq<seq<IDataRecord>>. /// Reader is closed when sequence is fully enumerated. member this.AsMultiSeq = let rowSeq (reader:IDataReader) = seq { while reader.Read() do yield reader :> IDataRecord } seq { use reader = this yield rowSeq reader while reader.NextResult() do yield rowSeq reader } /// Populates a new DataSet with the contents of the reader. Closes the reader after completion. member this.ToDataSet () = use reader = this let dataSet = new DataSet(RemotingFormat=SerializationFormat.Binary, EnforceConstraints=false) dataSet.Load(reader, LoadOption.OverwriteChanges, [| "" |]) dataSet type System.Data.IDataRecord with /// Gets a value from the record by name. /// DBNull and null are returned as the default value for the type. /// Supports both nullable and option types. member this.Field<'a> (fieldName:string) = this.[fieldName] |> UnboxT<'a>.Unbox /// Gets a value from the record by column index. /// DBNull and null are returned as the default value for the type. /// Supports both nullable and option types. member this.Field<'a> (ordinal:int) = this.GetValue(ordinal) |> UnboxT<'a>.Unbox type System.Data.DataRow with /// Identical to the Field method from DatasetExtensions, but supports the F# Option type. member this.Field2<'a> (columnName:string) = this.[columnName] |> UnboxT<'a>.Unbox /// Identical to the Field method from DatasetExtensions, but supports the F# Option type. member this.Field2<'a> (columnIndex:int) = this.[columnIndex] |> UnboxT<'a>.Unbox /// Identical to the Field method from DatasetExtensions, but supports the F# Option type. member this.Field2<'a> (column:DataColumn) = this.[column] |> UnboxT<'a>.Unbox /// Identical to the Field method from DatasetExtensions, but supports the F# Option type. member this.Field2<'a> (columnName:string, version:DataRowVersion) = this.[columnName, version] |> UnboxT<'a>.Unbox /// Identical to the Field method from DatasetExtensions, but supports the F# Option type. member this.Field2<'a> (columnIndex:int, version:DataRowVersion) = this.[columnIndex, version] |> UnboxT<'a>.Unbox /// Identical to the Field method from DatasetExtensions, but supports the F# Option type. member this.Field2<'a> (column:DataColumn, version:DataRowVersion) = this.[column, version] |> UnboxT<'a>.Unbox /// C# data-related extension methods. [<Extension; AbstractClass; Sealed>] type CsDataEx private () = /// Populates a new DataSet with the contents of the reader. Closes the reader after completion. [<Extension>] static member ToDataSet(this:IDataReader) = this.ToDataSet() /// Exposes a reader's current result set as IEnumerable{IDataRecord}. /// Reader is closed when sequence is fully enumerated. [<Extension>] static member AsEnumerable(this:IDataReader) = this.AsSeq /// Exposes all result sets in a reader as IEnumerable{IEnumerable{IDataRecord}}. /// Reader is closed when sequence is fully enumerated. [<Extension>] static member AsMultipleEnumerable(this:IDataReader) = this.AsMultiSeq /// Gets a value from the record by name. /// DBNull and null are returned as the default value for the type. /// Supports both nullable and option types. [<Extension>] static member Field<'T> (this:IDataRecord, fieldName:string) = this.Field<'T>(fieldName) /// Gets a value from the record by column index. /// DBNull and null are returned as the default value for the type. /// Supports both nullable and option types. [<Extension>] static member Field<'T> (this:IDataRecord, ordinal:int) = this.Field<'T>(ordinal)

在命令行应用程序中处理参数

 //We assume that the actual meat is already defined in function // DoStuff (string -> string -> string -> unit) let defaultOutOption = "N" let defaultUsageOption = "Y" let usage = "Scans a folder for and outputs results.\n" + "Usage:\n\t MyApplication.exe FolderPath [IncludeSubfolders (Y/N) : default=" + defaultUsageOption + "] [OutputToFile (Y/N): default=" + defaultOutOption + "]" let HandlArgs arr = match arr with | [|d;u;o|] -> DoStuff duo | [|d;u|] -> DoStuff du defaultOutOption | [|d|] -> DoStuff d defaultUsageOption defaultOutOption | _ -> printf "%s" usage Console.ReadLine() |> ignore [<EntryPoint>] let main (args : string array) = args |> HandlArgs 0 

(我对这种技术有一个模糊的记忆,受到Robert Pickering的启发,但是现在找不到参考)

一个方便的cachingfunction ,可以在字典中保持max (key,reader(key)) ,并使用SortedList来跟踪MRU密钥

 let Cache (reader: 'key -> 'value) max = let cache = new Dictionary<'key,LinkedListNode<'key * 'value>>() let keys = new LinkedList<'key * 'value>() fun (key : 'key) -> ( let found, value = cache.TryGetValue key match found with |true -> keys.Remove value keys.AddFirst value |> ignore (snd value.Value) |false -> let newValue = key,reader key let node = keys.AddFirst newValue cache.[key] <- node if (keys.Count > max) then let lastNode = keys.Last cache.Remove (fst lastNode.Value) |> ignore keys.RemoveLast() |> ignore (snd newValue)) 

创buildXElements

没有什么了不起,但是我一直被XNames的隐式转换所吸引:

 #r "System.Xml.Linq.dll" open System.Xml.Linq //No! ("type string not compatible with XName") //let el = new XElement("MyElement", "text") //better let xn s = XName.op_Implicit s let el = new XElement(xn "MyElement", "text") //or even let xEl so = new XElement(xn s, o) let el = xEl "MyElement" "text" 

Pairwise and pairs

I always expect Seq.pairwise to give me [(1,2);(3;4)] and not [(1,2);(2,3);(3,4)]. Given that neither exist in List, and that I needed both, here's the code for future reference. I think they're tail recursive .

 //converts to 'windowed tuples' ([1;2;3;4;5] -> [(1,2);(2,3);(3,4);(4,5)]) let pairwise lst = let rec loop prev rem acc = match rem with | hd::tl -> loop hd tl ((prev,hd)::acc) | _ -> List.rev acc loop (List.head lst) (List.tail lst) [] //converts to 'paged tuples' ([1;2;3;4;5;6] -> [(1,2);(3,4);(5,6)]) let pairs lst = let rec loop rem acc = match rem with | l::r::tl -> loop tl ((l,r)::acc) | l::[] -> failwith "odd-numbered list" | _ -> List.rev acc loop lst [] 

Naive CSV reader (ie, won't handle anything nasty)

(Using filereadlines and List.transpose from other answers here)

 ///Given a file path, returns a List of row lists let ReadCSV = filereadlines >> Array.map ( fun line -> line.Split([|',';';'|]) |> List.ofArray ) >> Array.toList ///takes list of col ids and list of rows, /// returns array of columns (in requested order) let GetColumns cols rows = //Create filter let pick cols (row:list<'a>) = List.map (fun i -> row.[i]) cols rows |> transpose //change list of rows to list of columns |> pick cols //pick out the columns we want |> Array.ofList //an array output is easier to index for user 

 "C:\MySampleCSV" |> ReadCSV |> List.tail //skip header line |> GetColumns [0;3;1] //reorder columns as well, if needs be. 

Date Range

simple but useful list of dates between fromDate and toDate

 let getDateRange fromDate toDate = let rec dates (fromDate:System.DateTime) (toDate:System.DateTime) = seq { if fromDate <= toDate then yield fromDate yield! dates (fromDate.AddDays(1.0)) toDate } dates fromDate toDate |> List.ofSeq 

toggle code to sql

More trivial than most on this list, but handy nonetheless:

I'm always taking sql in and out of code to move it to a sql environment during development. 例:

 let sql = "select a,b,c " + "from table " + "where a = 1" 

needs to be 'stripped' to:

 select a,b,c from table where a = 1 

keeping the formatting. It's a pain to strip out the code symbols for the sql editor, then put them back again by hand when I've got the sql worked out. These two functions toggle the sql back and forth from code to stripped:

 // reads the file with the code quoted sql, strips code symbols, dumps to FSI let stripForSql fileName = File.ReadAllText(fileName) |> (fun s -> Regex.Replace(s, "\+(\s*)\"", "")) |> (fun s -> s.Replace("\"", "")) |> (fun s -> Regex.Replace(s, ";$", "")) // end of line semicolons |> (fun s -> Regex.Replace(s, "//.+", "")) // get rid of any comments |> (fun s -> printfn "%s" s) 

then when you are ready to put it back into your code source file:

 let prepFromSql fileName = File.ReadAllText(fileName) |> (fun s -> Regex.Replace(s, @"\r\n", " \"\r\n+\"")) // matches newline |> (fun s -> Regex.Replace(s, @"\A", " \"")) |> (fun s -> Regex.Replace(s, @"\z", " \"")) |> (fun s -> printfn "%s" s) 

I'd love to get rid of the input file but can't even begin to grok how to make that happen. anyone?

编辑:

I figured out how to eliminate the requirement of a file for these functions by adding a windows forms dialog input/output. Too much code to show, but for those who would like to do such a thing, that's how I solved it.

Pascal's Triangle (hey, someone might find it useful)

So we want to create a something like this:

  1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 

Easy enough:

 let rec next = function | [] -> [] | x::y::xs -> (x + y)::next (y::xs) | x::xs -> x::next xs let pascal n = seq { 1 .. n } |> List.scan (fun acc _ -> next (0::acc) ) [1] 

The next function returns a new list where each item[i] = item[i] + item[i + 1].

Here's the output in fsi:

 > pascal 10 |> Seq.iter (printfn "%A");; [1] [1; 1] [1; 2; 1] [1; 3; 3; 1] [1; 4; 6; 4; 1] [1; 5; 10; 10; 5; 1] [1; 6; 15; 20; 15; 6; 1] [1; 7; 21; 35; 35; 21; 7; 1] [1; 8; 28; 56; 70; 56; 28; 8; 1] [1; 9; 36; 84; 126; 126; 84; 36; 9; 1] [1; 10; 45; 120; 210; 252; 210; 120; 45; 10; 1] 

For the adventurous, here's a tail-recursive version:

 let rec next2 cont = function | [] -> cont [] | x::y::xs -> next2 (fun l -> cont <| (x + y)::l ) <| y::xs | x::xs -> next2 (fun l -> cont <| x::l ) <| xs let pascal2 n = set { 1 .. n } |> Seq.scan (fun acc _ -> next2 id <| 0::acc)) [1] 

Flatten a List

if you have something like this:

 let listList = [[1;2;3;];[4;5;6]] 

and want to 'flatten' it down to a singe list so the result is like this:

 [1;2;3;4;5;6] 

it can be done thusly:

 let flatten (l: 'a list list) = seq { yield List.head (List.head l) for a in l do yield! (Seq.skip 1 a) } |> List.ofSeq 

List comprehensions for float

This [23.0 .. 1.0 .. 40.0] was marked as deprecated a few versions backed.

But apparently, this works:

 let dl = 9.5 / 11. let min = 21.5 + dl let max = 40.5 - dl let a = [ for z in min .. dl .. max -> z ] let b = a.Length 

(BTW, there's a floating point gotcha in there. Discovered at fssnip – the other place for F# snippets)

Parallel map

 let pmap fs = seq { for a in s -> async { return fs } } |> Async.Parallel |> Async.Run