How to move code to data, becoming more Data Driven Approach? - c#

For reference, please see article: https://gameprogrammingpatterns.com/bytecode.html#data-%3E-code
Hello, I'm currently looking at a lot of repetition in my game code in C#,
as for example:
public void SomeAction() {
DoSomething();
DoSomething();
DoAnotherThing();
}
I have managed to convert this code to data driven approach, using command pattern:
public List<Command> commands;
public void DoAction() {
foreach (cmd in commands) {
cmd.Execute();
}
}
I have realized that Data driven approach is beautifully designed and it is the right approach for me.
But then I stumbled into this one:
private float[] data;
public IEnumerable<float> ExampleQuery() {
return data
.Select(x => x + 2)
.Where(x => x < 50)
.Select(x => x * 3)
.Select(x => x * 10)
.Select(x => x > 999 ? 999 : x);
}
how to convert to this:
private float[] data;
public List<Rules> rules;
public IEnumerable<float> Query() {
// should I foreach here?
}
As you can see, this one should be data driven, so that if I want to make more rules for data query, I don't have to recompile and can just add more rules into the list.
From the reference:
We want them to be easy to modify, easy to reload, and physically separate from the rest of the executable.
I don’t know about you, but to me that sounds a lot like data. If we can define our behavior in separate data files that the game engine loads and “executes” in some way, we can achieve all of our goals. We just need to figure out what “execute” means for data.
I don't need to be all-out using bytecode pattern, I just want my rules to be hierarchy data, that is modeled using class/object, such as command pattern.
*Any good reference article will be a big help also.

You could do:
public List<Rules> rules;
var floats = data.Select(x => x + 2)
.Where(x => x < 50)
.Select(x => x * 3)
.Select(x => x * 10)
.Select(x => x > 999 ? 999 : x);
public List<Rules> ExampleQuery()
{
foreach(var d in floats)
{
rules.Add(d);
}
return rules;
}

Just use the a list of functions like this:
List<( Func<float,bool> where, Func<float,float> select )> rules;
public IEnumerable<float> Query ( IEnumerable<float> original ) {
IEnumerable<float> result = original;
foreach ( var i in rules ) {
result = result.Where( i.where ).Select( i.select );
}
return result;
}
rules.AddRange( new[] {
( (x => true), (x => x + 2) ),
( (x => x < 50), (x => x) ),
( (x => true), (x => x * 3) ),
( (x => true), (x => x * 10) ),
( (x => true), (x => ( x > 999 ) ? 999 : x) )
} );
or even better:
List<Func<IEnumerable<float>,IEnumerable<float>>> rules;
public IEnumerable Query ( IEnumerable<float> original ) {
foreach ( var i in rules ) original = i.Invoke( original );
return original;
}
rules.AddRange( new[] {
( x => x.Select( y => y + 2 ) ),
( x => x.Where( y => y < 50 ) ),
( x => x.Select( y => y * 3 ) ),
( x => x.Select( y => y * 10 ) ),
( x => x.Select( y => ( y > 999 ) ? 999 : y ) ),
} );

Related

convert two loops over two dimensional array to Linq syntax

I have a two dimensional array containing objects of type MyObj.
private MyObj[,] myObjs = new MyObj[maxX, maxY];
I want to get the indices from the array when passing in a matching object. I want to get the x and y value from this array. I can return these two values as a Position object that takes a x and y coordinate.
private Position GetIndices(MyObj obj)
{
for (int x = 0; x < myObjs.GetLength(0); x++)
{
for (int y = 0; y < myObjs.GetLength(1); y++)
{
if (myObjs[x, y] == obj)
{
return new Position(x, y);
}
}
}
}
Is it possible to get this code shorten to some Linq code lines?
But I don't think, it looks nice :)
var result = Enumerable.Range(0, myObjs.GetLength(0))
.Select(x => Enumerable.Range(0, myObjs.GetLength(1)).Select(y => new { x, y }))
.SelectMany(o => o)
.FirstOrDefault(o => myObjs[o.x, o.y] == obj);
Here's another option, if you're interested. It uses an indexer inside the first select and does a little math to find where that index falls inside the two-dimensional array.
var o = new MyObj();
myObjs[1,2] = o;
var p = myObjs.Cast<MyObj>()
.Select((x,i) => Tuple.Create(x,i))
.Where(x => x.Item1 == o)
.Select(x => new Point(x.Item2 / myObjs.GetLength(1), x.Item2 % myObjs.GetLength(1)))
.SingleOrDefault();
Console.WriteLine(p); // prints {X=1,Y=2}
It sorta looks like you're considering the x-coordinate to be the height of the array, and the y-coordinate the width, in which case you'd want to switch it up slightly:
var p = myObjs.Cast<MyObj>()
.Select((x,i) => Tuple.Create(x,i))
.Where(x => x.Item1 == o)
.Select(x => new Point(x.Item2 % myObjs.GetLength(1), x.Item2 / myObjs.GetLength(1)))
.SingleOrDefault();
Console.WriteLine(p); // prints {X=2,Y=1}
I used Point instead of Position since it's built into .NET but you should be able to just swap one for the other.
Yes, that is possible. You can ask Resharper to do the job (loop to linq) for you. After installation, just use the feature.

Remove duplicates in a list of XYZ points

Mylist.GroupBy(x => new{x.X, x.Y}).Select(g => g.First()).ToList<XYZ>();
The above code works fine for me. I only want to compare the points based on the round(5) of the point component.
For example x.X = 16.838974347323224 should be only compared as x.X = 16.83897 because I experienced some inaccuracy after the round 5. Any suggestions?
Solution:
Mylist.GroupBy(x => new { X = Math.Round(x.X,5), Y = Math.Round(x.Y,5) })
.Select(g => g.First()).ToList();
Using Round can create a situation where two numbers, even though incredibly close to each other, can end up being considered distinct.
Take this example:
var Mylist = new []
{
new { X = 1.0000051, Y = 1.0 },
new { X = 1.0000049, Y = 1.0 },
new { X = 1.1, Y = 1.0 },
new { X = 1.0, Y = 1.005 },
};
The first two values are very close - in fact they differ in the 6th decimal place.
By what if we run this code:
var result =
Mylist
.GroupBy(x => new
{
X = Math.Round(x.X,5, MidpointRounding.AwayFromZero),
Y = Math.Round(x.Y,5, MidpointRounding.AwayFromZero)
})
.Select(g => g.First())
.ToList();
The result is:
The rounding has allowed these two values to be kept.
The correct approach is to filter by distance. If a subsequent value is within a threshold of the previous values it should be discarded.
Here's the code that does that:
var threshold = 0.000001;
Func<double, double, double, double, double> distance
= (x0, y0, x1, y1) =>
Math.Sqrt(Math.Pow(x1 - x0, 2.0) + Math.Pow(y1 - y0, 2.0));
var result = Mylist.Skip(1).Aggregate(Mylist.Take(1).ToList(), (xys, xy) =>
{
if (xys.All(xy2 => distance(xy.X, xy.Y, xy2.X, xy2.Y) >= threshold))
{
xys.Add(xy);
}
return xys;
});
Now if we run that on the Mylist data we get this:
This is a better ideal for removing duplicates.
To do so use Math.Round:
var result = Mylist.GroupBy(x => new { X = Math.Round(x.X,5, MidpointRounding.AwayFromZero), Y = Math.Round(x.Y,5, MidpointRounding.AwayFromZero) })
.Select(g => g.First()).ToList();
However if what you want is to remove duplicates then instead of GroupBy go for one of these:
Select rounded and then Distinct:
var result = Mylist.Select(item => new XYZ { X = Math.Round(item.X,5, MidpointRounding.AwayFromZero),
Y = Math.Round(item.Y,5, MidpointRounding.AwayFromZero)})
.Distinct().ToList();
Distinct and override Equals and GetHashCode - (equals will do the rounding) - wouldn't suggest
Distinct and implement a custom IEqualityComparer:
public class RoundedXyzComparer : IEqualityComparer<XYZ>
{
public int RoundingDigits { get; set; }
public RoundedXyzComparer(int roundingDigits)
{
RoundingDigits = roundingDigits;
}
public bool Equals(XYZ x, XYZ y)
{
return Math.Round(x.X, RoundingDigits, MidpointRounding.AwayFromZero) == Math.Round(y.X, RoundingDigits, MidpointRounding.AwayFromZero) &&
Math.Round(x.Y,RoundingDigits, MidpointRounding.AwayFromZero) == Math.Round(y.Y, RoundingDigits, MidpointRounding.AwayFromZero);
}
public int GetHashCode(XYZ obj)
{
return Math.Round(obj.X, RoundingDigits, MidpointRounding.AwayFromZero).GetHashCode() ^
Math.Round(obj.Y, RoundingDigits, MidpointRounding.AwayFromZero).GetHashCode();
}
}
//Use:
myList.Distinct(new RoundedXyzComparer(5));

Classify values on ranges

I have an enumeration as follows:
public enum BPLevel {
Normal = 1,
HighNormal = 2,
HypertensionStage1 = 3,
ModerateHypertensionStage2 = 4,
SeverHypertensionStage3 = 5,
} // BloodPressureLevel
And I have the following classification:
I am using Entity Framework and I need count how many persons are in each level:
IDictionary<BPLevel, Int32> stats = context
.Persons
.Select(x => new { PersonId = x.Person.Id, BPDiastolic = x.BPDiastolic, BPSystolic = x.BPSystolic })
.Count( ...
My problem is how can I apply this classification in my query?
I would just add a classification member that is assigned to the result of a function call
IDictionary<BPLevel, Int32> stats = context
.Persons
.Select(x => new { PersonId = x.Person.Id, BPDiastolic = x.BPDiastolic,
BPSystolic = x.BPSystolic,
Classification = GetClassification(BPDiastolic, BPSystolic) })
.Count( ...
BPLevel GetClassification(int diastolic, int systolic)
{
...
}
Queries to EF sometimes don't like operations happening inside the queries, so you may need to do a ToList before the Select to get it into memory (so its LINQ to objects).
What I'd do in this case, is make a helper property in Person
public BPLevel BpLevel
{
get
{
if(Systolic >= 180)
return BPLevel.SeverHypertensionStage3
else if
...
}
}
and then I'd do a group by
.ToList() // you need to execute against the DB before you call the helper property
.GroupBy(x => x.BPLevel)
.Select(x => /*moar data transformation
x is a collection of Person
x.Key, is the BPLevel*/ )
Make sure that you do that ToList() part, or else you might get a not supported exception when it tries to convert your helper property to SQL
Another option would be to Select into a concrete class that has a BPLevel property getter that does the classification for you:
public class PersonWithBP {
// other properties
public BPLevel BPClassification {
get {
// logic to calculate BPLevel
return bpLevel;
}
}
Now your select becomes
.Select(x => new PersonWithBP() {}
Using a GroupBy statement would allow you to classify everyone into their respective BPLevel, which which point you merely need to perform a ToDictionary and count the people in each category. Thus
IDictionary<BPLevel, Int32> stats = context
.Persons
.Select(x => new { PersonId = x.Person.Id, BPDiastolic = x.BPDiastolic, BPSystolic = x.BPSystolic })
.AsEnumerable() // I'm not completely familiar with Entity Framework, so this line may be necessary to force evaluation to continue in-memory from this point forward
.GroupBy(p => ... // Test which returns a BPLevel)
.ToDictionary(g => g.Key, g => g.Count());
I would write a private static helper function in your class that does the classification for you and insert a call to that function in your projection. Something like:
private static BPLevel ClassifyBP(int diastolic, int systolic) {
// Appropriate switch statement here
}
and then your Select projection looks like:
.Select(x => new { PersonId = x.Person.Id,
BPDiastolic = x.BPDiastolic,
BPSystolic = x.BPSystolic,
BPLevel = ClassifyBP(x.BPDiastolic, x.BPSystolic) })
This is not pretty, but the count query will be executed in database.
var stats = context.Persons
.Select(x => new
{
Level = x.BPDiastolic < 85 && x.BPSystolic < 130
? BPLevel.Normal
: (x.BPDiastolic < 90 && x.BPSystolic < 140
? BPLevel.HighNormal
: (x.BPDiastolic < 100 && x.BPSystolic < 160
? BPLevel.HypertensionStage1)
: (x.BPDiastolic < 110 && x.BPSystolic < 180
? BPLevel.ModerateHypertensionStage2
: BPLevel.SeverHypertensionStage3)))
})
.GroupBy(x => x.Level)
.ToDictionary(x => x.Key, g => g.Count()) // execute in database
.Union(Enum.GetValues(typeof(BPLevel))
.OfType<BPLevel>()
.ToDictionary(x => x, x => 0)) // default empty level
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Sum(y => y.Value)); // combine both

How to group in Linq based on previous Value

I want to group a pointcloud based on 2 conditions
simple on Y so I wrote pointcloudH.GroupBy(KVP => KVP.Value.Y) where KVP is an KeyValuePair<string,System.Drawing.Point>
and now I want to group it also by X if X == (previousX + 1)
as far as I know I should us ThenBy() but what do I have to write between the brackets?
and here an example for a better illustration what I want to achieve
Sample pointcloud
(x|y) (1|1),(2|1),(4|1),(1|2),(2|3),(3|3),(4|3),(5|8),(9|10)
after step 1. it looks like this
group1 (1|1),(2|1),(4|1)
group2 (1|2)
group3 (2|3),(3|3),(4|3)
group4 (5|8)
group5 (9|10)
after step 2. it should look like this
group1 (1|1),(2|1)
group2 (4|1)
group3 (1|2)
group4 (2|3),(3|3),(4|3)
group5 (5|8)
group6 (9|10)
current code
var Hgroup = pointcloudH.OrderBy(KVP => KVP.Value.Y) // order by Y
.GroupBy(KVP => KVP.Value.Y) // groub by Y
.ThenBy(KVP => KVP.Value.X); // group by X ???
I don't think LINQ is the best tool for this kind of job, but it can be achieved. The important part is to think of the relation between your Point.X and the index of the relative Point in the Point.Y group. Once you realize you want to group them by Point.X - Index, you can do:
var Hgroup = pointcloudH.OrderBy(p => p.Y)
.GroupBy(p => p.Y)
.SelectMany(yGrp =>
yGrp.Select((p, i) => new {RelativeIndex = p.X - i, Point = p})
.GroupBy(ip => ip.RelativeIndex, ip => ip.Point)
.Select(ipGrp => ipGrp.ToList()))
.ToList();
Note that this will probably perform worst than a regular iterative algorithm. My pointcloudH is an array, but you can just change the lambda to reflect your own list. Also, remove the ToList() if you want to defer execution. This was to ease the result inspection in the debugger.
If you want to group all points in a Point.Y group regardless of their index (ie order by Point.X as well. Add ThenBy(p => p.X) after the first OrderBy clause.
Your problem cannot be solved by doing 2 separate group by clauses. I have created a little sample which should work for your problem. These are the key things that are happening in the code:
Construct 'mirror' array and insert a copy of the first item at index 0, this is used to keep track of the previous point
Create a variable that is incremented whenever a 'chain' is broken. This is whenever the next value is not equal to the previous + 1. This way we can group by an unique key per 'chain'.
class Program
{
public struct Point
{
public static Point Create(int x, int y)
{
return new Point() { X = x, Y = y };
}
public int X { get; set; }
public int Y { get; set; }
public override string ToString()
{
return string.Format("({0}|{1})", X, Y);
}
}
static void Main(string[] args)
{
//helper to avoid to much keystrokes :)
var f = new Func<int, int, Point>(Point.Create);
//compose the point array
//(1|1),(2|1),(4|1),(1|2),(2|3),(3|3),(4|3),(5|8),(9|10)
var points = new[] { f(1, 1), f(2, 1), f(4, 1), f(1, 2), f(2, 3), f(3, 3), f(4, 3), f(5, 8), f(9, 10) }.OrderBy(p => p.Y).ThenBy(p => p.X);;
//create a 'previous point' array which is a copy of the source array with a item inserted at index 0
var firstPoint = points.FirstOrDefault();
var prevPoints = new[] { f(firstPoint.X - 1, firstPoint.Y) }.Union(points);
//keep track of a counter which will be the second group by key. The counter is raised whenever the previous X was not equal
//to the current - 1
int counter = 0;
//the actual group by query
var query = from point in points.Select((x, ix) => new { current = x, prev = prevPoints.ElementAt(ix) })
group point by new { point.current.Y, prev = (point.prev.X == point.current.X - 1 ? counter : ++counter) };
//method chaining equivalent
query = points.Select((x, ix) => new { current = x, prev = prevPoints.ElementAt(ix) })
.GroupBy(point => new { point.current.Y, prev = (point.prev.X == point.current.X - 1 ? counter : ++counter) });
//print results
foreach (var item in query)
Console.WriteLine(string.Join(", ", item.Select(x=> x.current)));
Console.Read();
}
}

Filtering two arrays to avoid Inf/NaN values

I have two arrays of doubles of the same size, containg X and Y values for some plots.
I need to create some kind of protection against INF/NaN values. I need to find all that pairs of values (X, Y) for which both, X and Y are not INF nor NaN
If I have one array, I can do it using lambdas:
var filteredValues = someValues.Where(d=> !(double.IsNaN(d) || double.IsInfinity(d))).ToList();
Now, for two arrays I use the following loop:
List<double> filteredX=new List<double>();
List<double> filteredY=new List<double>();
for(int i=0;i<XValues.Count;i++)
{
if(!double.IsNan(XValues[i]) &&
!double.IsInfinity(XValues[i]) &&
!double.IsNan(YValues[i]) &&
!double.IsInfinity(YValues[i]) )
{
filteredX.Add(XValues[i]);
filteredY.Add(YValues[i]);
}
}
Is there a way of filtering two arrays at the same time using LINQ/lambdas, as it was done for the single array?
Unfortunately I can use only .NET 3.5.
Slight correction for Mark's original answer:
var filteredValues = XValues.Zip(YValues, (x,y) => new { x, y })
.Where(p => !(double.IsNan(p.x) || double.IsNan(p.y) ||
double.IsInfinity(p.x) || double.IsInfinity(p.y)))
.ToList();
Alternatively, you might want to make it slightly neater:
Func<double, bool> valid = z => !double.IsNan(z) && !double.IsInfinity(z);
var filteredValues = XValues.Zip(YValues, (x,y) => new { x, y })
.Where(p => valid(p.x) && valid(p.y))
.ToList();
If you then need the results back into two lists, you can do:
var filteredX = filteredValues.Select(p => p.x).ToList();
var filteredY = filteredValues.Select(p => p.y).ToList();
C# 4.0 introduces the Enumerable.Zip extension method to accomplish iterating over enumerables "in parallel" as you describe.
I haven't used it myself, but it should be something like:
var filteredValues =
XValues.Zip(YValues, (x,y) => new { X = x, Y = y})
.Where( o =>
!(double.IsNan(o.X) || double.IsNan(o.Y) || double.IsInfinity(o.X) || double.IsInfinity(o.Y)))
.ToList();
(Sorry for the funny indentation, wanted it to be more readable on SO)
OK, so you can't use .NET 4.0 and therefore can't use the Zip extension.
Or can you?
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (var eFirst = first.GetEnumerator())
using (var eSecond = second.GetEnumerator())
{
while (eFirst.MoveNext() && eSecond.MoveNext())
yield return resultSelector(eFirst.Current, eSecond.Current);
}
}
See for yourself :)
static void Main(string[] args)
{
var x = new double[] { 0.0, 1.0, 2.0, double.NaN, 4.0, 5.0 };
var y = new double[] { 0.5, 1.5, double.PositiveInfinity, 3.5, 4.5, 5.5 };
// note: using KeyValuePair<double, double> --
// you could just as easily use your own custom type
// (probably a simple struct)
var zipped = x.Zip(y, (a, b) => new KeyValuePair<double, double>(a, b))
.Where(kvp => IsValid(kvp.Key) && IsValid(kvp.Value))
.ToList();
foreach (var z in zipped)
Console.WriteLine("X: {0}, Y: {1}", z.Key, z.Value);
}
static bool IsValid(double value)
{
return !double.IsNaN(value) && !double.IsInfinity(value);
}
Output:
X: 0, Y: 0.5
X: 1, Y: 1.5
X: 4, Y: 4.5
X: 5, Y: 5.5
You can try to do this:
doubleArray1.Zip(doubleArray2, (x, y) => Tuple.Create(x, y))
.Where(tup => !double.IsNaN(tup.Item1) &&
!double.IsNaN(tup.Item2) &&
!double.IsInfinity(tup.Item1) &&
!double.IsInfinity(tup.Item1));
Alternately, you could make a method for filtering and zipping at the same time, the major benefit is that you're not limited to C# 4:
public static IEnumerable<Tuple<TOne, TTwo>> DualWhere<TOne, TTwo>(this IEnumerable<TOne> one, IEnumerable<TTwo> two, Func<TOne, TTwo, bool> predicate)
{
var oneEnumerator = one.GetEnumerator();
var twoEnumerator = two.GetEnumerator();
while (oneEnumerator.MoveNext() && twoEnumerator.MoveNext())
{
if (predicate(oneEnumerator.Current, twoEnumerator.Current))
yield return Tuple.Create(oneEnumerator.Current, twoEnumerator.Current);
}
oneEnumerator.Dispose();
twoEnumerator.Dispose();
}
Edit: The latter should work with C# 3.5.
Here's a solution that will work in C# 3 and .NET 3.5
List<double> list1 = new List<double>() { 1.2, 3.8, double.NaN, 17.8 };
List<double> list2 = new List<double>() { 9.4, double.PositiveInfinity, 10.4, 26.2 };
var query = from x in list1.Select((item, idx) => new { item, idx })
where !double.IsNaN(x.item) && !double.IsInfinity(x.item)
join y in list2.Select((item, idx) => new { item, idx })
on x.idx equals y.idx
where !double.IsNaN(y.item) && !double.IsInfinity(y.item)
select new { X = x.item, Y = y.item };
Iterating over the query would produce pairs containing 1.2 & 9.4 and 17.8 & 26.2. The middle two pairs would be discarded because one contains NaN and the other contains infinity.
foreach (var pair in query)
{
Console.WriteLine("{0}\t{1}", pair.X, pair.Y);
}

Categories