Visit the isogram exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
Distinct
Distinct
using System.Linq;
public static class Isogram
{
public static bool IsIsogram(string word)
{
var lowerLetters = word.ToLower().Where(char.IsLetter).ToList();
return lowerLetters.Distinct().Count() == lowerLetters.Count;
}
}
The steps for this solution are
ToLower produces a new string with its characters changed to lower case.
That string is chained to Where.
Where uses IsLetter to filter the string so only Unicode letters get put into a List.
So no hyphens nor apostrophes (nor anything else that is not a letter) will make it into the List.
Distinct uses Count to get the number of unique letters in the lowerLetters List.
- The function returns whether the number of distinct letters is the same as the number of all letters.
A string is an isogram if its number of distinct letters is the same as the number of all its letters.
- The word
Bravo is an isogram because it has five distinct letters and five letters total.
- The word
Alpha is not an isogram because a is considered to repeat A, so it has only four distinct letters but five letters total.
GroupBy
GroupBy
using System.Linq;
public static class Isogram
{
public static bool IsIsogram(string word)
{
return word.ToLower().Where(Char.IsLetter).GroupBy(ltr => ltr).All(ltr_grp => ltr_grp.Count() == 1);
}
}
The steps for this solution are
ToLower produces a new string with its characters changed to lower case.
That string is chained to Where.
Where uses IsLetter to filter the string so only Unicode letters get put into a List.
So no hyphens nor apostrophes (nor anything else that is not a letter) will make it into the List.
GroupBy groups each letter into its own group.
All uses Count to return whether all groups of letters have only one letter in its group.
If any group has a count of more than 1 for its letter, the word is not an isogram.
- The word
Bravo is an isogram because all five groups of letters has a count of 1.
- The word
Alpha is not an isogram because a is considered to repeat A, so it has only four groups of letters,
and the group for a has a count of 2.
Shortening
When the body of a function is a single expression, the function can be implemented as an expression-bodied member, like so
public static bool IsIsogram(string word) =>
word.ToLower().Where(Char.IsLetter).GroupBy(ltr => ltr).All(ltr_grp => ltr_grp.Count() == 1);
or
public static bool IsIsogram(string word) => word.ToLower().Where(Char.IsLetter).GroupBy(ltr => ltr).All(ltr_grp => ltr_grp.Count() == 1);
Bit field
Bit field
public static class Isogram
{
public static bool IsIsogram(string word) {
int letter_flags = 0;
foreach (char letter in word)
{
if (letter >= 'a' && letter <= 'z')
{
if ((letter_flags & (1 << (letter - 'a'))) != 0)
return false;
else
letter_flags |= (1 << (letter - 'a'));
}
else if (letter >= 'A' && letter <= 'Z')
{
if ((letter_flags & (1 << (letter - 'A'))) != 0)
return false;
else
letter_flags |= (1 << (letter - 'A'));
}
}
return true;
}
}
This solution uses the ASCII value of the letter to set the corresponding bit position.
The string loops through its characters and looks for a character being a through z or A through Z.
The ASCII value for a is 97, and for z is 122.
The ASCII value for A is 65, and for Z is 90.
- If the lower-cased letter is subtracted by
a, then a will result in 0, because 97 minus 97 equals 0.
z would result in 25, because 122 minus 97 equals 25.
So a would have 1 shifted left 0 places (so not shifted at all) and z would have 1 shifted left 25 places.
- If the upper-cased letter is subtracted by
A, then A will result in 0, because 65 minus 65 equals 0.
Z would result in 25, because 90 minus 65 equals 25.
So A would have 1 shifted left 0 places (so not shifted at all) and Z would have 1 shifted left 25 places.
In that way, both a lower-cased z and an upper-cased Z can share the same position in the bit field.
So, for an unsigned thirty-two bit integer, if the values for a and Z were both set, the bits would look like
zyxwvutsrqponmlkjihgfedcba
00000010000000000000000000000001
We can use the bitwise AND operator to check if a bit has already been set.
If it has been set, we know the letter is duplicated and we can immediately return false.
If it has not been set, we can use the bitwise OR operator to set the bit.
If the loop completes without finding a duplicate letter (and returning false), the function returns true.
This approach may be considered to be more idiomatic of the C language than C#.
It is fast, but it only works with the ASCII alphabet.
Source: Exercism csharp/isogram