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);
}
Related
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"
});
}
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
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
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.
This question already has answers here:
C# LINQ find duplicates in List
(13 answers)
Closed 3 years ago.
I have a List<string> which has some words duplicated. I need to find all words which are duplicates.
Any trick to get them all?
In .NET framework 3.5 and above you can use Enumerable.GroupBy which returns an enumerable of enumerables of duplicate keys, and then filter out any of the enumerables that have a Count of <=1, then select their keys to get back down to a single enumerable:
var duplicateKeys = list.GroupBy(x => x)
.Where(group => group.Count() > 1)
.Select(group => group.Key);
If you are using LINQ, you can use the following query:
var duplicateItems = from x in list
group x by x into grouped
where grouped.Count() > 1
select grouped.Key;
or, if you prefer it without the syntactic sugar:
var duplicateItems = list.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x.Key);
This groups all elements that are the same, and then filters to only those groups with more than one element. Finally it selects just the key from those groups as you don't need the count.
If you're prefer not to use LINQ, you can use this extension method:
public void SomeMethod {
var duplicateItems = list.GetDuplicates();
…
}
public static IEnumerable<T> GetDuplicates<T>(this IEnumerable<T> source) {
HashSet<T> itemsSeen = new HashSet<T>();
HashSet<T> itemsYielded = new HashSet<T>();
foreach (T item in source) {
if (!itemsSeen.Add(item)) {
if (itemsYielded.Add(item)) {
yield return item;
}
}
}
}
This keeps track of items it has seen and yielded. If it hasn't seen an item before, it adds it to the list of seen items, otherwise it ignores it. If it hasn't yielded an item before, it yields it, otherwise it ignores it.
and without the LINQ:
string[] ss = {"1","1","1"};
var myList = new List<string>();
var duplicates = new List<string>();
foreach (var s in ss)
{
if (!myList.Contains(s))
myList.Add(s);
else
duplicates.Add(s);
}
// show list without duplicates
foreach (var s in myList)
Console.WriteLine(s);
// show duplicates list
foreach (var s in duplicates)
Console.WriteLine(s);
If you're looking for a more generic method:
public static List<U> FindDuplicates<T, U>(this List<T> list, Func<T, U> keySelector)
{
return list.GroupBy(keySelector)
.Where(group => group.Count() > 1)
.Select(group => group.Key).ToList();
}
EDIT: Here's an example:
public class Person {
public string Name {get;set;}
public int Age {get;set;}
}
List<Person> list = new List<Person>() { new Person() { Name = "John", Age = 22 }, new Person() { Name = "John", Age = 30 }, new Person() { Name = "Jack", Age = 30 } };
var duplicateNames = list.FindDuplicates(p => p.Name);
var duplicateAges = list.FindDuplicates(p => p.Age);
foreach(var dupName in duplicateNames) {
Console.WriteLine(dupName); // Will print out John
}
foreach(var dupAge in duplicateAges) {
Console.WriteLine(dupAge); // Will print out 30
}
Using LINQ, ofcourse.
The below code would give you dictionary of item as string, and the count of each item in your sourc list.
var item2ItemCount = list.GroupBy(item => item).ToDictionary(x=>x.Key,x=>x.Count());
For what it's worth, here is my way:
List<string> list = new List<string>(new string[] { "cat", "Dog", "parrot", "dog", "parrot", "goat", "parrot", "horse", "goat" });
Dictionary<string, int> wordCount = new Dictionary<string, int>();
//count them all:
list.ForEach(word =>
{
string key = word.ToLower();
if (!wordCount.ContainsKey(key))
wordCount.Add(key, 0);
wordCount[key]++;
});
//remove words appearing only once:
wordCount.Keys.ToList().FindAll(word => wordCount[word] == 1).ForEach(key => wordCount.Remove(key));
Console.WriteLine(string.Format("Found {0} duplicates in the list:", wordCount.Count));
wordCount.Keys.ToList().ForEach(key => Console.WriteLine(string.Format("{0} appears {1} times", key, wordCount[key])));
I'm assuming each string in your list contains several words, let me know if that's incorrect.
List<string> list = File.RealAllLines("foobar.txt").ToList();
var words = from line in list
from word in line.Split(new[] { ' ', ';', ',', '.', ':', '(', ')' }, StringSplitOptions.RemoveEmptyEntries)
select word;
var duplicateWords = from w in words
group w by w.ToLower() into g
where g.Count() > 1
select new
{
Word = g.Key,
Count = g.Count()
}
I use a method like that to check duplicated entrys in a string:
public static IEnumerable<string> CheckForDuplicated(IEnumerable<string> listString)
{
List<string> duplicateKeys = new List<string>();
List<string> notDuplicateKeys = new List<string>();
foreach (var text in listString)
{
if (notDuplicateKeys.Contains(text))
{
duplicateKeys.Add(text);
}
else
{
notDuplicateKeys.Add(text);
}
}
return duplicateKeys;
}
Maybe it's not the most shorted or elegant way, but I think that is very readable.
lblrepeated.Text = "";
string value = txtInput.Text;
char[] arr = value.ToCharArray();
char[] crr=new char[1];
int count1 = 0;
for (int i = 0; i < arr.Length; i++)
{
int count = 0;
char letter=arr[i];
for (int j = 0; j < arr.Length; j++)
{
char letter3 = arr[j];
if (letter == letter3)
{
count++;
}
}
if (count1 < count)
{
Array.Resize<char>(ref crr,0);
int count2 = 0;
for(int l = 0;l < crr.Length;l++)
{
if (crr[l] == letter)
count2++;
}
if (count2 == 0)
{
Array.Resize<char>(ref crr, crr.Length + 1);
crr[crr.Length-1] = letter;
}
count1 = count;
}
else if (count1 == count)
{
int count2 = 0;
for (int l = 0; l < crr.Length; l++)
{
if (crr[l] == letter)
count2++;
}
if (count2 == 0)
{
Array.Resize<char>(ref crr, crr.Length + 1);
crr[crr.Length - 1] = letter;
}
count1 = count;
}
}
for (int k = 0; k < crr.Length; k++)
lblrepeated.Text = lblrepeated.Text + crr[k] + count1.ToString();