C# .Where & .Select - c#

I was looking into how to check character duplicates and I came across this method, it works, but I am trying to understand how it works. If anyone could explain this method so I can better understand what is occurring I would greatly appreciate it. Thank you.
static int duplicateAmount(string word)
{
var duplicates = word.GroupBy(a => a)
.Where(g => g.Count() > 1)
.Select(i => new { Number = i.Key, Count = i.Count() });
return duplicates.Count();
}

The idea is to group the characters in the string and check if any group contains more than one elements, signifying duplicate occurrence of characters. For example,
word.GroupBy would produce a grouping result as the following.
As you can observe, the characters t,i,and s has more than one occurrences. The Where condition filters the groups which has more than one element and the count method counts the numbers of filtered groups.
In your case, if you are interested only in counting the number of characters that are duplicate, you could refactor the method further as
static int duplicateAmount(string word)
{
return word.GroupBy(a => a)
.Count(g => g.Count() > 1);
}
This avoids creation of intermediate types, which is not quite required if you are interested only the count

When you iterate a string, you do so by iterating all its characters.
Therefore:
static int duplicateAmount(string word)
{
var duplicates = word.GroupBy(a => a) // Groups all the unique chars
.Where(g => g.Count() > 1) // filters the groups with more than one entry
// Maps the query result to an anonymous object containing the char
// and their amount of occurrences
.Select(i => new { Number = i.Key, Count = i.Count() });
// return the count of elements in the resulting collection
return duplicates.Count();
}
Now that you have understood that, you can probably tell the last step (the mapping) is unnecessary since we're creating a structure we're not using at all: { Number, Count}.
The code can perfectly be
static int duplicateAmount(string word)
{
return word.GroupBy(a => a) // Groups all the unique chars
// Counts the amount of groups with more than one occurrence.
.Count(g => g.Count() > 1);
}
Edited: Removed the where clause as noted in the comments. Thanks #DrkDeveloper

Related

Grouping sequences with linq

My task is:
A sequence of non-empty strings stringList is given, containing only uppercase letters of the
Latin alphabet.
For all strings starting with the same letter, determine their total length and obtain a sequence
of strings of the form "S-C", where S is the total length of all strings from stringList that begin
with the character C. Order the resulting sequence in descending order of the numerical values
of the sums, and for equal values of the sums, in ascending order of the C character codes.
Everything needs to be done in one line via linq.
I tried this:
return stringList.GroupBy(x => x.FirstOrDefault())
.Select(x => x.ToString().Length + "-" + x.Key)
.OrderByDescending(g => g.Length)
.ThenBy(g => g.FirstOrDefault());
But this one does not work properly.
The following method will take a list of strings and returns the results as you have asked for:
IEnumerable<string> ProcessValues(IEnumerable<string> strings) {
return strings.GroupBy(s => s.FirstOrDefault())
.Select(group => (Character: group.Key, Length: group.Sum(s => s.Length)))
.OrderByDescending(g => g.Length)
.ThenBy(g => g.Character)
.Select(g => $"{g.Length}-{g.Character}");
}
An example of the results you can expect is provided below:
var stringList = new List<string> { "Hello", "How", "Are", "You", "Today" }
var result = ProcessValues(stringList); // { "8-H", "5-T", "3-A", "3-Y" }

Sorting an array by another array's values using OrderByDescending() does not sort the array

I'm trying to implement a simple Huffman coding algorithm. I take my input string (ddddbbcccaeeeee) and use it to create 2 arrays, those being a char array called usedCharacters and an int array called characterCounts. However these arrays need to be sorted by the number of times the character appears in the input string so the Huffman tree can be constructed. I tried using LINQ's OrderByDescending() method like I had seen online:
usedCharacters = usedCharacters.OrderByDescending(i => characterCounts).ToArray();
characterCounts = characterCounts.OrderByDescending(i => i).ToArray();
The program runs but when I check the results the characters are very obviously still in order as they appear in the input string, meaning no sorting is actually done. On the other hand, characterCounts does succesfully sort. I also tried the more commonly seen online solution of usedCharacters.OrderByDescending(i => characterCounts.IndexOf(i)).ToArray() but that just causes an index out of bounds exception for reasons I don't fully understand. If anybody could give me some insight into what I'm missing that would be greatly appreciated. Thank you!
The simplest way to achieve what you're trying to do is to use a GroupBy expression.
var s = "ddddbbcccaeeeee";
var list = s.GroupBy(x => x)
.Select(x => new { Char = x.Key, Count = x.Count() })
.OrderByDescending(x => x.Count);
foreach(var item in list)
{
Console.WriteLine(item.Char + " " + item.Count);
}
The code treats s as a character array and counts instances of all characters. The OrderByDescending then sorts by Count.
The output of code below should look something like this:
e 5
d 4
c 3
b 2
a 1
Your LINQ statement is trying to sort each element in usedCharacters by an by a constant int[]. It doesn't do anything like matching up the elements of both arrays. It's exactly as if you are doing this:
usedCharacters = usedCharacters.OrderByDescending(i => 42).ToArray();
It just leaves the array in the same order.
If you have two separate list and you want to order the first based on the second then you need to use Zip like this:
usedCharacters =
usedCharacters
.Zip(characterCounts)
.OrderByDescending(x => x.Second)
.Select(x => x.First)
.ToArray();
If you have the initial string of characters then this is the simplest way to get your result:
string characters = "ddddbbcccaeeeee";
char[] usedCharacters =
characters
.GroupBy(x => x)
.OrderByDescending(x => x.Count())
.Select(x => x.Key)
.ToArray();

Compare and combine strings to get duplicates

I will try to describe my question in the best way I can.
I have a list with X strings ("NOTION", "CATION", "COIN", "NOON").
I am trying to compare them and find the most times each character (letter) was used, use that to get the number of that character, arrange them in alphabetical order, and create a string.
So the result string should be: "ACINNOOT"
Hope is clear what I am describing.
EDIT
So far:
for (int i = 0; i < currentWord.Length; i++)
{
string letter = word.Substring(i, 1);
tempDuplicatedLetterList.Add(letter);
}
// Which letters are repeated and how many times
var duplicatedQuery = tempDuplicatedLetterList.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(y => new { Element = y.Key, Counter = y.Count() })
.ToList();
I came to this, although I think there might be a cleaner way to do it:
var characterSets = new string[] { "NOTION", "CATION", "COIN", "NOON" }
.SelectMany(c => c.GroupBy(cc => cc)) // create character groups for each string, and flatten the groups
.GroupBy(c => c.Key) // group the groups
.OrderBy(cg => cg.Key) // order by the character (alphabetical)
.Select(cg => new string(cg.Key, cg.Max(v => v.Count()))) // create a string for each group, using the maximum count for that character
.ToArray(); // make an array
var result = string.Concat(characterSets);

How to do in this in Linq C#

So far, I have this:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)));
Configuration folder will contain pairs of files:
abc.json
abc-input.json
def.json
def-input.json
GetReportName() method strips off the "-input" and title cases the filename, so you end up with a grouping of:
Abc
abc.json
abc-input.json
Def
def.json
def-input.json
I have a ReportItem class that has a constructor (Name, str1, str2). I want to extend the Linq to create the ReportItems in a single statement, so really something like:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)))
**.Select(x => new ReportItem(x.Key, x[0], x[1]));**
Obviously last line doesn't work because the grouping doesn't support array indexing like that. The item should be constructed as "Abc", "abc.json", "abc-input.json", etc.
If you know that each group of interest contains exactly two items, use First() to get the item at index 0, and Last() to get the item at index 1:
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x)))
.Where(g => g.Count() == 2) // Make sure we have exactly two items
.Select(x => new ReportItem(x.Key, x.First(), x.Last()));
var v = Directory.EnumerateFiles(_strConfigurationFolder)
.GroupBy(x => GetReportName(Path.GetFileNameWithoutExtension(x))).Select(x => new ReportItem(x.Key, x.FirstOrDefault(), x.Skip(1).FirstOrDefault()));
But are you sure there will be exactly two items in each group? Maybe has it sence for ReportItem to accept IEnumerable, not just two strings?

Linq query group by distinct

I have a linq query which is almost complete.
It's working but I need to retrieve the original list of the items in the list that fulfills the requirements.
Now it only returns true or false if any has the count > numberOfResourceToBook.
But instead I want to return all the items in availableTimes having that (with all its properties).
bool enoughResourceAvailable = availableTimes.GroupBy(l => new { l.From, l.To })
.Select(g => new
{
Date = g.Key,
Count = g.Select(l => l.ResourceId).Distinct().Count()
}).Where(c => c.Count >= numberOfResourcesToBook).Count() > 0;
I realize this is an old question and hopefully you already figured this out long ago. But for someone else stumbling into this question, here is how you could solve it:
First, you need to add the available times for each group to the anonymous objects you are selecting so you have a way to get them back after grouping. Get rid of the .Count > 0 at the end so the result is an IEnumerable of the anonymous objects instead of a boolean.
var result = availableTimes
.GroupBy(l => new { l.From, l.To })
.Select(g => new
{
Date = g.Key,
Count = g.Select(l => l.ResourceId).Distinct().Count(),
Times = g.Select(l => l) // Add this to capture the times for the group
})
.Where(c => c.Count >= numberOfResourcesToBook);
Next, you can set the enoughResourceAvailable by using .Any() on the previous result. It does the same job as .Count() > 0 except it doesn't always need to enumerate the entire list: it can return true as soon as it finds at least one item.
bool enoughResourceAvailable = result.Any();
Lastly, to get all the times back which matched the query (if there are any), you can use SelectMany() on the result, like so:
var allMatchingTimes = result.SelectMany(c => c.Times);
Working demo: https://dotnetfiddle.net/HCEuMR

Categories