Skip to content

Commit

Permalink
Added functions to convert objects and attributes based on name
Browse files Browse the repository at this point in the history
  • Loading branch information
johannesmols committed Mar 8, 2023
1 parent 0e5308d commit bf8abda
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 2 deletions.
88 changes: 87 additions & 1 deletion OCEL.CSharp.Tests/UtilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ public void CanCorrectlyMergeTwoLogs()
public void CanCorrectlyRemoveDuplicateObjects()
{
var timestamp = DateTimeOffset.Now;

var log = new OcelLog(
new Dictionary<string, OcelValue>(),
new Dictionary<string, OcelEvent>
Expand Down Expand Up @@ -99,5 +98,92 @@ public void CanCorrectlyRemoveDuplicateObjects()

Assert.True(log.MergeDuplicateObjects() == correctLog);
}

[Fact]
public void CanCorrectlyConvertObjectsToAttributes()
{
var timestamp = DateTimeOffset.Now;
var log = new OcelLog(
new Dictionary<string, OcelValue>(),
new Dictionary<string, OcelEvent>
{
{ "e1", new OcelEvent("Activity 1", timestamp, new List<string> { "o1", "o2" }, new Dictionary<string, OcelValue>()) },
{ "e2", new OcelEvent("Activity 2", timestamp, new List<string> { "o2" }, new Dictionary<string, OcelValue>()) }
},
new Dictionary<string, OcelObject>
{
{ "o1", new OcelObject("Type 1", new Dictionary<string, OcelValue> { { "prop1", new OcelString("some string") }, { "prop2", new OcelInteger(123) } })},
{ "o2", new OcelObject("Type 2", new Dictionary<string, OcelValue> { { "prop1", new OcelBoolean(false) } })},
}
);

var correctLog = new OcelLog(
new Dictionary<string, OcelValue>(),
new Dictionary<string, OcelEvent>
{
{ "e1", new OcelEvent("Activity 1", timestamp, new List<string>(),
new Dictionary<string, OcelValue>
{
{ "Type 1", new OcelMap(new Dictionary<string, OcelValue> {
{ "prop1", new OcelString("some string") },
{ "prop2", new OcelInteger(123) }
})},
{ "Type 2", new OcelBoolean(false) }
})
},
{ "e2", new OcelEvent("Activity 2", timestamp, new List<string>(), new Dictionary<string, OcelValue>
{
{ "Type 2", new OcelBoolean(false) }
})}
},
new Dictionary<string, OcelObject>()
);

Assert.True(log.ConvertObjectsToAttributes(new List<string> { "Type 1", "Type 2" }) == correctLog);
}

[Fact]
public void CanCorrectlyConvertAttributesToObjects()
{
var timestamp = DateTimeOffset.Now;
var log = new OcelLog(
new Dictionary<string, OcelValue>(),
new Dictionary<string, OcelEvent>
{
{ "e1", new OcelEvent("Activity 1", timestamp, new List<string>(),
new Dictionary<string, OcelValue>
{
{ "Type 1", new OcelMap(new Dictionary<string, OcelValue> {
{ "prop1", new OcelString("some string") },
{ "prop2", new OcelInteger(123) }
})},
{ "Type 2", new OcelBoolean(false) }
})
},
{ "e2", new OcelEvent("Activity 2", timestamp, new List<string>(), new Dictionary<string, OcelValue>
{
{ "Type 2", new OcelBoolean(false) }
})}
},
new Dictionary<string, OcelObject>()
);

var result = log.ConvertAttributesToObjects(new List<string> { "Type 1", "Type 2" });
Assert.All(result.Events, e =>
{
switch (e.Key)
{
case "e1":
Assert.True(e.Value.OMap.Count() == 2 && e.Value.VMap.Count == 0);
return;
case "e2":
Assert.True(e.Value.OMap.Count() == 1 && e.Value.VMap.Count == 0);
return;
default:
throw new Exception("Unknown event");
}
});
Assert.True(result.Objects.Count == 2);
}
}
}
21 changes: 21 additions & 0 deletions OCEL.CSharp/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using OCEL;
using OCEL.Types;
using System.Linq;
using Microsoft.FSharp.Collections;

namespace OCEL.CSharp
{
Expand Down Expand Up @@ -89,6 +90,26 @@ public OcelLog MergeDuplicateObjects()
return new OcelLog(this.ToFSharpOcelLog().MergeDuplicateObjects());
}

/// <summary>
/// Convert a list of object types to attributes by moving objects to all events that reference them.
/// </summary>
/// <param name="objectTypes">The object types that should be converted.</param>
/// <returns>An updated OCEL log with objects moved to events.</returns>
public OcelLog ConvertObjectsToAttributes(IEnumerable<string> objectTypes)
{
return new OcelLog(this.ToFSharpOcelLog().ConvertObjectsToAttributes(ListModule.OfSeq(objectTypes)));
}

/// <summary>
/// Convert a list of attributes to objects by moving attributes to new objects and add references to the events.
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
public OcelLog ConvertAttributesToObjects(IEnumerable<string> attributes)
{
return new OcelLog(this.ToFSharpOcelLog().ConvertAttributesToObjects(ListModule.OfSeq(attributes)));
}

private static IEnumerable<string> ExtractKeysFromValue(OcelValue value)
{
switch (value)
Expand Down
91 changes: 90 additions & 1 deletion OCEL.Tests/UtilityTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,93 @@ type UtilityTests(output: ITestOutputHelper) =
] |> Map.ofList
}

log.MergeDuplicateObjects() = correctLog |> Assert.True
log.MergeDuplicateObjects() = correctLog |> Assert.True

[<Fact>]
let ``Can correctly convert objects to attributes`` () =
let timestamp = DateTimeOffset.Now
let log = {
GlobalAttributes = Map.empty
Events = [
"e1", {
Activity = "Activity 1"
Timestamp = timestamp
OMap = ["o1"; "o2"]
VMap = Map.empty
}
"e2", {
Activity = "Activity 2"
Timestamp = timestamp
OMap = ["o2"]
VMap = Map.empty
}
] |> Map.ofList
Objects = [
"o1", {
Type = "Type 1"
OvMap = ["prop1", OcelString "some string"; "prop2", OcelInteger 123] |> Map.ofList
}
"o2", {
Type = "Type 2"
OvMap = ["prop1", OcelBoolean false] |> Map.ofList
}
] |> Map.ofList
}

let correctLog = {
GlobalAttributes = Map.empty
Events = [
"e1", {
Activity = "Activity 1"
Timestamp = timestamp
OMap = []
VMap = [
"Type 1", OcelMap(["prop1", OcelString "some string"; "prop2", OcelInteger 123] |> Map.ofList)
"Type 2", OcelBoolean false
] |> Map.ofList
}
"e2", {
Activity = "Activity 2"
Timestamp = timestamp
OMap = []
VMap = ["Type 2", OcelBoolean false] |> Map.ofList
}
] |> Map.ofList
Objects = Map.empty
}

log.ConvertObjectsToAttributes ["Type 1"; "Type 2"] = correctLog |> Assert.True

[<Fact>]
let ``Can correctly convert attributes to objects`` () =
let timestamp = DateTimeOffset.Now
let log = {
GlobalAttributes = Map.empty
Events = [
"e1", {
Activity = "Activity 1"
Timestamp = timestamp
OMap = []
VMap = [
"Type 1", OcelMap(["prop1", OcelString "some string"; "prop2", OcelInteger 123] |> Map.ofList)
"Type 2", OcelBoolean false
] |> Map.ofList
}
"e2", {
Activity = "Activity 2"
Timestamp = timestamp
OMap = []
VMap = ["Type 2", OcelBoolean false] |> Map.ofList
}
] |> Map.ofList
Objects = Map.empty
}

// Resulting log will have randomly assigned object ID's, so direct equality won't work like in previous tests
let result = log.ConvertAttributesToObjects ["Type 1"; "Type 2"]
result.Events |> Map.iter (fun k e ->
match k with
| "e1" -> (e.OMap.Length = 2 && e.VMap.IsEmpty) |> Assert.True
| "e2" -> (e.OMap.Length = 1 && e.VMap.IsEmpty) |> Assert.True
| _ -> failwith "Unknown event")
result.Objects.Count = 2 |> Assert.True
60 changes: 60 additions & 0 deletions OCEL/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,66 @@ type OcelLog with
Objects = updatedObjs
}

/// Convert a list of object types to attributes by moving objects to all events that reference them.
member this.ConvertObjectsToAttributes objectTypes =

/// Add an object as attributes to all events that reference the object ID
let addObjectToEvents objId (obj: OcelObject) (log: OcelLog) =
{ log with
Events =
log.Events
|> Map.map (fun _ e ->
match e.OMap |> List.tryFindIndex (fun id -> id = objId) with
| None -> e
| Some i ->
let attr = // If object only has one attribute, we don't need to use a map for it
match obj.OvMap |> Map.toList with
| [_, o] -> o
| _ -> OcelMap(obj.OvMap)
{ e with
OMap = e.OMap |> List.removeAt i
VMap = e.VMap |> Map.add obj.Type attr
}
)
}

/// Remove an object with some ID from the log
let removeObject objId log =
{ log with Objects = log.Objects |> Map.remove objId }

/// Convert all instances of an object type to attributes in all referencing events
let convertObjectToAttributes objType (log: OcelLog) =
let objs = log.Objects |> Map.filter (fun _ o -> o.Type = objType) |> Map.toList
(log, objs) ||> List.fold (fun log (id, obj) -> log |> addObjectToEvents id obj |> removeObject id)

(this, objectTypes) ||> List.fold (fun log objType -> convertObjectToAttributes objType log)

/// Convert a list of attributes to objects by moving attributes to new objects and add references to the events.
member this.ConvertAttributesToObjects attributes =

/// Move an attribute from an event to the object map, removing the attribute from itself and adding a reference to the object instead
let moveAttrFromEventToObject (id, event: OcelEvent) attr (log: OcelLog) =
let objId = Guid.NewGuid().ToString()
let newObj = {
Type = attr
OvMap =
match event.VMap[attr] with
| OcelMap m -> m
| _ -> ["value", event.VMap[attr]] |> Map.ofList
}

{ log with
Events = log.Events |> Map.change id (fun _ -> Some { event with VMap = event.VMap |> Map.remove attr; OMap = objId :: event.OMap })
Objects = log.Objects |> Map.add objId newObj
}

/// Find all events that have the attribute, and add them to the log as objects
let convertAttributesToObjects attr (log: OcelLog) =
let events = log.Events |> Map.filter (fun _ e -> e.VMap.ContainsKey attr) |> Map.toList
(log, events) ||> List.fold (fun log event -> moveAttrFromEventToObject event attr log)

(this, attributes) ||> List.fold (fun log attr -> convertAttributesToObjects attr log) |> fun log -> log.MergeDuplicateObjects()

/// An empty log
static member Empty =
{
Expand Down

0 comments on commit bf8abda

Please sign in to comment.