Sorting List<List<string>> - c#

I have a List of List which can be of variable but repeated width. For example:
var test = new List<List<string>>();
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","4","12"});
test.Add(new List<string> {"1","2","9"});
test.Add(new List<string> {"1","4","5"});
test.Add(new List<string> {"6","7","8"});
But it could also be:
var test = new List<List<string>>();
test.Add(new List<string> {"1","2","3","3","3"});
test.Add(new List<string> {"1","4","12","1","7"});
test.Add(new List<string> {"1","2","9","9","4"});
test.Add(new List<string> {"1","4","5","8","5"});
test.Add(new List<string> {"6","7","8","2","7"});
It will never be:
var test = new List<List<string>>();
test.Add(new List<string> {"1"});
test.Add(new List<string> {"1","5"});
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","5"});
test.Add(new List<string> {"6","7","8"});
And I would like to have the list ordered left column to right column like:
["1","2","3"];
["1","2","9"];
["1","4","5"];
["1","4","12"];
["6","7","8"];
The following is a little test I setup to see what I could come up with (https://dotnetfiddle.net/B5ljig):
var test = new List<List<string>>();
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","4","5"});
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","4","5"});
test.Add(new List<string> {"6","7","8"});
var query = test.AsQueryable();
query = query.OrderBy(a=>a[0]);
var max = categories.Select(a=>a.Count()).Max();
for (int i = 1; i < max; i++)
{
query = query.ThenBy(a=>a[i]); // Error Here
}
var sorted = query.ToList();
Unfortunately the commented line errors with
'IQueryable>' does not contain a definition for 'ThenBy' and no accessible extension method 'ThenBy' accepting a first argument of type 'IQueryable>' could be found (are you missing a using directive or an assembly reference?)
Any ideas? Thoughts? Better ways.

If you want to Sort anything using your own rules, you can implement a custom comparer (IComparer<T>), IComparer<IList<string>> in this particular case:
public class MyListComparer : IComparer<IList<string>> {
private static int CompareItems(string left, string right) {
if (left.StartsWith("-"))
if (right.StartsWith("-"))
return -CompareItems(left.TrimStart('-'), right.TrimStart('-'));
else
return -1;
else if (right.StartsWith("-"))
return 1;
left = left.TrimStart('0');
right = right.TrimStart('0');
int result = left.Length.CompareTo(right.Length);
if (result != 0)
return result;
for (int i = 0; i < left.Length; ++i) {
result = left[i] - right[i];
if (result != 0)
return result;
}
return 0;
}
public int Compare(IList<string> x, IList<string> y) {
if (ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
for (int i = 0; i < Math.Min(x.Count, y.Count); ++i) {
int result = CompareItems(x[i], y[i]);
if (result != 0)
return result;
}
return x.Count.CompareTo(y.Count);
}
}
Then sort:
var test = new List<List<string>>();
test.Add(new List<string> { "1", "2", "3" });
test.Add(new List<string> { "1", "4", "12" });
test.Add(new List<string> { "1", "2", "9" });
test.Add(new List<string> { "1", "4", "5" });
test.Add(new List<string> { "6", "7", "8" });
// Time to sort with a custom comparer
test.Sort(new MyListComparer());
string report = string.Join(Environment.NewLine, test
.Select(line => string.Join(", ", line)));
Console.Write(report);
Outcome:
1, 2, 3
1, 2, 9
1, 4, 5
1, 4, 12
6, 7, 8
you can use the comparer with Linq query as well:
var sorted = test.OrderBy(new MyListComparer());

The problem is
1) the overuse of IQueryable, you don't need it,
2) the fact that i is actually captured, and when the query is executed, you have all "then by" that use the same i == 3, the last value after the end of the for loop! (Hence, an out of bounds exception at runtime)
Here is a working version (dotnetFiddle):
var query = test.OrderBy(a=>a[0]);
//var max = test.Select(a=>a.Count()).Max(); // If you say all lists have the same length, use `First(a => a.Count())` instead! And if they don't, then this will lead to an exception.
for (int i = 1; i < max; i++)
{
var j = i; // Intermediary variable so that 'global' i is not captured.
query = query.ThenBy(a=>a[j]);
};
var sorted = query.ToList();
On additional note, there are other solutions that use different approaches, already given, I think they feel more "idiomatic" for C# with the IComparer

There are two issues with your code. One is a syntax issue and one is a logic issue.
To remove the compilation error you are seeing, the query variable must be an IOrderedQueryable instead of the IQueryable that you have listed. If you combine the query variable definition and initial ordering in to one line like below, your issue should resolve.
var query = test.AsQueryable().OrderBy(a => a[0]);
You could also use the IOrderedEnumerable instead using
var query = test.OrderBy(a => a[0]);
The logic issue is that your code will not produce the result you are expecting. You are ordering the list of the list of strings by its first value before ordering each list of strings. In other words, your initial Orderby needs to be below your for loop. For simplicity I'm simplifying to this Linq expression:
var sorted = test
.Select(x => x.OrderBy(y => y).ToList())
.OrderBy(x => x[0])
.ToList();

Related

How to concat multiple list of object in single column c#

I'm facing an issue while displaying multiple lists the value in a single row column.
Here is an example of code.
public class Program
{
static void Main(string[] args)
{
Dictionary<string, List<object>> keyvalues = new Dictionary<string, List<object>>();
keyvalues.Add("Code", new List<object>() { 1, 2, 3, 4 });
keyvalues.Add("Name", new List<object>() { "A", "B", "C", "D" });
keyvalues.Add("Age", new List<object>() { 20, 30, 40, 50 });
var listData = keyvalues.Select(x => x.Value).Select((x, i) => new { obj = x, index = i });
var listData = keyvalues.Select((x, iparent) => x.Value.Select((z, i) => new { value = string.Concat(z, x.Value[i]) }).ToList()).ToList();
Console.ReadLine();
}
}
Expected output
1A20
2B30
3C40
4D50
If you are using .Net 6, you could make use of the new 3 way Zip extension.
var result = keyvalues["Code"].Zip(keyvalues["Name"], keyvalues["Age"])
.Select(x=> $"{x.First}{x.Second}{x.Third}");
Why make it so complicated?
for(int x = 0; x<keyValues["Code"].Count; x++)
Console.WriteLine(
keyValues["Code"][x]+
keyValues["Name"][x]+
keyValues["Age"][x]
);
LINQ's a hammer; not every problem is a nail.
ps if you have N keys, you can easily turn it into a
var keys = new[]{"Code","Name","Age","Foo","Bar"};
for(...)
foreach(var k in keys)
... //some concat here or use the values directly eg adding to your page
You could easily use Zip here. However, you could roll your own
public static IEnumerable<string> DoStuff<T, T2>(Dictionary<T, List<T2>> source)
{
var max = source.Values.Max(x => x?.Count ?? 0);
for (var i = 0; i < max; i++)
yield return string.Concat(source.Values.Select(x => x.ElementAtOrDefault(i)));
}
Usage
var results = DoStuff(keyvalues);
Console.WriteLine(string.Join(Environment.NewLine,results));
Output
1A20
2B30
3C40
4D50
or
public static IEnumerable<string> DoStuff<T>(List<T>[] source)
{
var max = source.Max(x => x?.Count ?? 0);
for (var i = 0; i < max; i++)
yield return string.Concat(source.Select(x => x.ElementAtOrDefault(i)));
}
...
var results = DoStuff(keyvalues.Values.ToArray());
Console.WriteLine(string.Join(Environment.NewLine,results));

find item in list , and get other item from other list at same location using linq

I have a class where it has a collection of list. I want to search a parameter inside one of the list. So the location that I found the list, I want to get at the same location at other list in the same class...
How to achieve this?
void Main()
{
var myListCollectionObj = new myListCollection();
Console.WriteLine(myListCollectionObj.firstList);
Console.WriteLine(myListCollectionObj.secondList);
Console.WriteLine(myListCollectionObj.thirdList);
var testFirstList = myListCollectionObj.firstList.Where(x => x == 3); //then i want to get "33", and 333 from secondList and thirdList respectively
Console.WriteLine(testFirstList);
}
class myListCollection
{
public List<int> firstList = new List<int>(){ 1, 2, 3, 4, 5};
public List<string> secondList = new List<string>(){ "11", "22", "33", "44", "55"};
public List<int> thirdList = new List<int>(){ 111, 222, 333, 444, 555};
}
int index = myListCollectionObj.firstList.IndexOf(3);
string elem2;
int elem3;
if (index >= 0 && index < myListCollectionObj.secondList.Length)
elem2 = myListCollectionObj.secondList[index]
if (index >= 0 && index < myListCollectionObj.thirdList.Length)
elem3 = myListCollectionObj.thirdList[index]
You don't need LINQ for that, only List<T>'s own IndexOf() method and indexer property:
int index = myListCollectionObj.firstList.IndexOf(3);
string secondValue = myListCollectionObj.secondList[index];
int thirdValue = myListCollectionObj.thirdList[index];
You may want to add error handling: if 3 is not contained in firstList, an index of -1 is returned by IndexOf().
I guess the best way if there are more than one 3 values would be using simple for loop:
var testFirstList = new List<int>();
var testSecondList = new List<string>();
var testThirdList = new List<int>();
for (var i = 0; i < myListCollectionObj.firstList.Length; ++i) {
if (myListCollectionObj.firstList[i] == 3) {
testFirstList.Add(myListCollectionObj.firstList[i]);
testSecondList.Add(myListCollectionObj.secondList[i]);
testThirdList.Add(myListCollectionObj.thirdList[i]);
}
}
A good guideline is that if you find yourself combining indices and LINQ, you probably have other options available. In this case, a good alternative would be using Zip
This approach lets you combine the 3 collections and act upon the resulting zipped collection as a single entity such that indices are no longer directly required.
var result = firstList.Zip(
secondList.Zip(thirdList,
(b, c) => new { b, c }),
(a, b) => new { Value1 = a, Value2 = b.b, Value3 = b.c })
.Where(x => x.Value1 == 3).ToList();
result.ForEach(v => Console.WriteLine(v));
correct me if i am wrong are you looking for the index of the item you searched in first list and then use the same index to retrieve from other list
If yes
Try this
var testFirstList = myListCollectionObj.firstList.Where(x => x == 3).FirstOrDefault(); //then i want to get "33", and 333 from secondList and thirdList respectively
var index = myListCollectionObj.firstList.IndexOf(testFirstList);

Alter a list of strings based on the items

I have a problem with a list that I want to alter, before outputting it back to the client.
For the sake of the question I will post an example of the list and how I need to result to look, because I have looked at Intersect, Except and everything else I could think of, but didn't get the result I am looking for.
Example List:
1, 4, 6, 8
1, 2, 6, 8
2, 4, 6, 8
3, 4, 5, 7
Required Result:
1, 4, 6, 8 //Initial row
-, 2, -, - //Items that have not changed will show as a -
2, 4, -, -
3, -, 5, 7
I really hope I explained it well.
I would be happy to explain this further if needed.
Thanks in advance for the advice, so far I have wrecked my brain over this. ;)
What I tried is too much to type here, so here is what I have so far. Except simply won't do anything with the data because it thinks the rows are different, so they just stay the same.
private List<List<string>> FilterData(List<string[]> datatable)
{
List<string> previousRow = new List<string>();
List<string> currentRow = new List<string>();
List<string> rowDifferences = new List<string>();
List<List<string>> resultingDataset = new List<List<string>>();
foreach (var item in datatable)
{
if (previousRow == null)
{
previousRow = item.ToList();
continue;
}
currentRow = item.ToList();
rowDifferences = currentRow.Except(previousRow).ToList();
resultingDataset.Add(rowDifferences);
}
return resultingDataset;
}
Few things you have to change in your code;
Here is code:
private List<string[]> FilterData(List<string[]> datatable)
{
// List is made of String Array, so need string[] variable not list
string[] previousRow = null ;
string[] currentRow;
string[] rowDifferences ;
// to store the result
List<string[]> resultingDataset = new List<string[]>();
foreach (var item in datatable)
{
if (previousRow == null)
{
previousRow = item;
resultingDataset.Add(previousRow); // add first item to list
continue;
}
currentRow = item;
// check and replace with "-" if elment exist in previous
rowDifferences = currentRow.Select((x, i) => currentRow[i] == previousRow[i] ? "-" : currentRow[i]).ToArray();
resultingDataset.Add(rowDifferences);
// make current as previos
previousRow = item;
}
return resultingDataset;
}
check this dotnetfiddle
private static List<List<string>> FilterData(List<List<string>> datatable)
{
var result = new List<List<string>>();
for(var rowindex = 0; rowindex < datatable.Count; rowindex++)
{
// Clone the string list
var refrow = datatable[rowindex]
.Select(item => (string)item.Clone()).ToList();
result.Add(refrow);
// First row will not get modify anyway
if (rowindex == 0) continue;
var row = result[rowindex];
// previous row of result has changed to "-", so use the original row to compare
var prevrow = datatable[rowindex - 1];
for(var columnindex = 0; columnindex < row.Count; columnindex++)
{
if (row[columnindex] == prevrow[columnindex])
row[columnindex] = "-";
}
}
return result;
}
fiddle
public static List<List<T>> RemoveDuplicates<T>(this List<List<T>> items, T replacedValue) where T: class
{
List<List<T>> ret = items;
items.ForEach(m=> {
var ind = items.IndexOf(m);
if(ind==0)
{
ret.Add(items.FirstOrDefault());
}
else
{
var prevItem = items.Skip(items.IndexOf(m)-1).FirstOrDefault();
var item = new List<T>();
for(var a = 0; a < prevItem.Count; a++)
{
item.Add(prevItem[a] == m[a]? replacedValue : m[a]);
}
ret.Add(item);
}
});
return ret;
}
How to use it:
var items = new List<List<string>>{
new List<string>{ "1", "4", "6", "8" },
new List<string>{ "1", "2", "6", "8" },
new List<string>{ "2", "4", "6", "8" },
new List<string>{ "3", "4", "5", "7" }
};
var result = items.RemoveDuplicates("-");
dotNetFiddle: https://dotnetfiddle.net/n36p64

remove duplicate based on position

I have two lists like below in C#.
List 1 = [{Item="A",position =1},{Item="B",position =2},{Item="A",position =3}]
List 2 = [{Item="AA",position =1},{Item="BB",position =2},{Item="AC",position =3}]
Now i want to remove duplicate values in the List 1 and that position should be removed in the List 2.
Example o/p
List 1 = [{Item="A",position =1},{Item="B",position =2}]
List 2 = [{Item="AA",position =1},{Item="BB",position =2}]
Can any one help me. Thanks.
List<string> lst1 = new List<string> { "A", "B", "A" };
List<string> lst2 = new List<string> { "AA", "BB", "AC" };
HashSet<string> seen = new HashSet<string>();
for (int i = 0; i < lst1.Count; i++) {
if (!seen.Add(lst1[i])) {
lst1.RemoveAt(i);
lst2.RemoveAt(i);
i--;
}
}
I used a HashSet to "save" the "already seen" elements of lst1 and then simply cycle the lst1 and remove the duplicate elements. HashSet.Add returns true if the HashSet doesn't already have an element, false if it already has it.
It isn't exactly clear what you want/what you have, but here there is the solution for another possible use case:
public class MyObject {
public string Item;
public int Position;
}
List<MyObject> lst1 = new List<MyObject> {
new MyObject { Item = "A", Position = 1 },
new MyObject { Item = "B", Position = 2 },
new MyObject { Item = "A", Position = 3 },
};
List<MyObject> lst2 = new List<MyObject> {
new MyObject { Item = "AA", Position = 1 },
new MyObject { Item = "BB", Position = 2 },
new MyObject { Item = "AC", Position = 3 },
};
HashSet<string> seen = new HashSet<string>();
HashSet<int> toBeDeleted = new HashSet<int>();
for (int i = 0; i < lst1.Count; i++) {
if (!seen.Add(lst1[i].Item)) {
toBeDeleted.Add(lst1[i].Position);
lst1.RemoveAt(i);
i--;
}
}
if (toBeDeleted.Count > 0) {
for (int i = 0; i < lst2.Count; i++) {
if (toBeDeleted.Contains(lst2[i].Position)) {
lst2.RemoveAt(i);
i--;
}
}
// or equivalent and shorter, without the for cycle
//lst2.RemoveAll(x => toBeDeleted.Contains(x.Position));
}
In this case in a first pass on lst1 we remove the duplicate items (as seen in the first example) and "save" the Positions that need to be deleted in the HashSet<int> tobedeleted and then we do a second pass on lst2 to remove the elements that need deleting.
Much not clear what you want do, but I try with this:
var filteredList1 = list1.GroupBy(x => x.Item).Select(g => g.First()).ToList();
var removeElements = list2.Where(f => !filteredList1.Any(t => t.Position == f.Position)).ToList();
removeElements.ForEach(x => list2.Remove(x));

Take groups of 5 strings from List [duplicate]

This question already has answers here:
Split List into Sublists with LINQ
(34 answers)
Closed 8 years ago.
I have a List<string> and I want to take groups of 5 items from it. There are no keys or anything simple to group by...but it WILL always be a multiple of 5.
e.g.
{"A","16","49","FRED","AD","17","17","17","FRED","8","B","22","22","107","64"}
Take groups of:
"A","16","49","FRED","AD"
"17","17","17","FRED","8"
"B","22","22","107","64"
but I can't work out a simple way to do it!
Pretty sure it can be done with enumeration and Take(5)...
You can use the integer division trick:
List<List<string>> groupsOf5 = list
.Select((str, index) => new { str, index })
.GroupBy(x => x.index / 5)
.Select(g => g.Select(x => x.str).ToList())
.ToList();
List<List<string>> result = new List<List<string>>();
for(int i = 0; i < source.Count; i += 5 )
result.Add(source.Skip(i).Take(5).ToList());
Like this?
In common programming syntax:
public List<List<string>> Split(List<string> items, int chunkSize = 5)
{
int chunkCount = items.Count/chunkSize;
List<List<string>> result = new List<List<string>>(chunkCount);
for (int i = 0; i < chunkCount; i++ )
{
result.Add(new List<string>(chunkSize));
for (int j = i * chunkSize; j < (i + 1) * chunkSize; j++)
{
result[i].Add(items[j]);
}
}
return result;
}
It's O((N/ChunkSize) x ChunkSize) = O(N), that is linear.
I recommend Batch method from MoreLINQ library:
var result = list.Batch(5).ToList();
Use Take() and Skip() to achieve this:
List<string> list = new List<string>() { "A", "16", "49", "FRED", "AD", "17", "17", "17", "FRED", "8", "B", "22", "22", "107", "64" };
List<List<string>> result = new List<List<string>>();
for (int i = 0; i < list.Count / 5; i++)
{
result.Add(list.Skip(i * 5).Take(5).ToList());
}
If you need performance or cannot use linq cause of your .net version here is a simple solution with O(n)
private List<List<string>> SplitList(List<string> input, int size = 5)
{
var result = new List<List<string>>();
for (int i = 0; i < input.Count; i++)
{
var partResult = new List<string>();
while (true)
{
// save n items
partResult.Add(input[i]);
if ((i+1) % size == 0)
{
break;
}
i++;
}
result.Add(partResult);
}
return result;
}
You can use this function:
public IEnumerable<string[]> GetChunk(string[] input, int size)
{
int i = 0;
while (input.Length > size * i)
{
yield return input.Skip(size * i).Take(size).ToArray();
i++;
}
}
it returns you chunks from your list
you can check it like
var list = new[]
{
"A", "16", "49", "FRED", "AD", "17", "17", "17", "FRED", "8", "B", "22", "22", "107", "64"
};
foreach (var strings in GetChunk(list, 5))
{
Console.WriteLine(strings.Length);
}

Categories