F#: Grouping a complex list - f#

I am trying to group the list by each element of player list
let playersScore= [
(["Player 1";"Player 2";], "First Game");
(["Player 2";"Player 3";"Player 4";], "Second Game");
(["Player 3"], "Third Game");
(["Player 1";"Player 2";"Player 3";"Player 4";], "Last Game")] : scorePlayerItem list
What I want to get is to find which player has joined to which games as
(string * string list) list
For example according to that list
Player1 -> First Game, Last Game
Player2 -> First Game, Second Game
Player3 -> Second, Third Game, Last Game
Player4 -> Second Game, Last Game
I tried to use List.groupBy but could not achive this. I just could not manage to group items by each element of player list.
Any help would be appreciated.

Here's a fairly simple solution:
let groups =
playersScore
|> List.collect (fun (players, game) -> players |> List.map (fun player -> player, game))
|> List.groupBy (fun (player, _) -> player)
|> Map.ofList
|> Map.map (fun _ games -> games |> List.map snd)
Break out the list of lists into a flat structure, group them by player, then convert to a map with the player as the key, and finally map the values to get just the name of the game. This produces the following map:
map
[("Player 1", ["First Game"; "Last Game"]);
("Player 2", ["First Game"; "Second Game"; "Last Game"]);
("Player 3", ["Second Game"; "Third Game"; "Last Game"]);
("Player 4", ["Second Game"; "Last Game"])]
EDIT
To keep things as a list, you can just use List.map instead of Map.ofList and Map.map:
let groups =
playersScore
|> List.collect (fun (players, game) -> players |> List.map (fun player -> player, game))
|> List.groupBy (fun (player, _) -> player)
|> List.map (fun (player, games) -> player, games |> List.map snd)
This returns the equivalent result as a list:
[("Player 1", ["First Game"; "Last Game"]);
("Player 2", ["First Game"; "Second Game"; "Last Game"]);
("Player 3", ["Second Game"; "Third Game"; "Last Game"]);
("Player 4", ["Second Game"; "Last Game"])]

Related

Create adjacency list with linked nodes using immutable lists

I'm trying to create a adjacency list with linked nodes that is currently defined like this:
type Node =
{
Name: string
Neighbors: Node list
}
type AdjacencyList(nodes: Node list) =
interface IHasNodes with
/// Gets the number of nodes in the adjacency list.
member this.NodeCount = nodes.Length
/// Gets a list of all nodes in the adjacency list.
member this.Nodes = nodes
The input from which i want to create the list is a sequence of strings in the format
node_name neighbor_name_1 ... neighbor_name_n
So, basically this should be a simple task, but i can't think of a way to update nodes without running into an cycle when one node, e.g. A, has an edge to B and B has an edge to A. In this case i have to update the neighbors of B when creating its node object and in turn update the neighbor reference in node A to node B, which in turn leaves me with updating node B again and so on.
module List =
/// <summary>
/// Replaces the first item in a list that matches the given predicate.
/// </summary>
/// <param name="predicate">The predicate for the item to replace.</param>
/// <param name="item">The replacement item.</param>
/// <param name="list">The list in which to replace the item.</param>
/// <returns>A new list with the first item that matches <paramref name="predicate"/> replaced by <paramref name="item"/>.</returns>
let replace predicate item list =
let rec replaceItem remainingItems resultList =
match remainingItems with
| [] -> resultList |> List.rev
| head::tail ->
match predicate(head) with
| false -> replaceItem tail (head::resultList)
| true -> (item::resultList |> List.rev) # tail
replaceItem list []
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module AdjacencyList =
let create (rows: seq<string>) =
let mutable preBuiltNodes: Node list = []
let rowsToEnumerate = rows |> Seq.where (fun str -> not (System.String.IsNullOrWhiteSpace(str)) || not (str.StartsWith("//")))
let neighborsForNodes = Dictionary<string, string array>()
// Build the base nodes and get the neighbors of each node.
for row in rowsToEnumerate do
let rowData = row.Split(' ')
neighborsForNodes.Add(rowData.[0], rowData |> Array.skip 1)
preBuiltNodes <- { Name = rowData.[0]; Neighbors = [] } :: preBuiltNodes
// Build the final nodes from the pre-built nodes.
let rec buildAdjacencyList remainingNodes (builtNodes: Node list) =
match remainingNodes with
| [] -> builtNodes
| head::tail ->
let neighbors = preBuiltNodes |> List.where (fun node -> neighborsForNodes.[head.Name] |> Array.exists (fun name -> name = node.Name))
let newNode = { head with Neighbors = neighbors };
// Update nodes referencing an old version of the new node.
let mutable newBuiltNodes: Node list = []
for i = 0 to (builtNodes.Length - 1) do
if builtNodes.[i].Neighbors |> List.exists (fun node -> node.Name = head.Name) then
let updatedNode = { builtNodes.[i] with Neighbors = builtNodes.[i].Neighbors |> List.replace (fun n -> n.Name = newNode.Name) newNode }
newBuiltNodes <- updatedNode :: newBuiltNodes
// Cycle here when updating newNode
// if it has an edge to the node at builtNodes.[i]
else
newBuiltNodes <- builtNodes.[i] :: newBuiltNodes
preBuiltNodes <- preBuiltNodes |> List.replace (fun n -> n.Name = newNode.Name) newNode
buildAdjacencyList tail (newNode::newBuiltNodes)
buildAdjacencyList preBuiltNodes [] |> AdjacencyList
I've implemented that algorithm in C# before (using mutable lists) so i might be missing a point here. Of course i could use mutable lists as well, but i wanted to try to use immutable ones.
I don't think there's any way to do what you want with your exact representation. One simple alternative is to make the set of neighbors lazy:
type node<'a> = {
id : 'a
neighbors : Lazy<node<'a> list>
}
let convert (m:Map<'a, 'a list>) =
let rec nodes = [
for KeyValue(k,vs) in m ->
{ id = k;
neighbors = lazy
vs
|> List.map (fun id ->
nodes
|> List.find (fun n -> n.id = id))}]
nodes
convert (Map [1,[2;3]; 2,[3]; 3,[1]])
The key is that by making the neighbor list lazy, we can first create the set of all nodes that we care about before populating the set of neighbors (the neighbor computation is specified at the same time as the node is created, but not run until later).
In C# you would include a reference to the neighbor Nodes. But with your definition you have a Neighbor node instead, that makes it an impossible task.
Apart from the solution by #kvb there are other options:
Option 1: use a string list
To me it would make much more sense to make the list reference the Node name and not the node itself:
type Node =
{
Name : string
Neighbors: string list
}
First some helper functions:
let addRelation relations (node1, node2) =
relations
|> Set.add (node1, node2)
|> Set.add (node2, node1)
let allRelations nodes =
nodes |> List.fold addRelation Set.empty
This would be the way to create it:
let getNodes nodes =
let rels = allRelations nodes
rels
|> Seq.groupBy fst
|> Seq.map (fun (name, neighbors) ->
{ Name = name
Neighbors = neighbors |> Seq.map snd |> Seq.toList
}
)
|> Seq.toList
and basically you feed it a list with the pair of names:
["1", "2"
"3", "4"
"1", "3"
"4", "5" ]
|> getNodes
|> Seq.iter (printfn "%A")
produces:
{Name = "1";
Neighbors = ["2"; "3"];}
{Name = "2";
Neighbors = ["1"];}
{Name = "3";
Neighbors = ["1"; "4"];}
{Name = "4";
Neighbors = ["3"; "5"];}
{Name = "5";
Neighbors = ["4"];}
Option 2: ref to Node list
You could have a reference to a list of neighbor nodes:
type Node =
{
Name : string
Neighbors: Node list ref
}
This way you can first create the nodes and then add them to the lists:
let getNodes nodePairs =
let rels = allRelations nodePairs
let nodes = rels |> Seq.map (fun (name, _) -> name, { Name = name ; Neighbors = ref [] }) |> Map
let getNode nm = nodes |> Map.find nm
rels
|> Seq.groupBy fst
|> Seq.iter (fun (name, neighbors) ->
(getNode name).Neighbors := neighbors |> Seq.map (snd >> getNode) |> Seq.toList
)
nodes |> Map.toList |> List.map snd
Test it like this:
["1", "2"
"3", "4"
"1", "3"
"4", "5" ]
|> getNodes
|> Seq.iter (printfn "%A")
output:
{Name = "1";
Neighbors =
{contents =
[{Name = "2";
Neighbors = {contents = [...];};};
{Name = "3";
Neighbors =
{contents =
[...;
{Name = "4";
Neighbors = {contents = [...; {Name = "5";
Neighbors = {contents = [...];};}];};}];};}];};}
{Name = "2";
Neighbors =
{contents =
[{Name = "1";
Neighbors =
{contents =
[...;
{Name = "3";
Neighbors =
{contents =
[...;
{Name = "4";
Neighbors =
{contents = [...; {Name = "5";
Neighbors = {contents = [...];};}];};}];};}];};}];};}
{Name = "3";
Neighbors =
{contents =
[{Name = "1";
Neighbors = {contents = [{Name = "2";
Neighbors = {contents = [...];};}; ...];};};
{Name = "4";
Neighbors = {contents = [...; {Name = "5";
Neighbors = {contents = [...];};}];};}];};}
{Name = "4";
Neighbors =
{contents =
[{Name = "3";
Neighbors =
{contents =
[{Name = "1";
Neighbors = {contents = [{Name = "2";
Neighbors = {contents = [...];};}; ...];};};
...];};}; {Name = "5";
Neighbors = {contents = [...];};}];};}
{Name = "5";
Neighbors =
{contents =
[{Name = "4";
Neighbors =
{contents =
[{Name = "3";
Neighbors =
{contents =
[{Name = "1";
Neighbors =
{contents = [{Name = "2";
Neighbors = {contents = [...];};}; ...];};}; ...];};};
...];};}];};}
The problem with your approach is the Node type. I propose a different approach like this:
type Node = Node of string
type Graph = Graph of Map<Node, Set<Node>>
let emptyGraph = Graph Map.empty
let addEdge nodeA nodeB g =
let update mainNode adjNode map =
let updatedAdjs =
match map |> Map.tryFind mainNode with
| Some adjs ->
adjs |> Set.add adjNode
| None ->
Set.singleton adjNode
map
|> Map.add mainNode updatedAdjs
let (Graph map) = g
map
|> update nodeA nodeB
|> update nodeB nodeA
|> Graph
let addIsolatedNode node g =
let (Graph map) = g
match map |> Map.tryFind node with
| Some _ ->
g
| None ->
map
|> Map.add node Set.empty
|> Graph
let getAdjs node g =
let (Graph map) = g
map
|> Map.tryFind node
// TEST
let myGraph =
emptyGraph
|> addEdge (Node "A") (Node "B")
|> addEdge (Node "A") (Node "C")
|> addEdge (Node "A") (Node "D")
|> addEdge (Node "B") (Node "C")
|> addEdge (Node "C") (Node "D")
|> addIsolatedNode (Node "E")
myGraph |> getAdjs (Node "A") // result: Some (set [Node "B"; Node "C"; Node "D"])
myGraph |> getAdjs (Node "E") // result: Some (set [])
myGraph |> getAdjs (Node "F") // result: None

F# combining two sequences

I have two sequences that I would like to combine somehow as I need the result of the second one printed right next to the first. The code is currently where playerItems refers to a list:
seq state.player.playerItems
|> Seq.map (fun i -> i.name)
|> Seq.iter (printfn "You have a %s")
seq state.player.playerItems
|> Seq.map (fun i -> i.description) |> Seq.iter (printfn "Description = %s")
The result currently is
You have a Keycard
You have a Hammer
You have a Wrench
You have a Screw
Description = Swipe to enter
Description = Thump
Description = Grab, Twist, Let go, Repeat
Description = Twisty poke
However, I need it to be
You have a Keycard
Description = Swipe to enter
You have a Hammer
Description = Thump
Any help with this would be very appreciated.
As Foggy Finder said in the comments, in your specific case you really don't have two sequences, you have one sequence and you want to print two lines for each item, which can be done with a single Seq.iter like this:
state.player.playerItems // The "seq" beforehand is not necessary
|> Seq.iter (fun player -> printfn "You have a %s\nDescription = %s" player.name player.description)
However, I'll also tell you about two ways to combine two sequences, for the time when you really do have two different sequences. First, if you want to turn the two sequences into a sequence of tuples, you'd use Seq.zip:
let colors = Seq.ofList ["red"; "green"; "blue"]
let numbers = Seq.ofList [25; 73; 42]
let pairs = Seq.zip colors numbers
printfn "%A" pairs
// Prints: seq [("red", 25); ("green", 73); ("blue", 42)]
If you want to combine the two sequences in some other way than producing tuples, use Seq.map2 and pass it a two-parameter function:
let colors = Seq.ofList ["red"; "green"; "blue"]
let numbers = Seq.ofList [25; 73; 42]
let combined = Seq.map2 (fun clr num -> sprintf "%s: %d" clr num) colors numbers
printfn "%A" combined
// Prints: seq ["red: 25"; "green: 73"; "blue: 42"]
Finally, if all you want is to perform some side-effect for each pair of items in the two sequences, then Seq.iter2 is your friend:
let colors = Seq.ofList ["red"; "green"; "blue"]
let numbers = Seq.ofList [25; 73; 42]
Seq.iter2 (fun clr num -> printfn "%s: %d" clr num)
That would print the following three lines to the console:
red: 25
green: 73
blue: 42
Note how in the Seq.iter function, I'm not storing the result. That's because the result of Seq.iter is always (), the "unit" value that is F#'s equivalent of void. (Except that it's much more useful than void, for reasons that are beyond the scope of this answer. Search Stack Overflow for "[F#] unit" and you should find some interesting questions and answers, like this one.

How to deal with missing properties in FSharp.Data JsonProvider?

Let's say there is a provider like this:
type ColorProvider = JsonProvider<"""
{
"id": "b35b5bcf-761a-4e50-9ff0-4c7de7dd0e5d",
"color": "Red"
}
""">
Trying to print colors from a collection will fail if one of these object doesn't have the color property at all:
dataAccess.QueryAsEnumerable<string>("SELECT Data FROM Objects")
|> Seq.map ColorProvider.Parse
|> Seq.iter (fun item -> printfn "%A" item.Color)
There is a JsonValue.Null to compare to but in this case it's not null, the property is just missing.
How to filter out items without the color property?
Your solution with TryGetProperty works, but there is much nicer way - you can use a more representative sample with two records where the color property is missing for one of them:
type ColorProvider = JsonProvider<"""[
{ "id": "b35b5bcf", "color": "Red" },
{ "id": "b2542345" } ]""", SampleIsList=true>
Then, the Color property is inferred as option<string> and you can handle it nicely using either pattern matching on options, or using defaultArg:
dataAccess.QueryAsEnumerable<string>("SELECT Data FROM Objects")
|> Seq.map ColorProvider.Parse
|> Seq.iter (fun item -> printfn "%s" (defaultArg item.Color " - "))
Okay, found it here:
dataAccess.QueryAsEnumerable<string>("SELECT Data FROM Objects")
|> Seq.map ColorProvider.Parse
|> Seq.iter (fun item ->
match item.JsonValue.TryGetProperty("color") with
| Some color -> printfn "%A" color
| None -> printfn "%s" " - "
)

Fetching and updating data in mnesia

I have multiple mnesia tuples like (GroupID is the primary key)
{GroupID, GroupName, GroupType, GroupDescription, GroupTag, CreatorID, AdminID, MemberList, Counter}.
MemberList = "memberone#xyz,membertwo#xyz,memberthree#xyz".
GroupName = "Any String Value". % e.g.: "basketball"
GroupTag = "comma separated values". % e.g.: "Sports,Cricket,Fifa,Ronaldo"
I will pass a character or word to a function. This function will search the character in GroupName and GroupTag.
If successful then it will return comma separated tuples of GroupID, GroupName, GroupDescription; And Counter should be incremented for the corresponding row.
Suppose in my mnesia database tuples are
{"A", "Cricket", "0", "A group for cricket fans", "Sports, Cricket, Sachin tendulkar", "Xyz", "XYZ", "XYZ", 1},
{"B", "Sports", "0", "A group for Sport fans", "Sports,Cricket,Fifa,Ronaldo,Sachin tendulkar", "Xyz", "XYZ", "XYZ", 0}.
So if I search for "sac", it should give the output
[{"A", "Cricket", "A group for cricket fans"},
{"B", "Sports", "A group for Sport fans"}]
Counter value for group A should be 2 (it was 1, check the last element of the tuple) and for group B should be 1 (it was 0, check the last element of the tuple).
Any pointers?
As far as I know, you cannot build a guard with a call to get the substring from a string, so instead of using a Erlang Match Specification you would have to iterate over all the records and filter the ones that you need.
-module(tuples).
-compile(export_all).
-record(group, {group_id,
group_name,
group_type,
group_description,
group_tag,
creator_id,
admin_id,
member_list,
counter}).
start() ->
mnesia:create_schema([node()]),
mnesia:start().
stop() ->
mnesia:stop(),
mnesia:delete_schema([node()]).
load_data() ->
mnesia:create_table(group,
[{attributes, record_info(fields, group)}]),
Record1 = #group{group_id = "A",
group_name = "Cricket",
group_type = "0",
group_description = "A group for cricket fans",
group_tag = "Spots,Cricket,Sachin tendulkar",
creator_id = "Xyz",
admin_id = "XYZ",
member_list = "XYZ",
counter = 1},
Record2 = #group{group_id = "B",
group_name = "Sports",
group_type = "0",
group_description = "A group for Sport fans",
group_tag = "Sports,Cricket,Fifa,Ronaldo,Sachin tendulkar",
creator_id = "Xyz",
admin_id = "XYZ",
member_list = "XYZ",
counter = 0},
Insert = fun() -> lists:map(fun(X) -> mnesia:write(X) end, [Record1, Record2]) end,
mnesia:transaction(Insert).
show_data() ->
CatchAll = [{'_', [], ['$_']}],
mnesia:dirty_select(group, CatchAll).
query(Substring) ->
Update = fun(Record) ->
NewRecord = Record#group{counter = Record#group.counter + 1},
mnesia:write(NewRecord),
NewRecord
end,
RequiredFields = fun(Record) -> {Record#group.group_id, Record#group.group_name, Record#group.group_description} end,
Constraint =
fun(Group, Acc) ->
case string:str(string:to_lower(Group#group.group_name),
string:to_lower(Substring)) of
0 ->
case string:str(string:to_lower(Group#group.group_tag),
string:to_lower(Substring)) of
0 ->
Acc;
_ ->
NewRecord = Update(Group),
[RequiredFields(Group) | NewRecord]
end;
_->
NewRecord = Update(Group),
[RequiredFields(Group) | NewRecord]
end
end,
Find = fun() -> mnesia:foldl(Constraint, [], group) end,
{_, Data} = mnesia:transaction(Find),
Data.
and to try the code:
Eshell V6.4 (abort with ^G)
1> c("tuples.erl").
{ok,tuples}
2> tuples:start().
ok
3> tuples:load_data().
{atomic,[ok,ok]}
4> tuples:show_data().
[{group,"A","Cricket","0","A group for cricket fans",
"Spots,Cricket,Sachin tendulkar","Xyz","XYZ","XYZ",1},
{group,"B","Sports","0","A group for Sport fans",
"Sports,Cricket,Fifa,Ronaldo,Sachin tendulkar","Xyz","XYZ",
"XYZ",0}]
5> tuples:query("sac").
[{group,"A","Cricket","0","A group for cricket fans",
"Spots,Cricket,Sachin tendulkar","Xyz","XYZ","XYZ",1}|
{group,"A","Cricket","0","A group for cricket fans",
"Spots,Cricket,Sachin tendulkar","Xyz","XYZ","XYZ",2}]
6> tuples:stop().
=INFO REPORT==== 14-Jun-2015::22:14:42 ===
application: mnesia
exited: stopped
type: temporary
ok
7> q().
ok
8>

Conditional sum in F#

I have defined a record type for some client data in F# as follows:-
type DataPoint = {
date: string;
dr: string;
Group: string;
Product: string;
Book: int;
Revenue: int} with
static member fromFile file =
file
|> File.ReadLines
|> Seq.skip 1 //skip the header
|> Seq.map (fun s-> s.Split ',') // split each line into array
|> Seq.map (fun a -> {date = string a.[0]; dr = string a.[1];
Group = string a.[2]; Product = string a.[3];
Book = int a.[4]; Revenue = int a.[5] });;
// creates a record for each line
let pivot (file) = DataPoint.fromFile file
|> ??????????
For the rows where date, dr, Group and Product are all equal, I want to then sum all of the Book and Revenue entries, producing a pivoted row. So some kind of if else statement should be fine. I suspect I need to start at the first data point and recursively add each matching row and then delete the matching row to avoid duplicates in the output.
Once I have done this I will be easily able to write these pivoted rows to another csv file.
Can anyone get me started?
Seq.groupBy and Seq.reduce are what you're looking for:
let pivot file =
DataPoint.fromFile file
|> Seq.groupBy (fun dp -> dp.date, dp.dr, dp.Group, dp.Product)
|> Seq.map (snd >> Seq.reduce (fun acc dp ->
{ date = acc.date; dr = acc.dr;
Group = acc.Group; Product = acc.Product;
Book = acc.Book + dp.Book;
Revenue = acc.Revenue + dp.Revenue; }))
Quickly hacked up, should give you some idea:
// Sample data
let data = [
{date = "2012-01-01"
dr = "Test"
Group = "A"
Product = "B"
Book = 123
Revenue = 123}
{date = "2012-01-01"
dr = "Test"
Group = "A"
Product = "B"
Book = 123
Revenue = 123}
{date = "2012-01-01"
dr = "Test"
Group = "B"
Product = "B"
Book = 11
Revenue = 123}]
let grouped = data |> Seq.groupBy(fun d -> (d.date, d.dr, d.Group, d.Product))
|> Seq.map (fun (k,v) -> (k, v |> Seq.sumBy (fun v -> v.Book), v |> Seq.sumBy (fun v -> v.Revenue)))
for g,books,revs in grouped do
printfn "Books %A: %d" g books
printfn "Revenues %A: %d" g revs
prints
Books ("2012-01-01", "Test", "A", "B"): 246
Revenues ("2012-01-01", "Test", "A", "B"): 246
Books ("2012-01-01", "Test", "B", "B"): 11
Revenues ("2012-01-01", "Test", "B", "B"): 11

Resources