Development Tip

편리한 F # 스 니펫

yourdevel 2020. 11. 12. 20:24
반응형

편리한 F # 스 니펫


F # / 기능적 조각에 대해 이미 두 가지 질문 이 있습니다 .

그러나 여기서 내가 찾고있는 것은 유용한 스 니펫, 재사용 가능한 작은 '도우미'기능입니다. 또는 결코 기억할 수없는 모호하지만 멋진 패턴.

다음과 같은 것 :

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는 http://fssnip.net/ F # 스 니펫 전용 사이트를 만들었습니다 .


Perl 스타일 정규식 일치

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

let test = "monkey" =~ "monk.+"표기법을 사용하여 텍스트를 일치시킬 수 있습니다 .


중위 연산자

나는 http://sandersn.com/blog//index.php/2009/10/22/infix-function-trick-for-f 에서 이것을 얻었습니다 . 자세한 내용은 해당 페이지로 이동하십시오.

Haskell을 알고 있다면 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" 

F #에는 진정한 '중위'연산자가 없지만 파이프 라인과 '백파이프 라인'을 통해 거의 동일한 작업을 수행 할 수 있습니다.

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

여러 줄 문자열

이것은 매우 사소하지만 널리 알려지지 않은 F # 문자열의 기능인 것 같습니다.

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

이것은 다음을 생성합니다.

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

F # 컴파일러는 문자열 리터럴 내에서 백 슬래시 뒤에 캐리지 리턴이 오는 것을 발견하면 백 슬래시에서 다음 줄의 공백이 아닌 첫 번째 문자까지 모든 것을 제거합니다. 이렇게하면 여러 줄의 문자열 연결을 사용하지 않고도 정렬되는 여러 줄 문자열 리터럴을 가질 수 있습니다.


일반 메모 , 남자 자신의 호의

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

이를 사용하여 다음과 같이 캐시 된 리더를 수행 할 수 있습니다.

let cachedReader = memoize reader

텍스트 파일에 대한 간단한 읽기-쓰기

이는 사소하지만 파일 액세스를 파이프 가능하게 만듭니다.

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)

그래서

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)

'잠긴'파일 (예 : 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  

null을 확인해야하는 성능 집약적 인 작업

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

약 20 배 더 빠릅니다.

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

"Banana Splits"라고도하는 활성 패턴 은 여러 정규식 패턴에 대해 하나의 일치를 허용하는 매우 편리한 구조입니다. 이는 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+([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

아마 모나드

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

초보자를위한 모나드에 대한 간략한 소개 입니다.


옵션 통합 연산자

defaultArgC # null-coalescing 연산자에 더 가까운 구문을 가진 함수 버전을 원했습니다 ??. 이렇게하면 매우 간결한 구문을 사용하여 기본값을 제공하면서 Option에서 값을 가져올 수 있습니다.

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

http://msdn.microsoft.com/en-us/library/ee806527(VS.100).aspxFloatWithMeasure 함수를 사용하여 단위처리하지 않는 함수를 '통합'합니다 .

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

그리고 여기에 (내 스케치 프로파일 링에서) 약간 느리지 만 내부 목록이 (내 컴퓨터에서) 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

내가 영리하다면 비동기로 병렬화하려고 노력할 것입니다 ...


F # 맵 <-> 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

(브라이언에서, 여기 . 아래 코멘트에 마우에 의해 제안 된 개선 (|KeyValue|)- FSharp.Core에서 - 동등한에 일치하는 KeyValuePair에 대한 활성 패턴입니다 (fun kvp -> kvp.Key, kvp.Value))

흥미로운 대안

불변의 장점을 모두 얻으려면 사전의 O (1) 조회 속도를 사용하여 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 k t -> dic.Add(k, t))
dic

여기서 이상한 점은 FSI가 유형을 다음과 같이보고한다는 것입니다 (예 :

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; } ...

트리 정렬 / 트리를 목록으로 병합

다음 이진 트리가 있습니다.

             ___ 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

이것은 꼬리 재귀가 아니며 @연산자가 불균형 이진 트리를 사용하여 O (n log n) 또는 O (n ^ 2)가되도록합니다. 약간의 조정으로 다음과 같은 꼬리 재귀 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

다음은 fsi의 출력입니다.

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

LINQ-to-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- 배열]을 기반으로 숫자 [n- 배열의 k- 배열]에 대한 가중치 [n- 배열] 합계 계산

( 이 질문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 함수를 부분적으로 적용하면 arr의 각 배열에 적용되는 새 함수 (Array.map ((*) 가중치)가 생성됩니다.

두 번째 줄 : Array.reduce는 두 번째 값에서 시작하고 첫 번째 값을 초기 '상태'로 사용한다는 점을 제외하면 폴드와 같습니다. 이 경우 각 값은 배열 배열의 '줄'입니다. 따라서 처음 두 줄에 Array.map2 (+)를 적용하면 처음 두 배열을 합산하여 새 배열을 남긴 다음 (Array.reduce) 다시 다음 배열에 합산합니다 (이 경우 마지막). 정렬.

결과:

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

성능 시험

( 여기 에서 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가 바인딩을 훔친 경우가 아니면 ...)


F #, DataReaders 용 DataSetExtensions

System.Data.DataSetExtensions.dllSystem.Nullable을 지원하여 우아하게 처리하는 방식으로 개별 셀의 값을 unboxing DataTable하는 IEnumerable<DataRow>것은 물론 a를 처리하는 기능을 추가합니다 DBNull. 예를 들어 C #에서는 null이 포함 된 정수 열의 값을 가져오고 DBNull매우 간결한 구문으로 기본값이 0으로 지정되도록 지정할 수 있습니다.

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

그러나 DataSetExtensions가 부족한 두 가지 영역이 있습니다. 첫째, 지원하지 않고 IDataReader둘째, F # option유형을 지원하지 않습니다 . 다음 코드는 두 가지를 모두 수행합니다. IDataReader를로 처리 seq<IDataRecord>할 수 있으며 F # 옵션 또는 System.Nullable을 지원하여 판독기 또는 데이터 집합에서 값을 unbox 할 수 있습니다. 다른 답변 의 옵션 통합 연산자와 결합 하면 DataReader로 작업 할 때 다음과 같은 코드를 사용할 수 있습니다.

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

아마도 데이터베이스 null을 무시하는 더 관용적 인 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 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

( 로버트 피커링 에서 영감을받은이 기술에 대한 모호한 기억이 있었지만 지금은 참조를 찾을 수 없습니다)


max (key,reader(key))사전에 보관하고 A를 사용 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))

XElement 생성

놀라운 것은 아니지만 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"

짝과 짝

나는 항상 Seq.pairwise가 [(1,2); (2,3); (3,4)]가 아닌 [(1,2); (3; 4)]를 줄 것으로 기대합니다. 둘 다 List에 존재하지 않고 둘 다 필요하다는 점을 감안할 때 향후 참조를위한 코드가 있습니다. 나는 그들이 꼬리 재귀 적이 라고 생각 합니다.

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

순진한 CSV 리더 (즉, 불쾌한 것을 처리하지 않음)

(여기에 다른 답변의 filereadlines 및 List.transpose 사용)

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

날짜 범위

사이의 날짜의 간단하지만 유용한 목록 fromDatetoDate

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

코드를 SQL로 전환

이 목록의 대부분보다 사소하지만 그럼에도 불구하고 편리합니다.

저는 개발 중에 SQL을 SQL 환경으로 옮기기 위해 항상 SQL을 코드 안팎으로 가져 가고 있습니다. 예:

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

다음을 위해 '제거'되어야합니다.

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

서식 유지. SQL 편집기의 코드 기호를 제거한 다음 SQL이 해결되면 다시 손으로 다시 넣는 것은 고통 스럽습니다. 이 두 함수는 SQL을 코드에서 스트립으로 앞뒤로 토글합니다.

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

그런 다음 코드 소스 파일에 다시 넣을 준비가되면 :

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)

나는 것 사랑 입력 파일을 제거 할 수 있지만, 심지어 일이 있는지 확인하는 방법을 grok 수 시작할 수 없습니다. 누군가?

편집하다:

Windows Forms 대화 상자 입력 / 출력을 추가하여 이러한 기능에 대한 파일 요구 사항을 제거하는 방법을 알아 냈습니다. 보여줄 코드가 너무 많지만 그런 일을하고 싶은 사람들에게는 그렇게 해결했습니다.


Pascal의 삼각형 (누군가가 유용하다고 생각할 수도 있습니다)

그래서 우리는 다음과 같은 것을 만들고 싶습니다.

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

충분히 쉬움 :

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]

next함수는 각 항목 [i] = 항목 [i] + 항목 [i + 1] 인 새 목록을 반환합니다.

다음은 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]

모험을 즐기는 분들을 위해 다음은 꼬리 재귀 버전입니다.

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]

목록 병합

다음과 같은 것이 있다면 :

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

결과는 다음과 같이 단일 목록으로 '평탄화'하고 싶습니다.

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

따라서 다음과 같이 할 수 있습니다.

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

float에 대한 목록 이해

이것은 [23.0 .. 1.0 .. 40.0]지원되는 몇 가지 버전에서 더 이상 사용되지 않는 것으로 표시되었습니다.

그러나 분명히 이것은 작동합니다.

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 f s =
    seq { for a in s -> async { return f s } }
    |> Async.Parallel
    |> Async.Run

참고URL : https://stackoverflow.com/questions/833180/handy-f-snippets

반응형