world visual studio sintaxis microsoft hello fsharp curso compilador f# reference code-snippets

f# - visual - fsharp



Handy F#snippets (30)

F # Mapa <-> C # Dictionary

(Lo sé, lo sé, System.Collections.Generic.Dictionary no es realmente un diccionario ''C #'')

C # a F #

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

(De Brian, here , con la mejora propuesta por Mauricio en el comentario a continuación. (|KeyValue|) es un patrón activo para hacer coincidir KeyValuePair - de FSharp.Core - equivalente a (fun kvp -> kvp.Key, kvp.Value) )

Interesante alternativa

Para obtener toda la bondad inmutable, pero con la velocidad de búsqueda O (1) del Diccionario, puede usar el operador dict , que devuelve un IDictionary inmutable (vea esta pregunta ).

Actualmente no puedo ver una forma de convertir directamente un diccionario utilizando este método, aparte de

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

F # a C #

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

Lo raro aquí es que FSI informará el tipo como (por ejemplo):

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

pero si vuelve a introducir dict [("a",1);("b",2)] , los informes de FSI

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

Ya hay two questions sobre F # / fragmentos de funciones.

Sin embargo, lo que estoy buscando aquí son fragmentos útiles , pequeñas funciones de "ayuda" que son reutilizables. O patrones oscuros pero ingeniosos que nunca podrás recordar del todo.

Algo como:

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}

Me gustaría hacer de esto una especie de página de referencia útil. Como tal, no habrá una respuesta correcta, pero con suerte muchas buenas.

EDITAR Tomas Petricek ha creado un sitio específicamente para F # snippets http://fssnip.net/ .


OK, esto no tiene nada que ver con los fragmentos, pero me olvido de esto:

Si se encuentra en la ventana interactiva, presione F7 para volver a la ventana de código (sin anular la selección del código que acaba de ejecutar ...)

Pasar de la ventana de códigos a la ventana F # (y también para abrir la ventana F #) es Ctrl Alt F

(a menos que CodeRush haya robado tus enlaces ...)


Para cosas intensivas en rendimiento donde necesita verificar null

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

Es aproximadamente 20 veces más rápido que

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


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


Operador Infix

Obtuve esto de http://sandersn.com/blog//index.php/2009/10/22/infix-function-trick-for-f vaya a esa página para más detalles.

Si conoces a Haskell, es posible que te falte un azúcar infijo en F #:

// 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"

Mientras que F # no tiene un verdadero operador ''infijo'', lo mismo se puede lograr casi tan elegantemente a través de una tubería y una ''línea de retroceso'' (¿quién sabía de tal cosa?)

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


Rango de fechas

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. Ejemplo:

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. ¿nadie?

editar:

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.


''Unificar'' una función que no maneja unidades Utilizando la función 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))

Ejemplo:

[<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>

VIEJA versión :

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

Felicitaciones a kvb


Ayudantes LINQ-a-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)


Coincidencia de expresiones regulares de estilo Perl

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

Le permite hacer coincidir el texto usando let test = "monkey" =~ "monk.+" Notación.


Creador de funciones de escala / relación

De nuevo, trivial, pero útil.

//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))

Ejemplo:

[<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>


Creando XElements

Nada sorprendente, pero sigo siendo atrapado por la conversión implícita de 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 s o = new XElement(xn s, o) let el = xEl "MyElement" "text"


Cuerdas de múltiples líneas

Esto es bastante trivial, pero parece ser una característica de las cadenas F # que no es ampliamente conocida.

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

Esto produce:

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

Cuando el compilador de F # ve una barra diagonal inversa seguida de un retorno de carro dentro de un literal de cadena, eliminará todo, desde la barra diagonal inversa hasta el primer carácter que no sea de espacio en la siguiente línea. Esto le permite tener literales de cadenas de múltiples líneas que se alinean, sin usar un montón de concatenación de cadenas.


DataSetExtensions para F #, DataReaders

System.Data.DataSetExtensions.dll agrega la capacidad de tratar un DataTable como un IEnumerable<DataRow> así como de desempaquetar los valores de las celdas individuales de una manera que gestiona DBNull al admitir System.Nullable. Por ejemplo, en C # podemos obtener el valor de una columna entera que contiene nulos, y especificar que DBNull debe establecerse en cero por defecto con una sintaxis muy concisa:

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

Sin embargo, hay dos áreas donde falta DataSetExtensions. En primer lugar, no es compatible con IDataReader y, en segundo lugar, no es compatible con el tipo de option F #. El siguiente código hace ambas cosas: permite que un IDataReader sea ​​tratado como un seq<IDataRecord> , y puede unbox valores de un lector o un conjunto de datos, con soporte para las opciones F # o System.Nullable. Combinado con el operador de coalescencia de opción en otra respuesta , esto permite un código como el siguiente cuando se trabaja con un DataReader:

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

Quizás una manera más idiomática de F # de ignorar los nulos de la base de datos sería ...

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

Además, los métodos de extensión definidos a continuación se pueden usar tanto desde F # como desde 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)


Lectura simple y archivos de texto

Estos son triviales, pero hacen que el acceso a los archivos pueda canalizarse:

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

Asi que

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

Y combinando eso con el visitante citado en la pregunta:

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)

Actualización ligera mejora si desea poder leer archivos ''bloqueados'' (por ejemplo, archivos csv que ya están abiertos en Excel ...):

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


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)


Los Patrones Activos , también conocidos como "Banana Splits", son una construcción muy útil que permite combinar con múltiples patrones de expresiones regulares. Esto es muy similar a AWK , pero sin el alto rendimiento de DFA porque los patrones se combinan en secuencia hasta que uno tiene éxito.

#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

Algunos ejemplos de uso:

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+([A-Z])/s*=/s*(/d+)$" g -> IntVal( g.[1].Value, Int32.Parse(g.[2].Value) ) | Match "^LET/s+([A-Z]/$)/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


Manejo de argumentos en una aplicación de línea de comando:

//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 d u o | [|d;u|] -> DoStuff d u defaultOutOption | [|d|] -> DoStuff d defaultUsageOption defaultOutOption | _ -> printf "%s" usage Console.ReadLine() |> ignore [<EntryPoint>] let main (args : string array) = args |> HandlArgs 0

(Tenía un vago recuerdo de que esta técnica estaba inspirada por Robert Pickering , pero ahora no puedo encontrar una referencia)


Memorización genérica , cortesía del hombre mismo

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 = f x cache.[x] <- res res

Al usar esto, podrías hacer un lector en caché como ese:

let cachedReader = memoize reader


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

Ejemplo

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


Operadores de fusión de opciones

Quería una versión de la función defaultArg que tuviera una sintaxis más cercana al operador C # null-coalescing, ?? . Esto me permite obtener el valor de una Opción a la vez que proporciona un valor predeterminado, usando una sintaxis muy concisa.

/// 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()


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 []


Parallel map

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


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]


Pruebas de rendimiento

(Se encuentra here y se actualizó para la última versión de 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))


Suma ponderada de matrices

Cálculo de una suma ponderada [n-array] de un [k-array de n-arrays] de números, basado en un [k-array] de ponderaciones

(Copiado de esta pregunta , y la answer kvb )

Dadas estas matrices

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|] |]

Queremos una suma ponderada (por columna), dado que ambas dimensiones de las matrices pueden ser variables.

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

Primera línea : la aplicación parcial de la primera función Array.map2 a pesos produce una nueva función (Array.map ((*) weight) que se aplica (para cada peso) a cada matriz en arr.

Segunda línea : Array.reduce es como fold, excepto que comienza en el segundo valor y usa el primero como el ''estado'' inicial. En este caso, cada valor es una ''línea'' de nuestra matriz de matrices. Entonces la aplicación de un Array.map2 (+) en las dos primeras líneas significa que sumamos las primeras dos matrices, lo que nos deja una nueva matriz, que luego (Array.reduce) sumamos de nuevo con la siguiente (en este caso, la última) formación.

Resultado:

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


Tal vez la mónada

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

Aquí hay una breve introducción a las monads para los no iniciados.


Transposición de una lista (vista en el blog de 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]]

Y aquí hay una versión recursiva de cola que (a partir de mi perfil incompleto) es levemente más lenta, pero tiene la ventaja de no arrojar un desbordamiento de pila cuando las listas internas son más largas que 10000 elementos (en mi máquina):

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

Si fuera inteligente, trataría de paralelizarlo con asincrónico ...


Tree-sort / Flatten a tree en una lista

Tengo el siguiente árbol binario:

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

Que se representa de la siguiente manera:

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))

Un método sencillo para aplanar el árbol es:

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

Esto no es recursivo de cola, y creo que el operador @ hace que sea O (n log n) u O (n ^ 2) con árboles binarios desequilibrados. Con un pequeño ajuste, se me ocurrió esta versión recíproca de 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'') c l) r loop [] (fun x -> x) t

Aquí está la salida en fsi:

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


Una práctica función de caché que mantiene hasta max (key,reader(key)) en un diccionario y utiliza SortedList para rastrear las claves 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))