From 85d94eca2fccfa477c599d351aa7a2be559cb55f Mon Sep 17 00:00:00 2001 From: Simon Diesenreiter Date: Thu, 5 Dec 2024 23:36:23 +0100 Subject: [PATCH] feat: implement generic data set manipulator, ref: NOISSUE --- TextParser.Tests/TextParserTests.cs | 80 +++++++++- TextParser/Data/DataConversionHelpers.cs | 49 ++++++ TextParser/Data/DataManipulationHelpers.cs | 23 +++ TextParser/Data/DataSetManipulatorBase.cs | 141 ++++++++++++++++++ TextParser/Data/DefaultDataSetIndexer.cs | 62 ++++++++ .../Data/DefaultOneDimensionalManipulator.cs | 60 ++++++++ TextParser/Data/DefaultPositionalDataIndex.cs | 14 ++ .../Data/DefaultTwoDimensionalManipulator.cs | 83 +++++++++++ TextParser/Data/Direction.cs | 43 ++++++ TextParser/Data/IDataIndex.cs | 4 + TextParser/Data/IDataSetIndexer.cs | 10 ++ TextParser/Data/ListIndexer.cs | 0 TextParser/{ => Data}/TokenConverter.cs | 69 +-------- TextParser/TextParser.cs | 1 + 14 files changed, 568 insertions(+), 71 deletions(-) create mode 100644 TextParser/Data/DataConversionHelpers.cs create mode 100644 TextParser/Data/DataManipulationHelpers.cs create mode 100644 TextParser/Data/DataSetManipulatorBase.cs create mode 100644 TextParser/Data/DefaultDataSetIndexer.cs create mode 100644 TextParser/Data/DefaultOneDimensionalManipulator.cs create mode 100644 TextParser/Data/DefaultPositionalDataIndex.cs create mode 100644 TextParser/Data/DefaultTwoDimensionalManipulator.cs create mode 100644 TextParser/Data/Direction.cs create mode 100644 TextParser/Data/IDataIndex.cs create mode 100644 TextParser/Data/IDataSetIndexer.cs create mode 100644 TextParser/Data/ListIndexer.cs rename TextParser/{ => Data}/TokenConverter.cs (63%) diff --git a/TextParser.Tests/TextParserTests.cs b/TextParser.Tests/TextParserTests.cs index 37cdb90..3384bd6 100644 --- a/TextParser.Tests/TextParserTests.cs +++ b/TextParser.Tests/TextParserTests.cs @@ -1,8 +1,8 @@ namespace TextParser.Tests; using Parsing; +using Parsing.Data; using Parsing.Schema; -using Parsing.Schema.BuildingBlocks; using Parsing.Tokenization; public class TextParserTests @@ -26,6 +26,10 @@ public class TextParserTests private const string testInput7 = @"adfdf1()324ddf3()()()svsdvs davnsldkvjs2()()m23423()()() mcsodkcn owdjnfj 1() asdfnad 23234 2()() sdvsdv"; + private const string testInput8 = @"2 4 6 4 1 3 5 4 7 2 4 6 8 3"; + private const string testInput9 = @"2 4 6 4 1 + 3 5 4 7 6 + 4 6 8 3 9"; [Fact] public void LineParser_TestSimpleRepetition() @@ -295,11 +299,11 @@ public class TextParserTests if (saw1) { indicator += 1; - } + } if (saw2) { indicator += 2; - } + } if (saw3) { indicator += 4; @@ -315,4 +319,74 @@ public class TextParserTests Assert.Equal(1, convertedData[4]); Assert.Equal(2, convertedData[5]); } + + [Fact] + public void DataManipulator_SimpleOneDimensionalTest() + { + var schemaBuilder = new InputSchemaBuilder(); + var schema = schemaBuilder + .Repeat() + .Expect(InputType.Integer) + .EndRepetition() + .Build(); + + var parser = new TextParser(schema); + var row = parser + .SetInputText(testInput8) + .Parse() + .AsSingleStream(); + + var searchSequence = new List { 4, 6 }; + var manipulator = DefaultOneDimensionalManipulator.Create(row); + var searchResults = manipulator.FindInSet(searchSequence); + + Assert.Equal(3, searchResults.Count); + Assert.Equal(1, searchResults[0].DataIndex.GetIndices()[0]); + Assert.Equal(3, searchResults[1].DataIndex.GetIndices()[0]); + Assert.Equal(10, searchResults[2].DataIndex.GetIndices()[0]); + Assert.Equal(Direction.Forward, searchResults[0].Direction); + Assert.Equal(Direction.Backward, searchResults[1].Direction); + Assert.Equal(Direction.Forward, searchResults[2].Direction); + } + + [Fact] + public void DataManipulator_SimpleTwoDimensionalTest() + { + var schemaBuilder = new InputSchemaBuilder(); + var schema = schemaBuilder + .Repeat() + .Expect(InputType.Integer) + .EndRepetition() + .Build(); + + var parser = new TextParser(schema); + var row = parser + .SetInputText(testInput9) + .Parse() + .AsListRows(); + + var searchSequence = new List { 4, 6 }; + var manipulator = DefaultTwoDimensionalManipulator.Create(row); + var searchResults = manipulator.FindInSet(searchSequence); + + Assert.Equal(6, searchResults.Count); + Assert.Equal(0, searchResults[0].DataIndex.GetIndices()[0]); + Assert.Equal(0, searchResults[0].DataIndex.GetIndices()[1]); + Assert.Equal(2, searchResults[1].DataIndex.GetIndices()[0]); + Assert.Equal(1, searchResults[1].DataIndex.GetIndices()[1]); + Assert.Equal(2, searchResults[2].DataIndex.GetIndices()[0]); + Assert.Equal(1, searchResults[2].DataIndex.GetIndices()[1]); + Assert.Equal(1, searchResults[3].DataIndex.GetIndices()[0]); + Assert.Equal(2, searchResults[3].DataIndex.GetIndices()[1]); + Assert.Equal(3, searchResults[4].DataIndex.GetIndices()[0]); + Assert.Equal(2, searchResults[4].DataIndex.GetIndices()[1]); + Assert.Equal(3, searchResults[5].DataIndex.GetIndices()[0]); + Assert.Equal(2, searchResults[5].DataIndex.GetIndices()[1]); + Assert.Equal(Direction.E, searchResults[0].Direction); + Assert.Equal(Direction.N, searchResults[1].Direction); + Assert.Equal(Direction.SW, searchResults[2].Direction); + Assert.Equal(Direction.E, searchResults[3].Direction); + Assert.Equal(Direction.SE, searchResults[4].Direction); + Assert.Equal(Direction.W, searchResults[5].Direction); + } } diff --git a/TextParser/Data/DataConversionHelpers.cs b/TextParser/Data/DataConversionHelpers.cs new file mode 100644 index 0000000..3edf187 --- /dev/null +++ b/TextParser/Data/DataConversionHelpers.cs @@ -0,0 +1,49 @@ +namespace Parsing.Data; + +using Parsing; +using Parsing.Tokenization; + +public static class DataConversionHelpers +{ + public static List ConvertData(this List tokenList, Func converter) where TTokenType : IValueToken + { + var newList = new List(); + foreach (var token in tokenList) + { + var typedToken = token as IValueToken; + if (typedToken == null) + { + throw new Exception("Invalid Token type encountered during value conversion"); + } + + newList.Add(converter(typedToken.GetValue())); + } + return newList; + } + + public static List ConvertData(this List tokenList, Func> converter) where TTokenType : IValueToken + { + var newList = new List(); + foreach (var token in tokenList) + { + var typedToken = token as IValueToken; + if (typedToken == null) + { + throw new Exception("Invalid Token type encountered during value conversion"); + } + + newList.AddRange(converter(typedToken.GetValue())); + } + return newList; + } + + public static List> ConvertData(this List> tokenListList, Func converter) where TTokenType : IValueToken + { + var newListList = new List>(); + foreach (var tokenList in tokenListList) + { + newListList.Add(tokenList.ConvertData(converter)); + } + return newListList; + } +} \ No newline at end of file diff --git a/TextParser/Data/DataManipulationHelpers.cs b/TextParser/Data/DataManipulationHelpers.cs new file mode 100644 index 0000000..68218a2 --- /dev/null +++ b/TextParser/Data/DataManipulationHelpers.cs @@ -0,0 +1,23 @@ +namespace Parsing.Data; + +public static class DataManipulationHelpers +{ + public static TType ReduceData(this List data, Func reducer) + { + if (data.Count < 2) + { + return data[0]; + } + TType result = data[0]; + for (int i = 1; i < data.Count; i++) + { + result = reducer(result, data[i]); + } + return result; + } + + public static TType ReduceData(this List data, Func, TType> reducer) + { + return reducer(data); + } +} \ No newline at end of file diff --git a/TextParser/Data/DataSetManipulatorBase.cs b/TextParser/Data/DataSetManipulatorBase.cs new file mode 100644 index 0000000..3f8da42 --- /dev/null +++ b/TextParser/Data/DataSetManipulatorBase.cs @@ -0,0 +1,141 @@ +using Parsing.Data; + +namespace Parsing.Data; + +public class SearchResult +{ + public IDataIndex? DataIndex { get; set; } +} + +public class DirectionalSearchResult : SearchResult +{ + public Direction Direction { get; set; } + public int Length { get; set; } +} + +public abstract class DataSetManipulatorBase where TDataType : IEquatable +{ + protected IDataSetIndexer indexer; + + protected List dataSet; + + public DataSetManipulatorBase(List dataSet, IDataSetIndexer indexer) + { + this.indexer = indexer; + this.dataSet = dataSet; + } + + // we do not know how to iterate a specific data set exactly, the implementation has to take care of validating directional input + protected abstract Direction ValidDirections(); + + protected void ValidateDirection(Direction d) + { + var allValidDirections = this.ValidDirections(); + var isValid = ((d | allValidDirections) == allValidDirections) && ((d & allValidDirections) > 0); + + if (!isValid) + { + throw new ArgumentException("Invalid search direction provided for given data set!"); + } + } + + protected List SimplifyDirections(Direction d) + { + this.ValidateDirection(d); + var allDirections = DirectionProvider.GetAllDirections(); + var singleDirections = new List(); + + foreach (Direction direction in allDirections) + { + if ((direction & d) > 0) + { + singleDirections.Add(direction); + } + } + + return singleDirections; + } + + public List GetValidDirectionList(Direction d) + { + return SimplifyDirections(this.ValidDirections()); + } + + // we do not know how to iterate a specific data set exactly, the implementation has to take care of ending traversal in any direction + public abstract bool IsValidIndex(IDataIndex queryPosition); + + // we do not know how to iterate a specific data set exactly, the implementation has to take care of traversing the set + public abstract IDataIndex Move(IDataIndex currentPosition, Direction direction); + + public List> GetNeighborIndices(IDataIndex currentPosition, Direction directions) + { + var singleDirections = this.SimplifyDirections(directions); + var neighbors = new List>(); + + foreach (var direction in singleDirections) + { + var newPosition = this.Move(currentPosition, direction); + if (this.IsValidIndex(newPosition)) + { + neighbors.Add(newPosition); + } + } + + return neighbors; + } + + // we do not know how to iterate a specific data set exactly, but we only need to find specific items to be able to continue with any other algorithm + public abstract List> FindInSet(TDataType data); + + public List> FindAtPosition(IDataIndex currentPosition, List data) + { + return this.FindAtPosition(currentPosition, data, this.ValidDirections()); + } + + public List> FindAtPosition(IDataIndex currentPosition, List data, Direction directions) + { + var results = new List>(); + var givenDirections = this.SimplifyDirections(directions); + if (EqualityComparer.Default.Equals(this.indexer.Get(this.dataSet, currentPosition), data[0])) + { + // found valid search start point, now validate each given direction + foreach (var direction in givenDirections) + { + int searchIndex = 1; + var searchPosition = this.Move(currentPosition, direction); ; + while (searchIndex < data.Count && this.IsValidIndex(searchPosition) + && EqualityComparer.Default.Equals(this.indexer.Get(this.dataSet, searchPosition), data[searchIndex])) + { + searchPosition = this.Move(searchPosition, direction); + searchIndex++; + } + if (searchIndex == data.Count) + { + var result = new DirectionalSearchResult(); + result.DataIndex = currentPosition; + result.Direction = direction; + result.Length = searchIndex; + results.Add(result); + } + } + } + + return results; + } + + public List> FindInSet(List data) + { + var result = new List>(); + + // find valid starting points in set and perform search from there + var startingPoints = this.FindInSet(data[0]); + foreach (var startingPoint in startingPoints) + { + foreach (var results in this.FindAtPosition(startingPoint.DataIndex, data)) + { + result.AddRange(results); + } + } + return result; + } +} \ No newline at end of file diff --git a/TextParser/Data/DefaultDataSetIndexer.cs b/TextParser/Data/DefaultDataSetIndexer.cs new file mode 100644 index 0000000..0eef44e --- /dev/null +++ b/TextParser/Data/DefaultDataSetIndexer.cs @@ -0,0 +1,62 @@ + + +public class DefaultDataSetIndexer : IDataSetIndexer +{ + public TDataType Get(List collection, IDataIndex index) + { + var indices = index.GetIndices(); + return this.GetInternal(collection, indices.ToArray()); + } + + private TDataType GetInternal(List collection, int[] indices) + { + if (indices.Length == 3) + { + return this.GetAtIndex((collection as List>>), indices[0], indices[1], indices[2]); + } + else if (indices.Length == 2) + { + return this.GetAtIndex((collection as List>), indices[0], indices[1]); + } + else if (indices.Length == 1) + { + return this.GetAtIndex((collection as List), indices[0]); + } + else + { + throw new ArgumentException("Invalid Data Set access!"); + } + } + + public TDataType Get(List collection, params int[] indices) + { + return this.GetInternal(collection, indices); + } + + public TDataType GetAtIndex(List collection, int index) + { + if (collection == null) + { + throw new ArgumentException("Invalid data set provided for access"); + } + return collection[index]; + } + + public TDataType GetAtIndex(List> collection, int x, int y) + { + if (collection == null) + { + throw new ArgumentException("Invalid data set provided for access"); + } + return collection[collection.Count - y - 1][x]; + } + + public TDataType GetAtIndex(List>> collection, int x, int y, int z) + { + if (collection == null) + { + throw new ArgumentException("Invalid data set provided for access"); + } + return collection[z][y][x]; + } +} \ No newline at end of file diff --git a/TextParser/Data/DefaultOneDimensionalManipulator.cs b/TextParser/Data/DefaultOneDimensionalManipulator.cs new file mode 100644 index 0000000..5124ab8 --- /dev/null +++ b/TextParser/Data/DefaultOneDimensionalManipulator.cs @@ -0,0 +1,60 @@ +using System.Runtime.InteropServices; +using Parsing.Data; + +namespace Parsing.Data; + +public static class DefaultOneDimensionalManipulator +{ + public static DefaultOneDimensionalManipulator Create(List dataSet) where TDataType : IEquatable + { + return new DefaultOneDimensionalManipulator(dataSet); + } +} + +public class DefaultOneDimensionalManipulator : DataSetManipulatorBase where TDataType : IEquatable +{ + public DefaultOneDimensionalManipulator(List dataSet) : base(dataSet, new DefaultDataSetIndexer()) + { + } + + protected override Direction ValidDirections() + { + return (Direction.Left | Direction.Right); + } + + public override bool IsValidIndex(IDataIndex queryPosition) + { + var index = queryPosition.GetIndices()[0]; + return (index >= 0) && (index < this.dataSet.Count); + } + + public override IDataIndex Move(IDataIndex currentPosition, Direction direction) + { + switch (direction) + { + case Direction.Forward: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] + 1); + case Direction.Backward: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] - 1); + default: + throw new ArgumentOutOfRangeException("Direction was not accounted for move for current data set!"); + } + } + + public override List> FindInSet(TDataType data) + { + var results = new List>(); + + for (int i = 0; i < this.dataSet.Count; i++) + { + if (EqualityComparer.Default.Equals(this.dataSet[i], data)) + { + var singleResult = new SearchResult(); + singleResult.DataIndex = new DefaultPositionalDataIndex(i); + results.Add(singleResult); + } + } + + return results; + } +} \ No newline at end of file diff --git a/TextParser/Data/DefaultPositionalDataIndex.cs b/TextParser/Data/DefaultPositionalDataIndex.cs new file mode 100644 index 0000000..fd7f464 --- /dev/null +++ b/TextParser/Data/DefaultPositionalDataIndex.cs @@ -0,0 +1,14 @@ +public class DefaultPositionalDataIndex : IDataIndex +{ + private List indices = new List(); + + public DefaultPositionalDataIndex(params int[] indices) + { + this.indices.AddRange(indices); + } + + public IList GetIndices() + { + return indices; + } +} \ No newline at end of file diff --git a/TextParser/Data/DefaultTwoDimensionalManipulator.cs b/TextParser/Data/DefaultTwoDimensionalManipulator.cs new file mode 100644 index 0000000..e712a99 --- /dev/null +++ b/TextParser/Data/DefaultTwoDimensionalManipulator.cs @@ -0,0 +1,83 @@ +using System.Runtime.InteropServices; +using Parsing.Data; + +namespace Parsing.Data; + +public static class DefaultTwoDimensionalManipulator +{ + public static DefaultTwoDimensionalManipulator Create(List> dataSet) where TDataType : IEquatable + { + return new DefaultTwoDimensionalManipulator(dataSet); + } +} + +public class DefaultTwoDimensionalManipulator : DataSetManipulatorBase, TDataType, int> where TDataType : IEquatable +{ + public DefaultTwoDimensionalManipulator(List> dataSet) : base(dataSet, new DefaultDataSetIndexer()) + { + } + + protected override Direction ValidDirections() + { + return (Direction.N + | Direction.NE + | Direction.E + | Direction.SE + | Direction.S + | Direction.SW + | Direction.W + | Direction.NW); + } + + public override bool IsValidIndex(IDataIndex queryPosition) + { + var xIndex = queryPosition.GetIndices()[0]; + var yIndex = queryPosition.GetIndices()[1]; + return (yIndex >= 0) && (yIndex < this.dataSet.Count) && (xIndex >= 0) && (xIndex < this.dataSet[yIndex].Count); + } + + public override IDataIndex Move(IDataIndex currentPosition, Direction direction) + { + switch (direction) + { + case Direction.N: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0], currentPosition.GetIndices()[1] + 1); + case Direction.NE: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] + 1, currentPosition.GetIndices()[1] + 1); + case Direction.E: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] + 1, currentPosition.GetIndices()[1]); + case Direction.SE: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] + 1, currentPosition.GetIndices()[1] - 1); + case Direction.S: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0], currentPosition.GetIndices()[1] - 1); + case Direction.SW: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] - 1, currentPosition.GetIndices()[1] - 1); + case Direction.W: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] - 1, currentPosition.GetIndices()[1]); + case Direction.NW: + return new DefaultPositionalDataIndex(currentPosition.GetIndices()[0] - 1, currentPosition.GetIndices()[1] + 1); + default: + throw new ArgumentOutOfRangeException("Direction was not accounted for move for current data set!"); + } + } + + public override List> FindInSet(TDataType data) + { + var results = new List>(); + + for (int y = 0; y < this.dataSet.Count; y++) + { + for (int x = 0; x < this.dataSet[this.dataSet.Count - y - 1].Count; x++) + { + if (EqualityComparer.Default.Equals(this.dataSet[this.dataSet.Count - y - 1][x], data)) + { + var singleResult = new SearchResult(); + singleResult.DataIndex = new DefaultPositionalDataIndex(x, y); + results.Add(singleResult); + } + } + } + + return results; + } +} \ No newline at end of file diff --git a/TextParser/Data/Direction.cs b/TextParser/Data/Direction.cs new file mode 100644 index 0000000..22a6951 --- /dev/null +++ b/TextParser/Data/Direction.cs @@ -0,0 +1,43 @@ +[Flags] +public enum Direction +{ + N = 1, + NE = 2, + E = 4, + SE = 8, + S = 16, + SW = 32, + W = 64, + NW = 128, + Horizontal = E | W, + Vertical = N | S, + Cardinal = Horizontal | Vertical, + RisingDiagonal = NE | SW, + FallingDiagonal = NW | SE, + Diagonal = RisingDiagonal | FallingDiagonal, + All = Cardinal | Diagonal, + Left = W, + Right = E, + Up = N, + Down = S, + Forward = Right, + Backward = Left +} + +public static class DirectionProvider +{ + public static Direction[] GetAllDirections() + { + var directions = new Direction[] { + Direction.N, + Direction.NE, + Direction.E, + Direction.SE, + Direction.S, + Direction.SW, + Direction.W, + Direction.NW + }; + return directions; + } +} \ No newline at end of file diff --git a/TextParser/Data/IDataIndex.cs b/TextParser/Data/IDataIndex.cs new file mode 100644 index 0000000..d8bfc56 --- /dev/null +++ b/TextParser/Data/IDataIndex.cs @@ -0,0 +1,4 @@ +public interface IDataIndex +{ + public IList GetIndices(); +} \ No newline at end of file diff --git a/TextParser/Data/IDataSetIndexer.cs b/TextParser/Data/IDataSetIndexer.cs new file mode 100644 index 0000000..0929616 --- /dev/null +++ b/TextParser/Data/IDataSetIndexer.cs @@ -0,0 +1,10 @@ +public interface IDataSetIndexer +{ + public TDataType Get(List collection, IDataIndex index); + public TDataType Get(List collection, params TIndexType[] indices); + public TDataType GetAtIndex(List collection, TIndexType index); + + public TDataType GetAtIndex(List> collection, TIndexType x, TIndexType y); + + public TDataType GetAtIndex(List>> collection, TIndexType x, TIndexType y, TIndexType z); +} \ No newline at end of file diff --git a/TextParser/Data/ListIndexer.cs b/TextParser/Data/ListIndexer.cs new file mode 100644 index 0000000..e69de29 diff --git a/TextParser/TokenConverter.cs b/TextParser/Data/TokenConverter.cs similarity index 63% rename from TextParser/TokenConverter.cs rename to TextParser/Data/TokenConverter.cs index 71588b5..5cd7fe4 100644 --- a/TextParser/TokenConverter.cs +++ b/TextParser/Data/TokenConverter.cs @@ -1,77 +1,10 @@ -namespace Parsing; +namespace Parsing.Data; using System; using System.Collections.Generic; using Parsing.Schema; using Parsing.Tokenization; -public static class DataConversionHelpers -{ - public static List ConvertData(this List tokenList, Func converter) where TTokenType : IValueToken - { - var newList = new List(); - foreach (var token in tokenList) - { - var typedToken = token as IValueToken; - if (typedToken == null) - { - throw new Exception("Invalid Token type encountered during value conversion"); - } - - newList.Add(converter(typedToken.GetValue())); - } - return newList; - } - - public static List ConvertData(this List tokenList, Func> converter) where TTokenType : IValueToken - { - var newList = new List(); - foreach (var token in tokenList) - { - var typedToken = token as IValueToken; - if (typedToken == null) - { - throw new Exception("Invalid Token type encountered during value conversion"); - } - - newList.AddRange(converter(typedToken.GetValue())); - } - return newList; - } - - public static List> ConvertData(this List> tokenListList, Func converter) where TTokenType : IValueToken - { - var newListList = new List>(); - foreach (var tokenList in tokenListList) - { - newListList.Add(tokenList.ConvertData(converter)); - } - return newListList; - } -} - -public static class DataManipulationHelpers -{ - public static TType ReduceData(this List data, Func reducer) - { - if (data.Count < 2) - { - return data[0]; - } - TType result = data[0]; - for (int i = 1; i < data.Count; i++) - { - result = reducer(result, data[i]); - } - return result; - } - - public static TType ReduceData(this List data, Func, TType> reducer) - { - return reducer(data); - } -} - public class TokenConverter { protected List> rawTokens = new List>(); diff --git a/TextParser/TextParser.cs b/TextParser/TextParser.cs index 976d256..c46eef3 100644 --- a/TextParser/TextParser.cs +++ b/TextParser/TextParser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using Parsing.Data; using Parsing.Schema; using Parsing.Tokenization;