linq order by based on multiple columns and various crietria - c#

I have data arranged in this order in sql.
Now, I want to order this list with both QuestionDataTypeId and DisplayOrder, but want this QuestionDataTypeId = 0 at last. so finally result will be like first row = 6 then 7 then 8 and then 1 to 5.
I want to achieve this with C# and linq.
What I have tried so far?
here is my code but it's not working.
var data = entity
.OrderByDescending(m => m.QuestionDataTypeId == 0)
.ThenBy(m => m.QuestionDataTypeId)
.ThenBy(m => m.DisplayOrder);
I have fixed this with merging 2 different variables sorted separately for QuestionDataTypeId = 0 and all other QuestionDataTypeId, but just want to know what will be the proper linq for this case in single line.
any help would be really appreciated. thanks!

Try replace QuestionDataTypeId where value = 0
.OrderBy(x=>x.QuestionDataTypeId==0?int.MaxValue:x.QuestionDataTypeId)
.ThenBy(t=>t.DisplayOrder)

You can write your own comparer for OrderBy
Sample data structure:
public record Table
{
public Table(int qdtId, int displayOrder, string text)
{
QuestionDataTypeId = qdtId;
DisplayOrder = displayOrder;
Text = text;
}
public int QuestionDataTypeId { get; set; }
public int DisplayOrder { get; set; }
public string Text { get; set; }
}
public class TableComparer : IComparer<Table>
{
public int Compare(Table? x, Table? y)
{
if(x.QuestionDataTypeId!= 0 && y.QuestionDataTypeId!=0 || x.QuestionDataTypeId == 0 && y.QuestionDataTypeId == 0)
{
return y.QuestionDataTypeId.CompareTo(x.QuestionDataTypeId);
}
return x.QuestionDataTypeId == 0 && y.QuestionDataTypeId != 0 ? int.MinValue : int.MaxValue;
}
}
Then in the code:
List<StringConcatBug.Table> list = new()
{
new(0, 1, "Comfortable"),
new(0, 2,"attainable"),
new(0, 3,"recent goal"),
new(0, 4,"comfortable"),
new(2, 2,"Last Name"),
new(3, 3,"Email"),
new(0, 5, "feeling"),
new(1, 1, "First Name"),
};
var ordered = list.OrderByDescending(t=>t,new TableComparer());
foreach(var v in ordered) { Console.WriteLine(v);}
Output
Table { QuestionDataTypeId = 1, DisplayOrder = 1, Text = First Name }
Table { QuestionDataTypeId = 2, DisplayOrder = 2, Text = Last Name }
Table { QuestionDataTypeId = 3, DisplayOrder = 3, Text = Email }
Table { QuestionDataTypeId = 0, DisplayOrder = 1, Text = Comfortable }
Table { QuestionDataTypeId = 0, DisplayOrder = 2, Text = attainable }
Table { QuestionDataTypeId = 0, DisplayOrder = 3, Text = recent goal }
Table { QuestionDataTypeId = 0, DisplayOrder = 4, Text = comfortable }
Table { QuestionDataTypeId = 0, DisplayOrder = 5, Text = feeling }

I usually use this algorithm
var mult=100000; // You can select a different number depending how many records of
// the same type you expecting; Numbers should not overlap
var data = entity
.OrderBy(m => (m.QuestionDataTypeId*mult + m.DisplayOrder))
.....

Related

How to programmatically determine tab index of fields on a form based on their X,Y coordinates?

I need to figure out the proper tabbing order of HTML form fields based on their absolute X, Y coordinates on the page. We use X, Y from the bottom-left corner of the div (page container) in which they are placed.
For example, in the image below, the numbers inside the boxes indicate the final tabIndex order I would expect as the result should the boxes overlap at all on the Y axis; the lowest X axis value would win and the Y axis wouldn't matter at all. If there's no overlap, then the highest Y axis value wins.
Context
Basically when filling out a PDF form, the natural tab index should be left to right even if box #3 is a little higher than box #2; you'd still want to fill it out left to right. However, since box #1 is on a completely different X plane than the other boxes (regardless if it's further right than the rest) it should still logically come before the other boxes when filling out a form. You wouldn't go across and then up.
The fields are in a C# object with X and Y properties. (pseudocode below)
var fields = new List<TestFieldModel>()
{
new TestFieldModel()
{
ExpectedOrderNumberResult = 3,
PageNumber = 1,
X = 7,
Y = 6,
Width = 5,
Height = 2
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 4,
PageNumber = 1,
X = 14,
Y = 4,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 1,
PageNumber = 1,
X = 17,
Y = 9,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 2,
PageNumber = 1,
X = 2,
Y = 5,
Width = 4,
Height = 2
}
};
My answer is very similar to yours. The main differences are
not using a boolean to break out of two loops.
In the "double break" scenario I prefer the maligned goto. It's subjective, but for me, is clearer because it avoids a check in the outer loop.
replacing the if statements on recalculating group bounds with Math.Min/Max. This shorthand expresses your intent more clearly.
calling Group.Add() for every field; there is no need to initialize the list of fields in a group differently if you have an empty list to being with
Psuedo C#:
foreach (var f in fields)
{
foreach (var g in groups)
{
if (g.VerticallyOverlapsWith(f))
{
g.Add(f);
goto NEXT_FIELD;
}
}
// no overlap detected, so make a new group
var newGroup = new Group();
newGroup.Add(f);
groups.Add(newGroup)
NEXT_FIELD :;
}
class Group
{
void AddField(Field f)
{
_group.Add(f);
_yTop = Max(f.Top, _yTop);
_yBottom = Min(f.Bottom, _yBottom);
}
List<Field> _group = new List<Field>();
int _yTop = int.MinValue();
int _yBottom = int.MaxValue();
}
At this point you have your groups. You now have to sort groups descending then by fields ascending (which you have done).
A couple of design points.
this does not address fields that overlap with fields in multiple groups, causing unnecessarily large groups. If you get into that, your tabbing order could get a bit unexpected (from the user's point of view). If you expect these
weird overlaps you'd be better off using a clustering algorithm or a "tolerance" function rather than a simple "overlap with first overlapping group" function (VerticallyOverlapsWith does the latter).
you can avoid the sort steps at the end by making the adds insert into an ordered collection in each case.
Ok this is how I was able to solve this:
[Fact]
public void GetFieldsOrderTest()
{
var fields = new List<TestFieldModel>()
{
new TestFieldModel()
{
ExpectedOrderNumberResult = 3,
PageNumber = 1,
X = 7,
Y = 6,
Width = 5,
Height = 2
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 4,
PageNumber = 1,
X = 14,
Y = 4,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 1,
PageNumber = 1,
X = 17,
Y = 9,
Width = 5,
Height = 3
},
new TestFieldModel()
{
ExpectedOrderNumberResult = 2,
PageNumber = 1,
X = 2,
Y = 5,
Width = 4,
Height = 2
}
};
var pageCount = 1;
for (var i = 1; i <= pageCount; i++)
{
var groups = new List<Group>();
foreach (var field in fields.Where(f => f.PageNumber == i))
{
var needsNewGroup = true;
foreach (var group in groups)
{
var fieldTop = field.Y + field.Height;
var fieldBottom = field.Y;
if ((fieldTop <= group.Top && fieldTop >= group.Bottom) || (fieldBottom >= group.Bottom && fieldBottom <= group.Top))
{
if (fieldTop > group.Top)
{
group.Top = fieldTop;
}
if (fieldBottom < group.Bottom)
{
group.Bottom = fieldBottom;
}
group.GroupFields.Add(field);
needsNewGroup = false;
break;
}
}
if (needsNewGroup)
{
var group = new Group
{
Top = field.Y + field.Height,
Bottom = field.Y,
GroupFields = new List<TestFieldModel>
{
field
}
};
groups.Add(group);
}
}
var groupFields = groups.OrderByDescending(g => g.Top).Select(g => g.GroupFields.OrderBy(gf => gf.X).ToList()).ToList();
fields = groupFields.Aggregate((acc, list) => acc.Concat(list).ToList());
fields.Should().HaveCount(4, "Count is not 4: " + fields.Count);
fields[0].ExpectedOrderNumberResult.Should().Be(1, $"Expected 1. Got {fields[0].ExpectedOrderNumberResult}.");
fields[1].ExpectedOrderNumberResult.Should().Be(2, $"Expected 2. Got {fields[1].ExpectedOrderNumberResult}.");
fields[2].ExpectedOrderNumberResult.Should().Be(3, $"Expected 3. Got {fields[2].ExpectedOrderNumberResult}.");
fields[3].ExpectedOrderNumberResult.Should().Be(4, $"Expected 4. Got {fields[3].ExpectedOrderNumberResult}.");
}
}
public class Group
{
public List<TestFieldModel> GroupFields { get; set; }
public int Top { get; set; }
public int Bottom { get; set; }
}
public class TestFieldModel
{
public int ExpectedOrderNumberResult { get; set; }
public int PageNumber { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int X { get; set; }
public int Y { get; set; }
}

how to sort a list then sort a subset of that list

I have a collection of lines. Each line is a collection of fields. It is very easy to find column 1 and sort by its content:
_lines = lines.OrderBy(l => l.Fields.Find(f => f.ColumnNumber == 1).Content).ToList();
But how do I then sort a subset of this list? I want to sort by f.ColumnNumber == 3 where (f.ColumnNumber == 1).Content is 6.
So my line collection looks like this:
Col1, Col2, Col3
1, data, data
5, data, data
6, data, Chuck
6, data, Chuck
6, data, Jeffster
6, data, Jeffster
6, data, Grunke
6, data, Gertrude
8, data, data
9, data, data
I want to sort by col1, then sort only col1's where col1 == 6 but sort by col3.
Is there a way to do this in c#? I can't seem to find the magic formula.
EDIT:
My sample data is a little over simplified. Some lines have 3 columns, other lines have more or less columns. So when I use the .ThenBy extention, if I am trying to subsort lines on col 3 but say line 9 only has one column, I get an object reference not set to an instance of an object exception.
Here is a better example of the sample data:
Col1, Col2, Col3, Col4
1, data, data, data
5, data, data
6, data, Chuck
6, data, Chuck
6, data, Jeffster
6, data, Jeffster
6, data, Grunke
6, data, Gertrude
8, data, data
9, data
Code 1 lines have 4 columns.
Code 5 lines have 3.
Code 6 lines have 3 - and I need to sort by col 3 alphabetically.
Code 8 lines have 3 columns.
Code 9 lines have 2.
There is no guarantee the list is sorted at all. So I need first of all to have the lines sorted by that first column 1 - 9, then I need only code 6's to be sorted by col 3.
EDIT 2:
My class structure is somewhat complicated so I was trying to keep it simple as possible hoping it would be sufficient, but it looks like that his not the case so let me share with you the class definition:
public class Field : IField
{
public FieldSpecification Specification { get; set; }
public string Content { get; set; }
}
public class FieldSpecification
{
public int ColumnNumber { get; set; }
public int Length { get; set; }
public int StartPosition { get; set; }
public int EndPosition { get { return StartPosition + Length - 1; } }
public Justification Justification { get; set; }
public char PadCharacter { get; set; }
}
Then I have a bunch of lines that conform to the ILine interface
public interface ILine
{
List<IField> Fields { get; set; }
int Credits { get; }
int Debits { get; }
BigInteger Hash { get; }
}
So technically above I show something like field.ColumnNumber, but it should be field.Specification.ColumnNumber.
The objective is to build fixed width files according to specifications that can change. So each line has a collection of fields with specifications, and then data can go into the content, and the specification can help do validation: formatting validation.
I was hoping there would be a way to sort subsets of lists using linq, but I may need to deconstruct my final collection of lines, sort it, then reconstruct the collection. I was hoping to avoid that.
You could use the ThenBy extension.
Looking at your desired output, it seems something as simple as,
var output = lines.Select(l => new
{
Col1 = int.Parse(l.Fields.Find(f => f.ColumnNumber == 1).Content),
Col2 = l.Fields.Find(f => f.ColumnNumber == 2).Content,
Col3 = l.Fields.Find(f => f.ColumnNumber == 3).Content
}).OrderBy(l => l.Col1).ThenBy(l => l.Col3);
would suffice.
If, for some reason, you only want to order the sub list when Col1 is 6,
var output = lines.Select(l => new
{
Col1 = int.Parse(l.Fields.Find(f => f.ColumnNumber == 1).Content),
Col2 = l.Fields.Find(f => f.ColumnNumber == 2).Content,
Col3 = l.Fields.Find(f => f.ColumnNumber == 3).Content
}).OrderBy(l => l.Col1).ThenBy(l => l.Col1 == 6 ? l.Col3 : null);
One last caveat, depending on the type of Fields there is probably a better approach to all of this.
It can be done via the ThenBy extension method; in general, the concept is known as lexicographical order.
If I can assume that you class definition is this:
public class Datum
{
public int ID { get; set; }
public string[] Cols { get; set; }
}
Then I can define the data like this:
var data = new []
{
new Datum() { ID = 1, Cols = new [] { "data", "data", "data", }, },
new Datum() { ID = 5, Cols = new [] { "data", "data", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Chuck", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Chuck", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Jeffster", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Jeffster", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Grunke", }, },
new Datum() { ID = 6, Cols = new [] { "data", "Gertrude", }, },
new Datum() { ID = 8, Cols = new [] { "data", "data", }, },
new Datum() { ID = 9, Cols = new [] { "data", }, },
};
And then I can sort like this:
var sorted =
from d in data
let key =
String.Format("{0:0000}", d.ID)
+ (d.ID != 6 ? "" : "-" + d.Cols[1])
orderby key
select d;
I get these results:

linq Contains but less

I have a list to search a table,
List<long> searchListIds = new List<long>();
searchListIds.Add(1);
searchListIds.Add(2);
List<long> searchListFieldValues = new List<long>();
searchListFieldValues.Add(100);
searchListFieldValues.Add(50);
and my query is:
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
group adField by adField.adId into adAdFields
where searchListIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
&& searchListFieldValues.All(i => adAdFields.Select(co => co.listFieldValue).Contains(i))
select adAdFields.Key;
everything is ok, but now: i need to get all records that meet less than searchListFieldValues. i mean:
all adId that have (listId == 1)&(listFieldValue <100) AND (listId == 2)&(listFieldValue <50)
contains part must change to something like contains-less
example:
cwContext.tblAdFields:
id 1 2 3 4 5 6 7
adId 1 2 1 2 3 3 3
listId 1 1 2 2 1 2 3
listfieldValue 100 100 50 50 100 49 10
Now if I want to get (listId == 1)&(listFieldValue ==100) AND (listId == 2)&(listFieldValue ==50) my code works, and return id adId: 1,2
but I can't get
all adId that have (listId == 1)&(listFieldValue ==100) AND (listId == 2)&(listFieldValue <50)
it must return 3
You should try changing Contains to Any, but I'm not sure if LINQ to Entities will translate it correctly into proper SQL statement.
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
group adField by adField.adId into adAdFields
where searchListIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
&& searchListFieldValues.All(i => adAdFields.Select(co => co.listFieldValue).Any(x => x < i))
select adAdFields.Key;
Here is a full example that should work if I understood you correctly:
class Program
{
static void Main(string[] args)
{
List<int> searchListIds = new List<int>
{
1,
2,
};
List<int> searchListFieldValues = new List<int>
{
100,
50,
};
List<Tuple<int, int>> searchParameters = new List<Tuple<int,int>>();
for (int i = 0; i < searchListIds.Count; i++)
{
searchParameters.Add(new Tuple<int,int>(searchListIds[i], searchListFieldValues[i]));
}
List<AdField> adFields = new List<AdField>
{
new AdField(1, 1, 1, 100),
new AdField(2, 2, 1, 100),
new AdField(3, 1, 2, 50),
new AdField(4, 2, 2, 50),
new AdField(5, 3, 1, 100),
new AdField(6, 3, 2, 49),
new AdField(7, 3, 3, 10)
};
var result = adFields.Where(af => searchParameters.Any(sp => af.ListId == sp.Item1 && af.ListFieldValue < sp.Item2)).Select(af => af.AdId).Distinct();
foreach (var item in result)
{
Console.WriteLine(item);
}
Console.Read();
}
public class AdField
{
public int Id { get; private set; }
public int AdId { get; private set; }
public int ListId { get; private set; }
public int ListFieldValue { get; private set; }
public AdField(int id, int adId, int listId, int listFieldValue)
{
Id = id;
AdId = adId;
ListId = listId;
ListFieldValue = listFieldValue;
}
}
}
First, you're probably looking for functionality of Any() instead of Contains(). Another thing is that if your search criteria consists of two items - use one list of Tuple<int,int> instead of two lists. In this case you will e able to efficiently search by combination of listId and fieldValue:
var result = from adField in cwContext.tblAdFields
where searchParams.Any(sp => adField.listId == sp.Item1 && adField.listFieldValue < sp.Item2)
group adField by adField.adId into adAdFields
select adAdField.Key;

using linq query to find value that differs from previously found value

Say i have a class that contains these items publicly accessible via properties:
class MyClass
{
int switch1; //0 or 1
int switch2; //0 or 1
int switch3; //0 or 1
}
This class represents switch states, and each time a switch state changes, i would like to add it to my transition list
I have a large sorted list that contains instances of this class and would like to use a query to capture only the entries in my list where the switch state for any switch changes.
Is this possible using a linq query?
try this:
Assuming your class looks like:
public class State
{
public int Id { get; set; }
public int Switch1 { get; set; }
public int Switch2 { get; set; }
public int Switch3 { get; set; }
public override bool Equals(object obj)
{
var other = obj as State;
if (other != null)
{
return Switch1 == other.Switch1 &&
Switch2 == other.Switch2 &&
Switch3 == other.Switch3;
}
return false;
}
}
I just added an Equals() to compare flags and my Id field is purely to demonstrate which items changed.
We can then craft a LINQ query like:
State previous = null;
var transitions = list.Where(s =>
{
bool result = !s.Equals(previous);
previous = s;
return result;
})
.ToList();
Not elegant, but it works, if you had this data set:
var list = new List<State>
{
new State { Id = 0, Switch1 = 0, Switch2 = 0, Switch3 = 0 },
new State { Id = 1, Switch1 = 0, Switch2 = 0, Switch3 = 0 },
new State { Id = 2, Switch1 = 1, Switch2 = 0, Switch3 = 0 },
new State { Id = 3, Switch1 = 0, Switch2 = 1, Switch3 = 0 },
new State { Id = 4, Switch1 = 0, Switch2 = 1, Switch3 = 0 },
new State { Id = 5, Switch1 = 0, Switch2 = 1, Switch3 = 0 },
new State { Id = 6, Switch1 = 1, Switch2 = 1, Switch3 = 0 },
new State { Id = 7, Switch1 = 0, Switch2 = 0, Switch3 = 1 },
new State { Id = 8, Switch1 = 0, Switch2 = 0, Switch3 = 1 },
new State { Id = 9, Switch1 = 0, Switch2 = 0, Switch3 = 0 },
};
And ran the query, the list would contain your state transitions at items: 0, 2, 3, 6, 7, 9
I would do as follow:
class MyClass
{
int ID; //needs for recognize the message
int switch1; //0 or 1
int switch2; //0 or 1
int switch3; //0 or 1
public int Pattern
{
get { return switch1 + switch2 << 1 + switch3 << 2; }
}
}
Then it must be declared a dictionary with the previous-state messages:
Dictionary<int, int> _prevStates;
each cell has for key the ID, and for value the "Pattern" of the message.
At this point, let's suppose that the new incoming message stream is a list of MyClass:
IEnumerable<MyClass> incoming = ...
var changed = from msg in incoming
where _prevStates.ContainsKey(msg.ID) //what to do?
where _prevStates[msg.ID].Pattern != msg.Pattern
select msg;
Finally, you must update the dictionary with the changed patterns.
Cheers

Using LINQ to count value frequency

I have a table
ID|VALUE
VALUE is an integer field with possible values between 0 and 4. How can I query the count of each value?
Ideally the result should be an array with 6 elements, one for the count of each value and the last one is the total number of rows.
This simple program does just that:
class Record
{
public int Id { get; set; }
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Record> records = new List<Record>()
{
new Record() { Id = 1, Value = 0},
new Record() { Id = 2, Value = 1 },
new Record() { Id = 3, Value = 2 },
new Record() { Id = 4, Value = 3 },
new Record() { Id = 5, Value = 4 },
new Record() { Id = 6, Value = 2 },
new Record() { Id = 7, Value = 3 },
new Record() { Id = 8, Value = 1 },
new Record() { Id = 9, Value = 0 },
new Record() { Id = 10, Value = 4 }
};
var query = from r in records
group r by r.Value into g
select new {Count = g.Count(), Value = g.Key};
foreach (var v in query)
{
Console.WriteLine("Value = {0}, Count = {1}", v.Value, v.Count);
}
}
}
Output:
Value = 0, Count = 2
Value = 1, Count = 2
Value = 2, Count = 2
Value = 3, Count = 2
Value = 4, Count = 2
Slightly modified version to return an Array with only the count of values:
int[] valuesCounted = (from r in records
group r by r.Value
into g
select g.Count()).ToArray();
Adding the rows count in the end:
valuesCounted = valuesCounted.Concat(new[] { records.Count()}).ToArray();
Here is how you would get the number of rows for each value of VALUE, in order:
var counts =
from row in db.Table
group row by row.VALUE into rowsByValue
orderby rowsByValue.Key
select rowsByValue.Count();
To get the total number of rows in the table, you can add all of the counts together. You don't want the original sequence to be iterated twice, though; that would cause the query to be executed twice. Instead, you should make an intermediate list first:
var countsList = counts.ToList();
var countsWithTotal = countsList.Concat(new[] { countsList.Sum() });

Categories