Предположение в комментариях выше оказалось верным.
ОБНОВЛЕНИЕ выполняло триггер, который в свою очередь имел ссылку на хранимую процедуру, которая больше не существует.
Транспонирование списка (видно из в блоге Джомо Фишера )
///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
Если бы я был умен, я бы попытался распараллелить его с асинхронным ...
Простые операции чтения и записи в текстовые файлы
Они тривиальны, но делают доступ к файлам доступным:
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
Удобная функция кеширования , которая хранит до макс
(ключ, считыватель (ключ))
в словаре и использует 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))
Активные шаблоны , также известные как «банановые расколы», представляют собой очень удобную конструкцию, позволяющую сопоставить несколько шаблонов регулярных выражений. Это очень похоже на 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
Общая мемоизация , любезно предоставлена самим человеком
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
'Unitize' функция, которая не обрабатывает единицы
Использование функции 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 версия :
let unitize (f:float -> float) (v:float<'u>) =
let unit = box 1. :?> float<'u>
unit * (f (v/unit))
Престижность kvb
Хорошо, это не имеет ничего общего с фрагментами, но я все время забываю об этом:
Если вы находитесь в интерактивном окне, вы нажимаете F7 , чтобы вернуться к окно кода (без отмены выбора кода, который вы только что запустили ...)
Для перехода из окна кода в окно F # (а также для открытия окна F #) используется Ctrl Alt F
] (если CodeRush не украл ваши привязки ...)
Сопоставление регулярных выражений в стиле Perl
let (=~) input pattern =
System.Text.RegularExpressions.Regex.IsMatch(input, pattern)
Позволяет сопоставить текст, используя нотацию let test = "monkey" = ~ "monk. +"
.
Naive CSV reader (i.e., 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
Example
"C:\MySampleCSV"
|> ReadCSV
|> List.tail //skip header line
|> GetColumns [0;3;1] //reorder columns as well, if needs be.
Параллельная карта
let pmap f s =
seq { for a in s -> async { return f s } }
|> Async.Parallel
|> Async.Run
Обработка аргументов в приложении командной строки:
//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
(Я смутно помнил, что этот метод был вдохновлен Робертом Пикерингом , но не могу найти ссылка сейчас)
Может быть, монада
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()
Вот краткое введение в монады для непосвященных.
Сортировка дерева / Сглаживание дерева в список
У меня есть следующее двоичное дерево:
___ 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]
Треугольник Паскаля (эй, кому-то это может пригодиться)
Итак, мы хотим создать что-то вроде этого:
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]
следующий
возвращает новый список, в котором каждый элемент [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]
Создание 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)]. Учитывая, что в 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 []
Взвешенная сумма массивов
Вычисление взвешенной [n-массива] суммы [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 ((*) weight), которая применяется (для каждого веса) к каждому массиву в arr.
Вторая строка : 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))
(я знаю, я знаю, что 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 |)
- активный шаблон для сопоставления KeyValuePair - из FSharp.Core - эквивалент (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; } ...