Count duplicates and combine them in a list - c#

I have a list like this:
var list = new List<string>() { "a", "b", "c", "a" };
As you can see I have one duplicate inside this list.
The desired output should look like this:
a (2)
b (1)
c (1)
The code which I already have looks like this:
list.GroupBy(x => x).Select(x => x.Key + "(" + x.Count() + ")").ToList().ForEach(x => list2.Add(x));
But as you can see I need a second list to get the desired output. My question now is how can I get the desired output with using only one list.

Why don't we just add the items into existing list2?
var list2 = new List<string>();
...
list2.AddRange(list
.GroupBy(item => item)
.Select(group => $"{group.Key} ({group.Count()})"));
Or create list2 from scratch:
var list2 = list
.GroupBy(item => item)
.Select(group => $"{group.Key} ({group.Count()})")
.ToList();

This does what you need:
var list = new List<string>() { "a", "b", "c", "a" };
foreach (var group in list.GroupBy(b => b).OrderBy(g => g.Key))
{
Console.WriteLine($"{group.Key} ({group.Count()})");
}
Consequentially, if one wishes to dedup this list, one could do:
list = list.GroupBy(b => b).Select(g => g.Key).ToList();
The result would be a deduped list with "a", "b" and "c" in it.

You can do this in one statement (I've split it over multiple lines for readability):
var list = new List<string> { "a", "b", "c", "a" };
Console.WriteLine(
string.Join("\n",
list.GroupBy(_=>_).Select(g => $"{g.Key} ({g.Count()})")));
Output:
a (2)
b (1)
c (1)

You can use the Distinct method to remove duplicates.
This returns a IEnumerable and in order to use the ForEach method you have to apply the ToList method first.
The Foreach iterates over the items {"a", "b", "c"}
and prints the current item and the count of this item in the list with dulicates.
list.Distinct().ToList().ForEach(v1 => Console.WriteLine($"{v1} ({list.Count(v2 => v1 == v2)})"))

Related

Dictionary of List to List of KeyValuePair using LINQ

I'm trying to find the most "elegant" way to deal with the transformation between two collections using LINQ. The source container type is Dictionary<int, List<string>> and I need to convert it to List<KeyValuePair<string, string>>. Basically I need to duplicate the key in the dictionary for every element in its corresponding list into a flattened list. Below shows my 2 attempts to solve the issue.
Dictionary<int, List<string>> source = new() {
{100, new() {"a", "b", "c"}}
};
List<KeyValuePair<string, string>> target = new();
// Solution 1
foreach (var (score, terms) in source)
{
foreach (var term in terms)
{
target.Add(new KeyValuePair<string, string>(score.ToString(), term));
}
}
// Solution 2
target = source.SelectMany(kvp =>
{
var(score, terms) = kvp;
return terms.Select(t => new KeyValuePair<string, string>(score.ToString(), t));
}).ToList();
Runnable sample here on .NET Fiddle.
I'm using .NET Core 5 and C# 9 in a console application.
Both of these work, I think, but I want to see if I can make this cleaner. It's pretty difficult to find out how to use LINQ to solve complex transformations like this. One thing I tried to do was "deconstruct" the KeyValuePair in SelectMany(), like:
source.SelectMany((score, terms) => ...)
But this didn't work. I'm not sure if there's a way to make that kind of deconstruction possible. Little things like this I think could go a long way to making this cleaner.
Maybe
var results = source
.SelectMany(x => x.Value
.Select(y => new KeyValuePair<string, string>(x.Key.ToString(), y)))
.ToList();
Or if you are happy with Value Tuples (which have a few benefits)
var results = source
.SelectMany(x => x.Value
.Select(y => (x.Key.ToString(), y)))
.ToList();
If you want to pattern match it in a single expression ("that kind of deconstruction"), you can abuse (?) the switch expression:
target = source.SelectMany(kvp => kvp switch { var (score, terms) =>
terms.Select(t => new KeyValuePair<string, string>(score.ToString(), t))
}).ToList();
You can use the overload of SelectMany that has a parameter resultSelector.
var results = source.SelectMany(sourceItem => sourceItem.Value,
// parameter resultSelector:
(sourceItem, listItem) => ...
In baby steps:
Source is a sequence of KeyValuePair<int, List<string>>. So every sourceItem is one KeyValuePair<int, List<string>>. sourceItem.Value is the List<string> that belongs to the int sourceItem.Key. In the parameter resultSelector you get a combination of the complete sourceItem, with one item from the list:
(sourceItem, listItem) =>
So if your source is:
{ 1, {"A", "B"} }
{ 2, {"C", "D" "E"} }
{ 3, empty list }
You get:
( { 1, {"A", "B"} }, "A")
( { 1, {"A", "B"} }, "B")
( { 2, {"C", "D" "E"} }, "C")
( { 2, {"C", "D" "E"} }, "D")
( { 2, {"C", "D" "E"} }, "E")
No items for 3, the list is empty
Now, from every sourceItem you want sourceItem.Key.ToString(), from every listItem you want the complete listItem. So your SelectMany will be:
var results = source.SelectMany(sourceItem => sourceItem.Value,
(sourceItem, listItem) => new KeyValuePair<string, string>
(sourceItem.Key.ToString, listItem))
.ToList();

List of list iteration(all possible one direction combination)

I have a List<List<string>> representing a grid of rows and columns (the count of each is dynamic).
I need to iterate over the items and display all possible combinations in one direction.
If I have the following items, for example:
var items = new List<List<string>>
{
new List<string> {"A", "B", "C", "D"},
new List<string> {"E", "F", "G", "H"},
new List<string> {"I", "J", "K", "L"},
};
The output should be :
---> *ABCD-ABCH-ABCL-ABGD-ABGH-ABGL-ABKD-ABKH-ABKL-........... IJKL*.
How can I iterate over the list to achieve this result?
What you want is the Cartesian product of the transpose. So break it down. First let's take the transpose:
public static List<List<T>> Transpose(
this List<List<T>> sequences)
{
// TODO: throw if sequences is null
// TODO: throw if sequences contains any null
// TODO: throw if the sequences are not all the same length
return Enumerable.Range(0, sequences[0].Count)
.Select(i =>
Enumerable.Range(0, sequences.Count)
.Select(j => sequences[j][i])
.ToList())
.ToList();
}
We can take the Cartesian Product from my answer here: https://stackoverflow.com/a/3098381/88656
And now the answer to your question is straightforward.
IEnumerable<string> combinations = items
.Transpose()
.CartesianProduct()
.Select(sequence => string.Join("", sequence));
Remember, the way to solve these problems is to break down the problem into a workflow of more basic operations on sequences, and then compose extension methods together into the workflow.
Another approach to this problem, if you need a combination of N unique elements, is to flatten the matrix:
var elementArray = items.SelectMany(x => x).ToList();
Which turns {{'A', 'B', 'C'}, {'D', 'E', 'F'}} into {'A', 'B', 'C', 'D', 'E', 'F'}
Then use the following LINQ extension taken from another question (place it anywhere in your project):
public static class Ex
{
public static IEnumerable<IEnumerable<T>> DifferentCombinations<T> (this IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).DifferentCombinations(k - 1).Select(c => (new[] { e }).Concat(c)));
}
}
Used as:
var combinations = elementArray.DifferentCombinations(4)
.Select(
l => string.Join("", l.ToArray())
).ToList();
In this case, it will combine up to a length of 4 (DifferentCombinations(4)).

Order by multiple properties

I have list which I want to order by like this
First by "ab"
Then by alphabetical order inside the list by "ab"
Then by "cd"
Then by alphabetical order inside the list by "cd"
Then by "ef"
Then by alphabetical order inside the list by "ef"
and then the rest by alphabetical order
I have this linq query
var groups = profileModel.Groups.
OrderByDescending(i => i.FullName.ToLower().Contains("ab")).
ThenByDescending(i => i.FullName.ToLower().Contains("cd")).
ThenByDescending(i => i.FullName.ToLower().Contains("ef"));
How should I extend this one? Do I have to use group by?
It seems that you want this, then no need to use GroupBy:
var groupOrders = new List<string> { "ab", "cd", "ef" };
var resultList = profileModel.Groups
.Select(x => new { ModelGroup = x, First2Letter = x.FullName.Substring(Math.Min(2, x.FullName.Length)) })
.Select(x => new
{
x.ModelGroup,
x.First2Letter,
Index = groupOrders.FindIndex(s => s.Equals(x.First2Letter, StringComparison.OrdinalIgnoreCase))
})
.OrderByDescending(x => x.Index >= 0) // first all known groups
.ThenBy(x => x.Index)
.ThenBy(x => x.ModelGroup.FullName)
.Select(x => x.ModelGroup)
.ToList();
For custom ordering, you can assign a value to each compare condition and OrderByDescending will order by that. Something like this..
lstModel = profileModel.Groups.OrderByDescending(m => m.FullName.ToLower().Contains("ab") ? 3 :
m.FullName.ToLower().Contains("cd") ? 2 :
m.FullName.ToLower().Contains("ef") ? 1 : 0).ToList();
If I got the problem correctly, this will order items based on what they contain. should work in EF as well.
var orderItems = from item in profileModel.Groups
let name = item.FullName.ToLower()
orderby (name.Contains("ab") ? 1 : 0) + (name.Contains("cd") ? 0.1 : 0) + (name.Contains("ef") ? 0.01 : 0) descending
select item;
EDIT
After rereading the problem this might be the right solution
var orderItems = from item in profileModel.Groups
let name = item.FullName.ToLower()
let order = name.Contains("ab") ? 3 : name.Contains("cd") ? 2 : name.Contains("ef") ? 1 : 0
orderby order descending, item.FullName
select item;
If you might have more or different level1 values to test, you may want a generic version.
Using a convenient extension method FirstOrDefault that takes the default value to return
public static T FirstOrDefault<T>(this IEnumerable<T> src, Func<T, bool> test, T defval) => src.Where(aT => test(aT)).DefaultIfEmpty(defval).First();
You can create an array of first level values in order, and sort first on that, then alphabetically:
var level1 = new[] { "ab", "cd", "ef" };
var ans = groups.OrderBy(i => level1.Select((op, n) => (op, n))
.FirstOrDefault(opn => i.FullName.Contains(opn.op),
(op: String.Empty, n: level1.Length)).n)
.ThenBy(i => i.FullName);

Compare 2 List<string> with a LIKE clause

I have 2 lists, one a list of file names, the second a list of file name stubs, i would like to select everything from the first list where the file name is like the file name stub.
List<string> files = new List<string>() {"a.txt", "b.txt", "c.txt"};
List<string> fileStub = new List<string>() {"a", "b", "c"};
The query would return all records from the first list.
Thanks in advance.
var results = files.Where(x => fileStub.Any(y => x.Contains(y))).ToList();
if the order is important (of course, with this sample, the size of the two lists is important, if you don't want IndexOutOfRange exceptions)
var res = files.Where((file, index) => file.Contains(fileStub[index]));
if you don't mind order (than list sizes are not important)
var res = files.Where(file => fileStub.Any(fs => file.Contains(fs)));
var result = files.Where(item => fileStub.Any(stub => item.Contains(stub))).ToList();
Use the Any and Contains methods.
List<string> files = new List<string>() {"a.txt", "b.txt", "c.txt"};
List<string> fileStub = new List<string>() {"a", "c"};
var result = files.Where(x => fileStub.Any(y => x.Contains(y))).ToList();

Get a new result from a List

I have a detailed list and I want a new one with the elements of one property with no duplicates like this.
List<Class1> list = List<Class1>();
list.Add(new Class1("1", "Walmart", 13.54));
list.Add(new Class1("2", "Target", 12.54));
list.Add(new Class1("3", "Walmart", 14.54));
list.Add(new Class1("4", "BestBuy", 16.54));
list.Add(new Class1("5", "Walmart", 19.54));
list.Add(new Class1("6", "Amazon", 12.33));
My new list
List<Stores> newList = list.Select / FindAll / Group ?
I want this collection
newList = "Walmart", "Target", "BestBuy", "Amazon"
You need Distinct and Select.
var newList = list.Select(x => x.Name).Distinct().ToList();
If you also want your original class, you would have to get a bit more fancy.
Either get MoreLINQ and use its DistinctBy method:
var newList = list.DistinctBy(x => x.Name).ToList();
Or use a clever GroupBy hack:
var newList = list.GroupBy(x => x.Name).Select(x => x.First());
Lets assume that Class1 is defined as :
public class Class1
{
string Id {get;set;}
string Store {get;set;}
double Price {get;set;}
}
You can have your result as:
var result = list.Select(x => x.Store).Distinct().ToList();
You want to use Select() to select a specific property of the items:
list.Select(c => c.Company);
This returns an IEnumerable<string>.
You would then want to call .Distinct().
newList = list.Select(x => x.Store).Distinct().ToList();

Categories

Resources