Is there a way to avoid pattern matching - f#

I have SQL table for hierarchical data:
Maximum level of hierarchy is 5.
Lines #4 and #5 are children of line #1, for example.
I heed to have query expression to get child records by given one. Now I have this pattern matching:
let private queryForChild (db: dbml.MobileDataContext) id1 id2 id3 id4 id5 et =
match (id1, id2, id3, id4, id5) with
| _, "", "", "", "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 <> "" && rows.Id3 = "" && rows.Id4 = "" && rows.Id5 = "" && rows.EntityType = et)
select rows
}
| _, _, "", "", "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 <> "" && rows.Id4 = "" && rows.Id5 = "" && rows.EntityType = et)
select rows
}
| _, _, _, "", "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 = id3 && rows.Id4 <> "" && rows.Id5 = "" && rows.EntityType = et)
select rows
}
| _, _, _, _, "" -> query {
for rows in db.ItemType do
where (rows.Id1 = id1 && rows.Id2 = id2 && rows.Id3 = id3 && rows.Id4 = id4 && rows.Id5 <> "" && rows.EntityType = et)
select rows
}
| _, _, _, _, _ -> query {
for rows in db.ItemType do
where (rows.Id1 = "-1")
select rows
}
I don't like it and wondering is there any way to rewrite it using boolean operators to avoid pattern matching ?

It is indeed not possible to use custom functions in a query computation, because the inside of the query computation gets quoted (as an F# Quotation) and then (ultimately) translated to SQL, and custom functions can't be thus translated.
However, unlike C#, F# does offer a code reuse facility within code quotations - it's called "splicing".
Consider an example:
let q = query { for x in listOfInts do yield x + 42 }
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))
Let's say I really don't like that + 42 over there, I'd like to abstract it away. Well, I can do it like this:
let add42 = <# fun i -> i + 42 #>
let q = query { for x in listOfInts do yield (%add42) x }
If we now examine q.Expression, we'll find that it's identical to the previous version:
> q.Expression
val it : Expression = [1; 2; 3; ... ].Select(_arg1 => (_arg1 + 42))
Here's what's happened here. add42 is a code quotation that contains a function that adds 42 to its argument. The %add42 expression "inserts" (aka "splices") that quotation in the middle of the larger quotation, resulting in an expression like this:
let q = query { for x in listOfInts do yield (fun i -> i + 42) x }
This expression then got simplified during translation from F# code quotation to System.Linq.Expressions.Expression, resulting in an expression identical to the first version.
The final piece to add: spliced quotations don't have to be "constant", they can be produced by functions too. These functions get evaluated during construction of the overall quotation, and their results then get spliced. For example, I could redefine the above code like this:
let add a = <# fun x -> x + a #>
let q2 = query { for x in list do yield (% add 42) x }
Now add is a function that takes 42 as argument and produces a code quotation that contains another function. Phew!
And now we can apply all that to your case: make yourself a function that will take idx as argument and produce a quotation of a function that, after splicing, would be applied to row.idx:
// NOTE: I'm not sure if this logic is correct. You'll have to verify it.
//
// For the i-th ID:
// * if all previous IDs are non-empty,
// but the i-th ID itself is empty,
// then the condition should check for i-th ID being non-empty.
// This means "query rows of i-th level".
// * if all previous IDs are non-empty,
// and the i-th ID itself is non-empty,
// then the condition should check for i-th ID being equal to
// This means "query rows of j-th level", where j > i
// * Otherwise, the condition should check for
// the i-th ID being empty.
// This means "query rows of j-th level", where j < i
let compare prevIds thisId =
if List.all ((<>) "") prevIds
then if thisId = ""
then <# fun id -> id <> "" #>
else <# fun id -> id = thisId #>
else <# fun id -> id = "" #>
let private queryForChild (db: dbml.MobileDataContext) id1 id2 id3 id4 id5 et =
query {
for rows in db.ItemType do
where (
(% compare [] id1) rows.Id1 &&
(% compare [id1] id2) rows.Id2 &&
(% compare [id1; id2] id3) rows.Id3
(% compare [id1; id2; id3] id4) rows.Id4
(% compare [id1; id2; id3; id4] id5) rows.Id5 &&
rows.EntityType = et )
select rows
}
Also note that the way you constructed your function, its behavior is not well defined for inputs with "holes" - i.e. id1="x", id2="", id3="y" - did you mean to query second or fourth level in this case? I would recommend a better data structure that excludes nonsensical inputs.

Perhaps, you can use something like this:
let equalOrNotEmpty a b =
match a with
| "" -> b <> ""
| a -> a = b
and usage would be
let private queryForChild (db:dbml.MobileDataContext) id1 id2 id3 id4 id5 =
query {
for rows in db.ItemType do
where (equalOrNotEmpty rows.Id1 id1
&& equalOrNotEmpty rows.Id2 id2
&& equalOrNotEmpty rows.Id3 id3
&& equalOrNotEmpty rows.Id4 id4
&& equalOrNotEmpty rows.Id5 id5
&& rows.EntityType = et)
select rows
}

Related

How do I reduce code duplication with nested 'if' statements?

let's consider this code:
let getBuildDate (assembly: Assembly) : DateTime option =
let buildVersionMetadataPrefix = "+build"
let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
if attribute <> null && attribute.InformationalVersion <> null then
let value = attribute.InformationalVersion
let index = value.IndexOf(buildVersionMetadataPrefix)
if index > 0 then
let value = value.Substring(index + buildVersionMetadataPrefix.Length)
let success, timestamp = DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None)
if success then
Some timestamp
else
None
else
None
else
None
Is there a way to get rid of all the 'else None' statements to have only one?
On one side, I can imagine that for some people the code is more clear with all the None statements spelled out, but on the other side, coming from the C world, I see it as clutter that reduces readability.
There are many cases where you need a series of conditions to be met and all the failed cases go to one place.
If I have a list of conditions that depend on each others' success, how can I make a concise short exit without duplication.
Another approach might be to use the Option functions - each of these steps will effectively short circuit if the input from the previous step is None.
let getBuildDate (assembly: Assembly) : DateTime option =
let tryDate value =
match DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None) with
| true, date -> Some date
| false, _ -> None
let buildVersionMetadataPrefix = "+build"
let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
Option.ofObj attribute
|> Option.bind (fun attr -> Option.ofObj attr.InformationalVersion)
|> Option.map (fun infVer -> infVer, infVer.IndexOf buildVersionMetadataPrefix)
|> Option.filter (fun (_, index) -> index > 0)
|> Option.map (fun (infVer, index) -> infVer.Substring(index + buildVersionMetadataPrefix.Length))
|> Option.bind tryDate
Whether this is 'better' is arguable - and definitely a matter of opinion!
The other answers show how to do this using more sophisticated functional programming methods, like using computation expressions or option values. Those are definitely useful and make sense if this is something that you are doing in many places throughout your system.
However, if you just want a simple way to change the code so that the control flow is more clear (without making it more clever), I would negate the conditions. Previously, you had:
if something then
moreStuff()
Some result
else
None
You can rewrite this by returning None if not something. I think the F# coding convention in this case also allows you to remove the indentation, so it looks more like imperative early return:
if not something then None else
moreStuff()
Some result
With this, you can write your original function as follows - without any extra clever tricks:
let getBuildDate (assembly: Assembly) : DateTime option =
let buildVersionMetadataPrefix = "+build"
let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
if attribute = null || attribute.InformationalVersion = null then None else
let value = attribute.InformationalVersion
let index = value.IndexOf(buildVersionMetadataPrefix)
if index <= 0 then None else
let value = value.Substring(index + buildVersionMetadataPrefix.Length)
let success, timestamp = DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None)
if not success then None else
Some timestamp
A readable approach might be use a computation expression builder for Option.
type OptionBuilder() =
member _.Return v = Some v
member _.Zero () = None
member _.Bind(v, f) = Option.bind f v
member _.ReturnFrom o = o
let opt = OptionBuilder()
You can simulate an imperative style of if-then-return.
let condition num = num % 2 = 0
let result = opt {
if condition 2 then
if condition 4 then
if condition 6 then
return 10
}
Rewriting your example:
let getBuildDate (assembly: Assembly) : DateTime option = opt {
let buildVersionMetadataPrefix = "+build"
let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
if attribute <> null && attribute.InformationalVersion <> null then
let value = attribute.InformationalVersion
let index = value.IndexOf(buildVersionMetadataPrefix)
if index > 0 then
let value = value.Substring(index + buildVersionMetadataPrefix.Length)
let success, timestamp = DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None)
if success then
return timestamp
}
No more None.
open System
open System.Reflection
open System.Globalization
let inline guard cond next = if cond then next () else None
let getBuildDate (assembly: Assembly) : DateTime option =
let buildVersionMetadataPrefix = "+build"
let attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
guard (attribute <> null && attribute.InformationalVersion <> null) <| fun _ ->
let value = attribute.InformationalVersion
let index = value.IndexOf(buildVersionMetadataPrefix)
guard (index > 0) <| fun _ ->
let value = value.Substring(index + buildVersionMetadataPrefix.Length)
let success, timestamp = DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None)
guard success <| fun _ ->
Some timestamp
If you can stomach the inelegance of having to write <| fun _ -> on every guard, this is an option worth considering.
Have you considered using Result<TSuccess, TError>. It is very structuring - making the code rigid and flat - and makes it possible to provide detailed error information for the step that possible fails. It's a little more code, but IMO more readable and maintainable:
let getBuildDate (assembly: Assembly) : Result<DateTime, string> =
let buildVersionMetadataPrefix = "+build"
let extractAttribute (assem: Assembly) =
match assem.GetCustomAttribute<AssemblyInformationalVersionAttribute>() with
| attrib when attrib <> null -> Ok attrib
| _ -> Error "No attribute found"
let extractDateString (attrib: AssemblyInformationalVersionAttribute) =
match attrib.InformationalVersion.IndexOf (buildVersionMetadataPrefix) with
| x when x > 0 -> Ok (attrib.InformationalVersion.Substring (x + buildVersionMetadataPrefix.Length))
| _ -> Error "Metadata prefix not found"
let toDateTime dateString =
match DateTime.TryParseExact(dateString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None) with
| true, timeStamp -> Ok timeStamp
| false, _ -> Error "Invalid date time format"
extractAttribute assembly
|> Result.bind extractDateString
|> Result.bind toDateTime
Usage
let optBuildDate = getBuildDate (Assembly.GetExecutingAssembly())
match optBuildDate with
| Ok date -> printfn "%A" date
| Error msg -> printfn "ERROR: %s" msg
There is an approach that I really love which is the use of an array in certain scenarios.
Example:
Instead of using something like:
if (grade >= 90) {
scale = "A";
} else if (grade >= 80) {
scale = "B";
} else if (grade >= 70) {
scale = "C";
} else if (grade >= 60) {
scale = "D";
} else {
scale = "F";
}
Use an array like:
function calculate(scores) {
var grade, scale;
let sum = 0;
for (let i = 0; i < scores.length; i++) {
sum += scores[i];
}
grade = sum / scores.length;
scale = {
[90 <= grade && grade <= 100]: "O",
[80 <= grade && grade < 90]: "E",
[70 <= grade && grade < 80]: "A",
[55 <= grade && grade < 70]: "P",
[40 <= grade && grade < 55]: "D",
[grade < 40]: "T"
};
console.log(scale.true);
}
In python could be like:
def calculate(scores: list) -> str:
grade = sum(scores) / len(scores)
print(grade)
scale = {90 <= grade <= 100: "O", 80 <=
grade < 90: "E", 70 <= grade < 80: "A",
55 <= grade < 70: "P", 40 <= grade < 55: "D",
grade < 40: "T"}
return scale.get(True)

Why does this function using IndexOf always return 0?

I have this function that checks the number of occurrences of a pattern in a string. The problem is that is keeps returning 0 no matter the input. The most frustrating part is that it worked 2 min ago and I did not change anything.
let Counter (text : string) (pattern : string) =
let mutable count = 0
let mutable i = 0
while ((i = text.IndexOf(pattern, i)) <> false) do
i <- i + pattern.Length
count <- count + 1
count
The main problem is that it looks like you're trying to assign a new value to i inside the test in the while loop, but the = operator tests equality and does not perform assignment. The <- assignment operator has return type unit (it does not return the assigned value), so the fix can't be as simple as replacing the call to = with a call to <-.
The most straightforward fix is probably to break that test out into a separate inner function:
let counter (text : string) (pattern : string) =
let mutable i = 0
let moveNext() =
i <- text.IndexOf(pattern, i)
i
let mutable count = 0
while (moveNext() >= 0) do
i <- i + pattern.Length
count <- count + 1
count
However, note that this is not idiomatic F# code. Instead, I'd write it like this:
let counter (text : string) (pattern : string) =
let rec countFrom (i:int) total =
match text.IndexOf(pattern, i) with
| j when j >= 0 -> countFrom (j+pattern.Length) (total+1)
| _ -> total
countFrom 0 0

How to write a generic function that updates fields of a Record

Consider
type alias Rec = { a: Int, b: Int, c:Int }
updateRec r aVal = { r|a = aVal }
updateRec2 r aVal bVal = { r|a = aVal, b= bVal }
updateRec3 r aVal bVal cVal = ...
How to generalize updateRec and updateRec2... into one function?
Here's a better way of doing the same thing:
updateA : Int -> Rec -> Rec
updateA x rec = { rec | a = x }
-- Similarly for b, c
Now you can do the following, supposing you already have a value rec : Rec you want to update:
myUpdatedRec : Rec
myUpdatedRec =
rec
|> updateA 7
|> updateB 19
You can now update an arbitrary number of fields by stringing together |> updateX ....
You want to write a function that has a variable number of differently-typed parameters. That's common in dynamically typed languages (Javascript, Python, Ruby) but usually not allowed in typed languages. Elm doesn't allow it.
You can emulate a variable number of differently-typed parameterswith the Maybe type, understanding Nothing as "missing argument":
updateRec : Rec -> Maybe Int -> Maybe Int -> Maybe Int -> Rec
updateRec r a b c =
{ r
| a = a |> Maybe.withDefault r.a
, b = b |> Maybe.withDefault r.b
, c = c |> Maybe.withDefault r.c
}
If the record fields are all of the same type (here Int), you could accept a List Int instead:
updateRec : Rec -> List Int -> Rec
updateRec r fields =
case fields of
[a] -> { r | a = a }
[a,b] -> { r | a = a, b = b }
[a,b,c] -> { r | a = a, b = b, c = c }
_ -> r
I don't like this solution because you it'll fail silently if you accidentally supply a list with 0 or 4+ elements. If this function is helpful to you anyway, perhaps it would be better to use List Int instead of Rec in the first place.
Elm's record update syntax seems to be exactly what you are looking for. You "pass in" the record that you want updated, r below, and you can return a record with whatever fields you want changed without having to specify every field:
ex1 = { r|a = 1 }
ex2 = { r|b = 2, c = 3 }
You don't need to create a set of additional functions for updating only certain fields at certain times, because Elm's record update syntax is that generalized function.

F# records and mapping

I am new to F# and have been messing around with records and changing them. I am trying to apply my own function with out using map to my list. This is what i have so far. I am just wondering if my approach for how to write a mapping without using the map function the correct way of thinking about it.
module RecordTypes =
// creation of simple record
// immutable by default - key word mutable allows that to change
type Student =
{
Name : string
mutable age : int
mutable major : string
}
// setting up a few records with student information
// studentFive.age <- studentFive.age + 2 ; example of how to change mutable variable
let studentOne = { Name = "bob" ; age = 20 ; major = "spanish" }
let studentTwo= { Name = "sally" ; age = 18 ; major = "english" }
let studentThree = { Name = "frank" ; age = 22 ; major = "history" }
let studentFour = { Name = "lisa" ; age = 19 ; major = "math" }
let studentFive = { Name = "john" ; age = 17 ; major = "philosophy" }
// placing the records into a lits
let studentList = [studentOne; studentTwo; studentThree ;studentFour; studentFive]
// placing the records into a lits
let studentList = [studentOne; studentTwo; studentThree ;studentFour; studentFive]
// itterate through a list and printing each records
printf "the unsorted list of students: \n"
studentList |> List.iter (fun s-> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
// a sort of the records based on the name, can be sorted by other aspects in the records
let sortStudents alist =
alist
|> List.sortBy (function student -> student.age)
let rec selectionSort = function
| [] -> [] //if the list is empty it will return an empty list
| l -> let min = List.min l in // otherwise set a min variable and use the min function to find the smallest item in a list
let rest = List.filter (fun i -> i <> min) l in // set a variable to hold the rest of the list using filter
// Returns a new collection containing only the elements of the collection for which the given predicate returns true
// fun sets up a lambda expression that if ( i -> i <> (not equal boolean) min) if i(the record is not the min put it into a list)
let sortedList = selectionSort rest in // sort the rest of the list that isnt the min
min :: sortedList // :: is an operator that creates a list, left elem appended to right side
let unsortedList = studentList
let sortedList = selectionSort unsortedList
printfn "sorted list based on first name:\n"
sortedList |> List.iter(fun s -> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
here is where i tried to create my own map with function foo
let foo x = x + 1
let applyOnEachElement (list : Student list) (someFunction) =
list |> List.iter(fun s -> someFunction s.age)
//let agedStudents = applyOnEachElement studentList foo
printf " the students before function is applied to each: \n"
sortedList |> List.iter(fun s -> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
printf " the student after function is applied to each: \n"
agedStudents |> List.iter(fun s -> printf "Name: %s, Age: %d, Major: %s\n" s.Name s.age s.major)
In the last comment, the OP mentions his almost complete solution. With a bit of added formatting and a forgotten match construct, it looks as follows:
let rec applyOnEachElement2 (list: Student list) (f) =
match list with
| [] -> []
| hd :: tl -> hd::applyOnEachElement2 f tl
This is quite close to the correct implementation of map function! There are only two issues:
when calling applyOnEachElement2 recursively, you switched the parameters
the f parameter is passed recursively but never actually used for anything
To fix this, all you need is to switch the order of parameters (I'll do this on the function arguments to get the parameters in the same order as standard map) and call the f function on hd on the last line (so that the function returns a list of transformed elements):
let rec applyOnEachElement2 f (list: Student list) =
match list with
| [] -> []
| hd :: tl -> (f hd)::applyOnEachElement2 f tl
You can also make it generic by dropping the type annotation, which gives you a function with the same type signature as the built in List.map:
let rec applyOnEachElement2 f list =
match list with
| [] -> []
| hd :: tl -> (f hd)::applyOnEachElement2 f tl

F# How to Percentile Rank An Array of Doubles?

I am trying to take a numeric array in F#, and rank all the elements so that ties get the same rank. Basically I'm trying to replicate the algorithm I have below in C#, but just for an array of doubles. Help?
rankMatchNum = 0;
rankMatchSum = 0;
previousScore = -999999999;
for (int i = 0; i < factorStocks.Count; i++)
{
//The 1st time through it won't ever match the previous score...
if (factorStocks[i].factors[factorName + "_R"] == previousScore)
{
rankMatchNum = rankMatchNum + 1; //The count of matching ranks
rankMatchSum = rankMatchSum + i + 1; //The rank itself...
for (int j = 0; j <= rankMatchNum; j++)
{
factorStocks[i - j].factors[factorName + "_WR"] = rankMatchSum / (rankMatchNum + 1);
}
}
else
{
rankMatchNum = 0;
rankMatchSum = i + 1;
previousScore = factorStocks[i].factors[factorName + "_R"];
factorStocks[i].factors[factorName + "_WR"] = i + 1;
}
}
Here's how I would do it, although this isn't a direct translation of your code. I've done things in a functional style, piping results from one transformation to another.
let rank seq =
seq
|> Seq.countBy (fun x -> x) // count repeated numbers
|> Seq.sortBy (fun (k,v) -> k) // order by key
|> Seq.fold (fun (r,l) (_,n) -> // accumulate the number of items seen and the list of grouped average ranks
let r'' = r + n // get the rank after this group is processed
let avg = List.averageBy float [r+1 .. r''] // average ranks for this group
r'', ([for _ in 1 .. n -> avg]) :: l) // add a list with avg repeated
(0,[]) // seed the fold with rank 0 and an empty list
|> snd // get the final list component, ignoring the component storing the final rank
|> List.rev // reverse the list
|> List.collect (fun l -> l) // merge individual lists into final list
Or to copy Mehrdad's style:
let rank arr =
let lt item = arr |> Seq.filter (fun x -> x < item) |> Seq.length
let lte item = arr |> Seq.filter (fun x -> x <= item) |> Seq.length
let avgR item = [(lt item) + 1 .. (lte item)] |> List.averageBy float
Seq.map avgR arr
I think that you'll probably find this problem far easier to solve in F# if you rewrite the above in a declarative manner rather than in an imperative manner. Here's my off-the-top-of-my-head approach to rewriting the above declaratively:
First we need a wrapper class to decorate our items with a property carrying the rank.
class Ranked<T> {
public T Value { get; private set; }
public double Rank { get; private set; }
public Ranked(T value, double rank) {
this.Value = value;
this.Rank = rank;
}
}
Here, then, is your algorithm in a declarative manner. Note that elements is your input sequence and the resulting sequence is in the same order as elements. The delegate func is the value that you want to rank elements by.
static class IEnumerableExtensions {
public static IEnumerable<Ranked<T>> Rank<T, TRank>(
this IEnumerable<T> elements,
Func<T, TRank> func
) {
var groups = elements.GroupBy(x => func(x));
var ranks = groups.OrderBy(g => g.Key)
.Aggregate(
(IEnumerable<double>)new List<double>(),
(x, g) =>
x.Concat(
Enumerable.Repeat(
Enumerable.Range(x.Count() + 1, g.Count()).Sum() / (double)g.Count(),
g.Count()
)
)
)
.GroupBy(r => r)
.Select(r => r.Key)
.ToArray();
var dict = groups.Select((g, i) => new { g.Key, Index = i })
.ToDictionary(x => x.Key, x => ranks[x.Index]);
foreach (T element in elements) {
yield return new Ranked<T>(element, dict[func(element)]);
}
}
}
Usage:
class MyClass {
public double Score { get; private set; }
public MyClass(double score) { this.Score = score; }
}
List<MyClass> list = new List<MyClass>() {
new MyClass(1.414),
new MyClass(2.718),
new MyClass(2.718),
new MyClass(2.718),
new MyClass(1.414),
new MyClass(3.141),
new MyClass(3.141),
new MyClass(3.141),
new MyClass(1.618)
};
foreach(var item in list.Rank(x => x.Score)) {
Console.WriteLine("Score = {0}, Rank = {1}", item.Value.Score, item.Rank);
}
Output:
Score = 1.414, Rank = 1.5
Score = 2.718, Rank = 3
Score = 2.718, Rank = 3
Score = 2.718, Rank = 3
Score = 1.414, Rank = 1.5
Score = 3.141, Rank = 5
Score = 3.141, Rank = 5
Score = 3.141, Rank = 5
Score = 1.618, Rank = 8
Note that I do not require the input sequence to be ordered. The resulting code is simpler if you enforce such a requirement on the input sequence. Note further that we do not mutate the input sequence, nor do we mutate the input items. This makes F# happy.
From here you should be able to rewrite this in F# easily.
This is not a very efficient algorithm (O(n2)), but it's quite short and readable:
let percentile arr =
let rank item = ((arr |> Seq.filter (fun i -> i < item)
|> Seq.length |> float) + 1.0)
/ float (Array.length arr) * 100.0
Array.map rank arr
You might mess with the expression fun i -> i < e (or the + 1.0 expression) to achieve your desired way of ranking results:
let arr = [|1.0;2.0;2.0;4.0;3.0;3.0|]
percentile arr |> print_any;;
[|16.66666667; 33.33333333; 33.33333333; 100.0; 66.66666667; 66.66666667|]
Mehrdad's solution is very nice but a bit slow for my purposes. The initial sorting can be done 1 time. Rather than traversing the lists each time to get the number of items < or <= the target, we can use counters. This is more imperative (could have used a fold):
let GetRanks2 ( arr ) =
let tupleList = arr |> Seq.countBy( fun x -> x ) |> Seq.sortBy( fun (x,count) -> x )
let map = new System.Collections.Generic.Dictionary<int,float>()
let mutable index = 1
for (item, count) in tupleList do
let c = count
let avgRank =
let mutable s = 0
for i = index to index + c - 1 do
s <- s + i
float s / float c
map.Add( item, avgRank )
index <- index + c
//
map

Resources