Compare elements in a list and create a new one - c#

I have a List with Users:
List<UserEntry> list1 = new List<UserEntry>();
list1.Add(new UserEntry { login = "1", requestorList = new List<string>() { "1", "2", "3" } });
list1.Add(new UserEntry { login = "2", requestorList = new List<string>() { "1", "4", "3" } });
list1.Add(new UserEntry { login = "3", requestorList = new List<string>() { "1", "2", "3" } });
I want to find the elements that have same requestorList and group them in a second List. In the above example the first and third element have 1,2,3.
I tried this and it doesn't work:
for (int i = 0; i < list1.Count; i++)
{
var kar = list1.ElementAt(i);
for (int j = 0; j < list1.Count; j++)
{
if(kar.requestorList.Equals(list1.ElementAt(j).requestorList))
{
list2.Add(kar);
}
}
}
EDIT: The secoond List should have only 2 elements, since the first one and the third have same requestorLists

If you want to obtain the List you want, fancy doing this:
First of all, you need to add a second if, like this:
if(list1.ElementAt(i).Equals(list1.ElementAt(j))){
continue;
}
in order to skip the cases where you would compare an element to itself.
Also, if you don't want duplicates, use this instead of only doing list2.Add(kar); :
if(!list2.Contains(kar)){
list2.Add(kar);
}
Edit: The full code should look something like this if i didn't mess up:
for (int i = 0; i < list1.Count; i++)
{
var kar = list1.ElementAt(i);
for (int j = 0; j < list1.Count; j++)
{
if(kar.requestorList.SequenceEqual(list1.ElementAt(j).requestorList))
{
if(list1.ElementAt(i).Equals(list1.ElementAt(j))){
continue;
}
if(!list2.Contains(kar)){
list2.Add(kar);
}
}
}
}

This is your code with a slight difference
for (int i = 0; i < list1.Count; i++)
{
var kar = list1.ElementAt(i);
for (int j = i+1; j < list1.Count; j++)
{
if (Enumerable.SequenceEqual(kar.requestorList.OrderBy(t => t), list1.ElementAt(j).requestorList.OrderBy(t => t)))
{
list2.Add(kar);
list2.Add(list1.ElementAt(j));
}
}
}

There are several problems with the code:
You can use indexer to access the element from the list instead of ElementAt() which is more verbal. It looks cleaner to me
Equals() only compares reference of your list, not the content. So ["1", "2", "3"] and ["1", "3", "2"] will give false
And the other thing is you compare element in list1 with itself like other answers mention
Here's my attempt using LINQ:
var list2 = list1.GroupBy(e =>
e.requestorList.OrderBy(r => r) //Order list ascending
.Aggregate((i, j) => i + j)) //Append elements in the list together to create "signature" of the list
.Where(g => g.Count() > 1) //Select grouping that has at least 2 elements
.SelectMany(g => g); //Flatten all the lists into single list
I use OrderBy() and Aggregate() to create kind of "signature" for your requestorList, so that ["1", "3", "2"] and ["1", "2", "3"] having the same signature "123", then group the elements by that signature
Not sure what you expect in the list2, so I just flatten everything that have duplicate requestorList into single list

Related

Initialize list - specified number of objects c#

Items is public list of MenuListItemViewModel items, in example below im creating new list with 2 elements:
Items = new List<MenuListItemViewModel>
{
new MenuListItemViewModel
{
Value = "500",
Letter = "D"
},
new MenuListItemViewModel
{
Value = "-500",
Letter = "W"
},
};
How to do exactly the same but with variable numbers of items i want to have in the list? Something like loop x times (like below, but it wont work in current state)
for (int i = 0; i < 5; i++)
{
new MenuListItemViewModel
{
Value = "500",
Letter = "D"
},
}
You can use LINQ:
Items = Enumerable.Range(0, number)
.Select(i => new MenuListItemViewModel
{
Value = "500",
Letter = "D"
}).ToList();
You can do
Items = new List<MenuListItemViewModel>();
for (int i = 0; i < 5; i++)
{
Items.Add(
new MenuListItemViewModel
{
Value = "500",
Letter = "D"
});
}

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);
}

How to find modal value accross List<List<double>> for each inner value?

This is remarkably similar to another question I asked previously. I have no idea how to do things in Linq so I need some help with this one. I want to find the Modal value of a List> for each inner value.
I have the following list:
List<List<double>> myFullList = new List<List<double>>();
for(int i = 1; i <= numberOfLoops; i++)
{
List<double> myInnerList = new List<double>();
for(int i = 1; i <= 10; i++)
{
// Populate inner list with random numbers
myInnerList.Add(double myRandomNumber);
}
// Add the inner list to the full list
myFullList.Add(myInnerList);
}
The list should look something like this:
myFullList[0] = {rand#1,rand#2,rand#3,...,rand#10}
myFulllist[1] = {rand#1,rand#2,rand#3,...,rand#10}
.
.
.
.
myFulllist[1] = {rand#1,rand#2,rand#3,...,rand#10}
I need to find the MODAL VALUE for that data to form ONE single list that looks something like this:
List<double> mode= new List<double>();
mode= {mode#1, mode#2........mode#10}
This output variable will find the mode of the data for the same "row" of data in the inner list.
Simple example:
innerList[0] = {1.00,2.00,3.00};
innerList[1] = {3.00,2.00,8.00};
innerList[2] = {3.00,9.00,1.00};
innerList[3] = {3.00,1.00,1};
fullList = {innerList[0], innerList[1], innerList[2], innerList[3]};
modeList = {3,2,1};
Not the most elegant way, but probably easier to Understand. It has been succesfully tested :)
class Program
{
static void Main(string[] args)
{
Random rnd = new Random();
int numberOfLoops = 10;
List<List<int>> myFullList = new List<List<int>>();
for (int i = 0; i < numberOfLoops; i++)
{
List<int> myInnerList = new List<int>();
for (int j = 0; j < 10; j++)
{
// Populate inner list with random numbers
myInnerList.Add(rnd.Next(0, 10));
}
// Add the inner list to the full list
myFullList.Add(myInnerList);
}
myFullList = Transpose<int>(myFullList);
List<int> result = new List<int>();
foreach (List<int> subList in myFullList)
result.Add(Mode(subList));
//TO-DO: linq version!
//List<int> result = myFullList.ForEach(num => Mode(num));
}
public static int Mode(List<int> x)
{
int mode = x.GroupBy(v => v)
.OrderByDescending(g => g.Count())
.First()
.Key;
return mode;
}
public static List<List<T>> Transpose<T>(List<List<T>> lists)
{
var longest = lists.Any() ? lists.Max(l => l.Count) : 0;
List<List<T>> outer = new List<List<T>>(longest);
for (int i = 0; i < longest; i++)
outer.Add(new List<T>(lists.Count));
for (int j = 0; j < lists.Count; j++)
for (int i = 0; i < longest; i++)
outer[i].Add(lists[j].Count > i ? lists[j][i] : default(T));
return outer;
}
}
That's quiet simple, here is code (sorry, haven't fully tested it, but it's good to start with):
public static class ModalHelper
{
public static List<double> GetModals(List<List<double>> source)
{
return source.Select(list => list.Sum()/list.Count).ToList();
}
}
This linq query should do the trick
var result = list.Select<List<double>, List<KeyValuePair<int, double>>>(sub =>
{
List<KeyValuePair<int, double>> elems = new List<KeyValuePair<int, double>>(sub.Count);
for (int i = 0; i < sub.Count; ++i)
elems.Add(new KeyValuePair<int, double>(i, sub[i]));
return elems;
}).SelectMany((x) => x).GroupBy((x) => x.Key).Select<IGrouping<int, KeyValuePair<int, double>>, double>(x =>
{
var y = x.GroupBy(g => g.Value).OrderByDescending(g => g.Count());
return y.First().First().Value;
});
Here is an example:
static void Main(string[] args)
{
List<List<double>> list = new List<List<double>>();
list.Add(new List<double> { 1.00, 2.00, 3.00 });
list.Add(new List<double> { 3.00, 2.00, 8.00 });
list.Add(new List<double> { 3.00, 9.00, 1.00 });
list.Add(new List<double> { 3.00, 1.00, 1 });
var result = list.Select<List<double>, List<KeyValuePair<int, double>>>(sub =>
{
List<KeyValuePair<int, double>> elems = new List<KeyValuePair<int, double>>(sub.Count);
for (int i = 0; i < sub.Count; ++i)
elems.Add(new KeyValuePair<int, double>(i, sub[i]));
return elems;
}).SelectMany((x) => x).GroupBy((x) => x.Key).Select<IGrouping<int, KeyValuePair<int, double>>, double>(x =>
{
var y = x.GroupBy(g => g.Value).OrderByDescending(g => g.Count());
return y.First().First().Value;
});
foreach (double val in result)
Console.Write(val + " ");
Console.WriteLine();
}
Here a live version at ideone: http://ideone.com/ye2EhG
First the lists are transformed to lists of key-value-pairs which add the information of the index inside each list. Then these lists are flattened to one single list and then this new list is grouped by the index. The groups are ordered by the count of values and the most-frequent element is returned for each group.
Something like this should give the mode:
var temp = myFullList.SelectMany(l => l).GroupBy(all => all).Select(result => new
{
Value = result.Key,
Count = result.Count()
}).OrderByDescending(t => t.Count);
Explanation:
From MSDN - The SelectMany
Projects each element of a sequence to an IEnumerable and flattens
the resulting sequences into one sequence.
So it gives us each decimal from the sub lists. We then group that by the decimals themselves and select the count for each along with their value. Finally we order by the count to give the most frequently occurring decimals first.
Edit based on the comment from Robert S
It seems the above code isn't what was required. As Robert S points out that code gives the mode of ALL numbers in the List<List<double>> but the question is how to get the mode from each column.
The following code should give the mode per column. Note that this code ignores duplicates; if more than one number appears the same amount of times the first number will be given:
var result1 = myFullList[0].Select((l, i) => new
{
Column = i,
Mode = myFullList.GroupBy(fl => fl[i]).OrderByDescending(t => t.Count()).Select(t => t.Key).FirstOrDefault()
});
foreach (var item in result1)
{
Console.WriteLine(string.Format("{0} {1}", item.Column, item.Mode));
}
The code is using the overload of Select to take the index of the element (the column in the OP's definition). It then groups each item at that index. Note there are no bounds checks on myFullList but in production code there should be.
If duplicates are an issue we need two steps:
var temp2 = myFullList[0].Select((l, i) => new
{
Column = i,
Mode = myFullList.GroupBy(fl => fl[i]).Select(t => new { Number = t.Key, Count = t.Count() }).OrderByDescending(a => a.Count)
});
var result2 = temp2.Select(t => new
{
Column = t.Column,
Mode = t.Mode.Where(m => m.Count == t.Mode.Max(tm => tm.Count))
});
foreach (var item in result2)
{
for (int i = 0; i < item.Mode.Count(); i++)
{
Console.WriteLine(string.Format("{0} {1}", item.Column, item.Mode.ElementAt(i)));
}
}
In the above code temp2.Mode will contain an IEnumerable of an anonymous object containing the number and how many times that number has appeared. result2 is then populated by grabbing each of those items where the count matches the max of the count.
Given the input:
myFullList.Add(new List<double> { 1.00, 2.00, 3.00 });
myFullList.Add(new List<double> { 3.00, 2.00, 3.00 });
myFullList.Add(new List<double> { 3.00, 9.00, 1.00 });
myFullList.Add(new List<double> { 3.00, 1.00, 1 });
The first code outputs
0 3
1 2
2 3
and the second outputs
0 3
1 2
2 3
2 1
Note we have two outputs for column 2 as both 3 and 1 are equally popular.

Linq List of Objects GroupBy Multiple Property Equality

I have a List<ReportObject> and want to be able to combine certain elements of the list into a single element based on the equality of certain properties from one element matching certain other properties from a second element in the list. In this case, I want to update the first element with values from the second element and then return a list with only the collection of "first elements".
Perhaps GroupBy (or LINQ in general) isn't the right solution here, but it does seem like it would be a lot cleaner than doing a traditional foreach loop and newing up a second list. What I want is something like this:
List<ReportObject> theList = new List<ReportObject>()
{ new ReportObject() { Property1 = "1", Property2 = "2" },
new ReportObject() { Property1 = "2", Property2 = "3" }
new ReportObject() { Property1 = "1", Property2 = "3" } };
List<ReportObject> newList = new List<ReportObject>();
for(int i = 0; i < theList.Count; i++)
{
for(int j = i + 1; i < theList.Count; j++)
{
if (theList[i].Property1 == theList[j].Property2)
{
theList[i].Property2 = theList[j].Property2);
newList.Add(theList[i]);
theList.RemoveAt(j);
}
}
}
return newList;
var newList = theList.GroupBy(x => x.Property1).Select(g => g.First()).ToList();
From your code, it's obviously that the newList will contain all items which have Property1 = Property2:
var newList = theList.SelectMany((x,i)=>
theList.Where((y,j)=>j>i && y.Propery2 == x.Propery1)
.Select(a=> new ReportObject{
Property1=x.Property1,
Property2=x.Property1
});
I think something like theList.GroupBy(x => x.Property1, x => x.Property2); will do what you want.

Query first element in all columns in a jagged array

int numPics = 3; //Populated from a query
string[] picID = new string[numPics];
//pictureTable is constructed so that it correlates to pictureTable[column][row]
string[][] pictureTable = null; //assume the table has data
for (int i = 0; i < numPics; i++)
{
//LINQ query doesn't work. Returns an IEnumerable<string> instead of a string.
picID[i] = pictureTable.Where(p => p[0].Equals("ID")).Select(p => p[i]);
}
I am new to LINQ, but I've been searching and haven't found an answer. I want to be able to check the first string of every column in my pictureTable using LINQ to see if it matches a string. Then, I want to take that column and extract data from each of the rows from 0 to i. I understand I can do it with a for loop by changing the column and keeping the row the same, but I want to use LINQ to achieve the same result.
Also if it is possible to get rid of the first for loop and achieve the same result, I would really be interested in that as well.
EDIT: Lets say we have a table that has the following data, keep in mind everything is a string.
Column Name [ID] [Name] [Age]
Row 1 [1] [Jim] [25]
Row 2 [2] [Bob] [30]
Row 3 [3] [Joe] [35]
I want to be able to query a columns name, then be able to get data from it, either by index or querying the row's data. I'll give an example using a for loop that achieves what I want.
string[][] table = new string[][] {
new string[] { "ID", "Name", "Age" },
new string[] { "1", "Jim", "25" },
new string[] { "2", "Bob", "30" },
new string[] { "3", "Joe", "35" }};
string[] resultRow = new string[table.GetLength(1)];
for (int i = 0; i < table.GetLength(0); i++)
{
if (table[i][0] == "Name") //Given this in a LINQ Query
{
Console.WriteLine("Column Name = {0}\n", table[i][0]);
for (int j = 1; j < table.GetLength(1); j++) //starts at 1
{
resultRow[i] = table[i][j]; //This is what I want to happen.
}
break; //exit outer loop
}
}
//Output:
//Column Name = Name
I think this would give you the equivalent of what you are looking for in your resultRow array
string[][] table = new string[][] {
new string[] { "ID", "Name", "Age" },
new string[] { "1", "Jim", "25" },
new string[] { "2", "Bob", "30" },
new string[] { "3", "Joe", "35" }
};
//get index of column to look at
string columnName = "Name";
var columnIndex = Array.IndexOf(table[0], columnName, 0);
// skip the title row, and then skip columns until you get to the proper index and get its value
var results = table.Skip(1).Select(row => row.Skip(columnIndex).FirstOrDefault());
foreach (var result in results)
{
Console.WriteLine(result);
}
One other thing to look at would be SelectMany, as you can use it to flatten mlutiple lists into a single list.
Do you just want to concatenate the strings together? If so you can just do this:
picID[i] = string.Concat(pictureTable.Where(p => p[0].Equals("ID")).Select(p => p[i]));

Categories