Ответ 1
Соответствие регулярному выражению стиля Perl
let (=~) input pattern =
System.Text.RegularExpressions.Regex.IsMatch(input, pattern)
Он позволяет вам сопоставлять текст с использованием нотации let test = "monkey" =~ "monk.+"
.
Уже есть два questions об 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}
Я хотел бы сделать это своего рода справочной страницей. Как таковой не будет правильного ответа, но, надеюсь, много хороших.
EDIT Tomas Petricek создал сайт специально для фрагментов F # http://fssnip.net/.
Соответствие регулярному выражению стиля Perl
let (=~) input pattern =
System.Text.RegularExpressions.Regex.IsMatch(input, pattern)
Он позволяет вам сопоставлять текст с использованием нотации let test = "monkey" =~ "monk.+"
.
Оператор Infix
Я получил это от 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 # не имеет истинного оператора "infix", то же самое можно сделать почти так же элегантно через конвейер и "backpipeline" (кто знал такую вещь??)
// 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
Для высокопроизводительных материалов, где вам нужно проверить null
let inline isNull o = System.Object.ReferenceEquals(o, null)
if isNull o then ... else ...
примерно в 20 раз быстрее, чем
if o = null then ... else ...
Простые чтения-записи в текстовые файлы
Это тривиально, но сделать доступ к файлам доступным:
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)
Обновить Небольшое улучшение, если вы хотите читать "заблокированные" файлы (например, файлы csv, которые уже открыты в 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
Активные шаблоны, также называемые "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
"Унифицировать" функцию, которая не обрабатывает единицы
Использование функции 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>
OLD version:
let unitize (f:float -> float) (v:float<'u>) =
let unit = box 1. :?> float<'u>
unit * (f (v/unit))
Престижность kvb
Возможно, монада
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()
Вот краткое введение в monads для непосвященных.
Операторы Option-coalescing
Мне нужна версия функции 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()
Конструктор функций масштабирования/соотношения
Опять же, тривиально, но удобно.
//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
Если бы я был умным, я бы попытался распараллелить его с помощью async...
(Я знаю, я знаю, System.Collections.Generic.Dictionary на самом деле не является словарем С#)
С# в F #
(dic :> seq<_>) //cast to seq of KeyValuePair
|> Seq.map (|KeyValue|) //convert KeyValuePairs to tuples
|> Map.ofSeq //convert to Map
(От Брайана, здесь, с улучшением, предложенным Mauricio в комментарии ниже. (|KeyValue|)
- активный шаблон для сопоставления KeyValuePair - от FSharp.Core - эквивалентно (fun kvp -> kvp.Key, kvp.Value)
)
Интересная альтернатива
Чтобы получить всю неизменную доброту, но с помощью скорости поиска O (1) словаря, вы можете использовать оператор dict
, который возвращает неизменяемый IDictionary (см. this вопрос).
В настоящее время я не вижу способа прямого преобразования словаря с помощью этого метода, кроме
(dic :> seq<_>) //cast to seq of KeyValuePair
|> (fun kvp -> kvp.Key, kvp.Value) //convert KeyValuePairs to tuples
|> dict //convert to immutable IDictionary
F # до С#
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)
Взвешенная сумма массивов
Вычисление взвешенной [n-array] суммы [k-массива n-массивов] чисел на основе [k-массива] весов
(Скопировано из этого вопроса и 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((*)), которая применяется (для каждого веса) к каждому массиву в обр.
Вторая строка: Array.reduce похожа на fold, за исключением того, что она начинается со второго значения и использует первое как начальное "состояние". В этом случае каждое значение является "линией" нашего массива массивов. Таким образом, применение 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 не украл ваши привязки...)
DataSetExtensions для F #, DataReaders
System.Data.DataSetExtensions.dll добавляет возможность обрабатывать DataTable
как IEnumerable<DataRow>
, а также распаковывать значения отдельных ячеек таким образом, чтобы изящно обрабатывать DBNull
, поддерживая System.Nullable. Например, в С# мы можем получить значение целочисленного столбца, содержащего нули, и указать, что DBNull
должен иметь значение по умолчанию с нулевым синтаксисом:
var total = myDataTable.AsEnumerable()
.Select(row => row.Field<int?>("MyColumn") ?? 0)
.Sum();
Есть две области, где отсутствуют DataSetExtensions. Во-первых, он не поддерживает IDataReader
, а во-вторых, он не поддерживает тип F # option
. Следующий код делает это: он позволяет обрабатывать 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 #, так и из С#/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 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 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)
Удобная функция кеширования, которая поддерживает 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))
Обработка аргументов в приложении командной строки:
//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
(У меня была смутная память об этом методе, вдохновленном Robert Pickering, но теперь не найти ссылку)
Создание XElements
Ничего удивительного, но я все время поймал неявное преобразование 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); (3; 4)], а не [(1,2); (2,3); (3,4)]. Учитывая, что в списке нет ни одного, и что мне нужны оба, вот код для дальнейшего использования. Я думаю, что они рекурсивные хвосты.
//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.
Треугольник Паскаля (эй, кому-то это может показаться полезным)
Итак, мы хотим создать что-то вроде этого:
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] = item [i] + item [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]
Диапазон дат
простой, но полезный список дат между fromDate
и 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
переключить код на 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)
Я бы люблю, чтобы избавиться от входного файла, но даже не могу понять, как это сделать. кто-нибудь?
редактировать:
Я понял, как устранить требование файла для этих функций, добавив диалог ввода/вывода диалогового окна. Слишком много кода для показа, но для тех, кто хотел бы сделать такое, как я его решил.
Сгладить список
если у вас есть что-то вроде этого:
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
(Кстати, там есть плавающая точка. Обнаружено в fssnip - другое место для фрагментов F #)
Параллельная карта
let pmap f s =
seq { for a in s -> async { return f s } }
|> Async.Parallel
|> Async.Run