key based functional fold - f#

I have a map reduce code for which I group in each of the threads by some key and then in the reduce part merge the results. My current approach is to search for an specific key index in the accumulator and then mapi to retrieve the combined result only for this key, leaving the rest unmodified:
let rec groupFolder sequence acc =
match sequence with
| (by:string, what) :: rest ->
let index = acc |> Seq.tryFindIndex( fun (byInAcc, _) -> byInAcc.Equals(by) )
match index with
| Some (idx) ->
acc |> Seq.mapi( fun i (byInAcc, whatInAcc) -> if i = idx then (by, (what |> Array.append whatInAcc) ) else byInAcc, whatInAcc )
|> groupFolder rest
| None -> acc |> Seq.append( seq{ yield (by, what) } )
|> groupFolder rest
My question is, is it a more functional way to achieve just this?
As an example input to this reducer
let GroupsCommingFromMap = [| seq { yield! [|("key1", [|1;2;3|] ); ("key2", [|1;2;3|] ); ("key3", [|1;2;3|]) |] }, seq { yield! [|("key1", [|4;5;6|] ); ("key2", [|4;5;6|] ); ("key3", [|4;5;6|]) |] } |];;
GroupsCommingFromMap |> Seq.reduce( fun acc i ->
acc |> groupFolder (i |> Seq.toList))
the expected result should contain all key1..key3 each with the array 1..6

From the code you posted, it is not very clear what you're trying to do. Could you include some sample inputs (together with the output that you would like to get)? And does your code actually work on any of the inputs (it has incomplete pattern match, so I doubt that...)
Anyway, you can implement key-based map reduce using Seq.groupBy. For example:
let mapReduce mapper reducer input =
input
|> Seq.map mapper
|> Seq.groupBy fst
|> Seq.map (fun (k, vs) ->
k, vs |> Seq.map snd |> Seq.reduce reducer)
Here:
The mapper takes a value from the input sequence and turns it into key value pair. The mapReduce function then groups the values using the key
The reducer is then used to reduce all values associated with each key
This lets you create a word count function like this (using simple mapper that returns the word as the key with 1 as a value and reducer that just adds all the numbers):
"hello world hello people hello world".Split(' ')
|> mapReduce (fun w -> w, 1) (+)
EDIT: The example you mentioned does not really have "mapper" part, but instead it has array of arrays as an input - so perhaps it is easier to write this directly using Seq.groupBy like this:
let GroupsCommingFromMap =
[| [|("key1", [|1;2;3|] ); ("key2", [|1;2;3|] ); ("key3", [|1;2;3|]) |]
[|("key1", [|4;5;6|] ); ("key2", [|4;5;6|] ); ("key3", [|4;5;6|]) |] |]
GroupsCommingFromMap
|> Seq.concat
|> Seq.groupBy fst
|> Seq.map (fun (k, vs) -> k, vs |> Seq.map snd |> Array.concat)

Related

F#.Data HTML Parser Extracting Strings From Nodes

I am trying to use FSharp.Data's HTML Parser to extract a string List of links from href attributes.
I can get the links printed out to console, however, i'm struggling to get them into a list.
Working snippet of a code which prints the wanted links:
let results = HtmlDocument.Load(myUrl)
let links =
results.Descendants("td")
|> Seq.filter (fun x -> x.HasClass("pagenav"))
|> Seq.map (fun x -> x.Elements("a"))
|> Seq.iter (fun x -> x |> Seq.iter (fun y -> y.AttributeValue("href") |> printf "%A"))
How do i store those strings into variable links instead of printing them out?
Cheers,
On the very last line, you end up with a sequence of sequences - for each td.pagenav you have a bunch of <a>, each of which has a href. That's why you have to have two nested Seq.iters - first you iterate over the outer sequence, and on each iteration you iterate over the inner sequence.
To flatten a sequence of sequences, use Seq.collect. Further, to convert a sequence to a list, use Seq.toList or List.ofSeq (they're equivalent):
let a = [ [1;2;3]; [4;5;6] ]
let b = a |> Seq.collect id |> Seq.toList
> val b : int list = [1; 2; 3; 4; 5; 6]
Applying this to your code:
let links =
results.Descendants("td")
|> Seq.filter (fun x -> x.HasClass("pagenav"))
|> Seq.map (fun x -> x.Elements("a"))
|> Seq.collect (fun x -> x |> Seq.map (fun y -> y.AttributeValue("href")))
|> Seq.toList
Or you could make it a bit cleaner by applying Seq.collect at the point where you first encounter a nested sequence:
let links =
results.Descendants("td")
|> Seq.filter (fun x -> x.HasClass("pagenav"))
|> Seq.collect (fun x -> x.Elements("a"))
|> Seq.map (fun y -> y.AttributeValue("href"))
|> Seq.toList
That said, I would rather rewrite this as a list comprehension. Looks even cleaner:
let links = [ for td in results.Descendants "td" do
if td.HasClass "pagenav" then
for a in td.Elements "a" ->
a.AttributeValue "href"
]

Map every 2 elements to a function/constructor

I have an integer array in which I want to send every two elements from it to the constructor of another function.
Something like intArray |> Array.map (fun x, y -> new Point(x, y))
Is this possible? I'm new to F# and functional programming so I'm trying to avoid just looping through every 2 items in the array and adding the point to a list. I hope that's reasonable.
If using F# 4.0, use Gustavo's approach. For F# 3, you can do:
intArray
|> Seq.pairwise // get sequence of tuples of element (1,2); (2,3); (3,4); (4,5) etc
|> Seq.mapi (fun i xy -> i, xy) // combine the index with the tuple
|> Seq.filter (fun (i,_) -> i % 2 = 0) // Filter for only the even indices to get (1,2); (3,4)
|> Seq.map (fun xy -> Point xy) // make a point from the tuples
|> Array.ofSeq // convert back to array
You can use Array.chunkBySize:
intArray
|> Array.chunkBySize 2
|> Array.map (function
| [|x; y|] -> new Point (x, y)
| _ -> failwith "Array length is not even.")
An alternative solution to the existing answers, would be to write a custom function, that creates a list/an array of tuples using pattern matching:
let chunkify arr =
let rec chunkify acc lst =
if (List.length lst) > 1 then (* proceed if there are at least two elements *)
match lst with
(* save every constructed pair, until the input is not empty *)
| h1 :: h2 :: tail -> chunkify ([(h1, h2)] # acc) tail
| _ -> acc (* else return the list of pairs *)
else (* return the list of pairs *)
acc
chunkify List.empty (List.ofSeq arr) |> List.rev |> Array.ofSeq
The function can be then used like this:
// helper
let print = (fun (x:'a, y:'a) -> printfn "new Object(%A,%A)" x y)
// ints
[|1;2;3;4;5;6|]
|> chunkify
|> Array.iter print
// strings
[|"a";"b";"c";"d";"e"|]
|> chunkify
|> Array.map print
|> ignore
The output is:
new Object(1,2)
new Object(3,4)
new Object(5,6)
new Object("a","b")
new Object("c","d")
This solution/approach uses pattern matching with lists.

GroupBy Year then take Pairwise diffs except for the head value then Flatten Using Deedle and F#

I have the following variable:
data:seq<(DateTime*float)>
and I want to do something like the following F# code but using Deedle:
data
|> Seq.groupBy (fun (k,v) -> k.Year)
|> Seq.map (fun (k,v) ->
let vals = v |> Seq.pairwise
let first = seq { yield v |> Seq.head }
let diffs = vals |> Seq.map (fun ((t0,v0),(t1,v1)) -> (t1, v1 - v0))
(k, diffs |> Seq.append first))
|> Seq.collect snd
This works fine using F# sequences but I want to do it using Deedle series. I know I can do something like:
(data:Series<DateTime*float>) |> Series.groupBy (fun k v -> k.Year)...
But then I need to take the within group year diffs except for the head value which should just be the value itself and then flatten the results into on series...I am bit confused with the deedle syntax
Thanks!
I think the following might be doing what you need:
ts
|> Series.groupInto
(fun k _ -> k.Month)
(fun m s ->
let first = series [ fst s.KeyRange => s.[fst s.KeyRange]]
Series.merge first (Series.diff 1 s))
|> Series.values
|> Series.mergeAll
The groupInto function lets you specify a function that should be called on each of the groups
For each group, we create series with the differences using Series.diff and append a series with the first value at the beginning using Series.merge.
At the end, we get all the nested series & flatten them using Series.mergeAll.

Merging two lists in F#

I wrote this function which merges two lists together but as I'm fairly new to functional programming I was wondering whether there is a better (simpler) way to do it?
let a = ["a"; "b"; "c"]
let b = ["d"; "b"; "a"]
let merge a b =
// take all a and add b
List.fold (fun acc elem ->
let alreadyContains = acc |> List.exists (fun item -> item = elem)
if alreadyContains = true then
acc
else
elem :: acc |> List.rev
) b a
let test = merge a b
Expected result is: ["a"; "b"; "c"; "d"], I'm reverting the list in order to keep the original order. I thought I would be able to achieve the same using List.foldBack (and dropping List.rev) but it results in an error:
Type mismatch. Expecting a
'a
but given a
'a list
The resulting type would be infinite when unifying ''a' and ''a list'
Why is there a difference when using foldBack?
You could use something like the following
let merge a b =
a # b
|> Seq.distinct
|> List.ofSeq
Note that this will preserve order and remove any duplicates.
In F# 4.0 this will be simplified to
let merge a b = a # b |> List.distinct
If I wanted to write this in a way that is similar to your original version (using fold), then the main change I would do is to move List.rev outside of the function (you are calling List.rev every time you add a new element, which is wrong if you're adding even number of elements!)
So, a solution very similar to yours would be:
let merge a b =
(b, a)
||> List.fold (fun acc elem ->
let alreadyContains = acc |> List.exists (fun item -> item = elem)
if alreadyContains = true then acc
else elem :: acc)
|> List.rev
This uses the double-pipe operator ||> to pass two parameters to the fold function (this is not necessary, but I find it a bit nicer) and then passes the result to List.rev.

Idiomatic way of aggregating denormalized data

I have a set of data of arrays of arrays. As an example
[[1,3],
[4,3],
[1,2],
[7,2]]
I'd like to transform this to
[(3,[1,4])
(2,[1,7])]
that is: create an array of tuples, where the first member is from index 1 of the original and the array is all the values of index 0 from the original grouped based on index 1. I can solve this imperatively but would like to do it in a more FP kind of way
Use Seq.groupBy in combination with a few maps will get the desired result
[[1;3];
[4;3];
[1;2];
[7;2]]
|> Seq.groupBy (fun (a::b) -> b)
|> Seq.map (fun (a,b) -> a,b|> Seq.toList)
|> Seq.map (fun (a,b) -> a,b|>List.map (fun (c::d) -> c ))
F# is a statically typed functional programming language so the first thing you want to do is convert your input into a typeful representation such as a list of pairs of ints:
[ 1, 3
4, 3
1, 2
7, 2 ]
Then you can pipe it through the Seq.groupBy function using the snd function to key on the second element of each pair:
|> Seq.groupBy snd
This gives you [3, [1, 3; 4, 3]; ...] etc. so you want to map over the right hand sides extracting just the values (i.e. stripping out the keys) using the fst function:
|> Seq.map (fun (k, kvs) -> k, Seq.map fst kvs)
This gives your desired answer: [(3, [1; 4]); (2, [1; 7])].
Similar to #John's answer, but assume that inner collections are arrays with at least two elements:
[|[|1; 3|];
[|4; 3|];
[|1; 2|];
[|7; 2|]|]
|> Seq.map (fun arr -> arr.[0], arr.[1])
|> Seq.groupBy snd
|> Seq.map (fun (k, v) -> k, Seq.map fst v)
// val it : seq<int * seq<int>> = seq [(3, seq [1; 4]); (2, seq [1; 7])]
My answer is not essentially different to the answers above, but it uses a bit of combinatory logic, so it looks more idiomatic (to me). Also, it has some validity check.
Apply2 is essentially an S combinator.
let data =
[[1;3];
[4;3];
[1;2];
[7;2]]
// Apply2 operator applies two functions to x
// and returns both results as a tuple
let (.&.) f g x = f x, g x
// A naive validator for sequences
let assert' predicate message xs =
if not <| Seq.forall predicate xs then
failwith message
xs
let aggregate data =
data
// validate the input
|> assert' (List.length >> (=) 2) "All elements must be of length of two"
// essentially, convert a 2-element list to a tuple
|> Seq.map (List.head .&. (List.tail >> List.head))
// group over the second element of a tuple
|> Seq.groupBy snd
// we no longer need the key element in a tuple, so remove it
|> Seq.map (fst .&. (snd >> Seq.map fst))
aggregate data |> printf "%A"

Resources