How to find index of a value in array of char? - c#

Suppose I have this array of char:
private char[] _alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
.ToCharArray();
I want find the index of the character B, so the code must return 1.
I wrote this method:
public int? GetCharIndex(string code)
{
return _alpha.FirstOrDefault(c => c.ToString() == code.ToUpper());
}
where code is b. The result of GetCharIndex is: 66, why I get this result?
Thanks in advance for any help.

use Array.IndexOf
https://learn.microsoft.com/en-us/dotnet/api/system.array.indexof?view=netframework-4.7.2
The 66 you are getting is 'B' ascii value.
The FirstOrDefault function returns the actual item (B) not the index.
So:
int index = Array.IndexOf(_alpha, code.ToUpper());

As you've mentioned, you'd like to use Linq. The Select in LINQ has an override that let's you expose the index. Getting the first matching value will give us the index of the occurrence. You can also replace the .FirstOrDefault() to .ToList() if you want all indexes on the match.
ToList
char[] _alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToArray();
var code = "b";
var key = _alpha.Select((s, i) => new { i, s })
.Where(t => t.s.ToString().ToUpper() == code.ToUpper())
.Select(t => t.i)
.ToList();
Alternatively the above can also be written as
FirstOrDefault
return _alpha.Select((s, i) => new { i, s }).FirstOrDefault(t => t.s.ToString().ToUpper() == code.ToUpper()).i;

Why I get this result?
Nullable<Char> is implicitly convertible to Nullable<Int32> because Char is implicitly convertible to Int32. That happens in your return statement. You're returning the character from the array if found.
Where people are saying ASCII code, they mean character code; in particular, since .NET uses the UTF-16 character encoding of the Unicode character set the UTF-16 code unit value.
BTW—VB4/5/6/A/Script, Java, JavaScript, … also use UTF-16 for text datatypes.
To use standard LINQ operators, choose one that supplies an index like Select:
Char[] _alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
Func<Char, StringComparison, Int32?> GetIndexOf = (code, comparisonType) => {
var value = code.ToString();
return _alpha
.Select((c, i) => c.ToString()
.Equals(value, StringComparison.InvariantCultureIgnoreCase) ? i : (Int32?)null)
.FirstOrDefault(i => i.HasValue); } ;
Debug.Assert(1 == GetIndexOf('B', StringComparison.InvariantCultureIgnoreCase));
Debug.Assert(null == GetIndexOf('3', StringComparison.InvariantCultureIgnoreCase));
And, you do have to decide what you really intend by your ".ToUpper". As a question/code review issue, it's unclear. It would be better to write your question and code (or at least as a code comment) in such a way as it explains what the proper culture is for letter case comparison. You used one that depends on the program's current culture, which is initialized from the user's culture at the time the program started.

Related

Maximum number of occurrences a character appears in an array of strings

In C#, given the array :
string[] myStrings = new string[] {
"test#test",
"##test",
"######", // Winner (outputs 6)
};
How can I find the maximum number of occurrences that the character # appears in a single string ?
My current solution is :
int maxOccurrences = 0;
foreach (var myString in myStrings)
{
var occurrences = myString.Count(x => x == '#');
if (occurrences > maxOccurrences)
{
maxOccurrences = occurrences;
}
}
return maxOccurrences;
Is their a simplier way using linq that can act directly on the myStrings[] array ?
And can this be made into an extension method that can work on any IEnumerable<string> ?
First of all let's project your strings into a sequence with count of matches:
myStrings.Select(x => x.Count(x => x == '#')) // {1, 2, 6} in your example
Then pick maximum value:
int maximum = myStrings
.Select(s => s.Count(x => x == '#'))
.Max(); // 6 in your example
Let's make an extension method:
public static int CountMaximumOccurrencesOf(this IEnumerable<string> strings, char ch)
{
return strings
.Select(s => s.Count(c => c == ch))
.Max();
}
However there is a big HOWEVER. What in C# you call char is not what you call character in your language. This has been widely discussed in other posts, for example: Fastest way to split a huge text into smaller chunks and How can I perform a Unicode aware character by character comparison? then I won't repeat everything here. To be "Unicode aware" you need to make your code more complicate (please note code is wrote here then it's untested):
private static IEnumerable<string> EnumerateCharacters(string s)
{
var enumerator = StringInfo.GetTextElementEnumerator(s.Normalize());
while (enumerator.MoveNext())
yield return (string)enumerator.Value;
}
Then change our original code to:
public static int CountMaximumOccurrencesOf(this IEnumerable<string> strings, string character)
{
return strings
.Select(s => s.EnumerateCharacters().Count(c => String.Equals(c, character, StringComparison.CurrentCulture))
.Max();
}
Note that Max() alone requires collection to don't be empty (use DefaultIfEmpty() if collection may be empty and it's not an error). To do not arbitrary decide what to do in this situation (throw an exception if it should happen or just return 0) you can may make this method less specialized and leave this responsibility to caller:
public static int CountOccurrencesOf(this IEnumerable<string> strings,
string character,
StringComparison comparison = StringComparison.CurrentCulture)
{
Debug.Assert(character.EnumerateCharacters().Count() == 1);
return strings
.Select(s => s.EnumerateCharacters().Count(c => String.Equals(c, character, comparison ));
}
Used like this:
var maximum = myStrings.CountOccurrencesOf("#").Max();
If you need it case-insensitive:
var maximum = myStrings.CountOccurrencesOf("à", StringComparison.CurrentCultureIgnoreCase)
.Max();
As you can now imagine this comparison isn't limited to some esoteric languages but it also applies to invariant culture (en-US) then for strings that must always be compared with invariant culture you should specify StringComparison.InvariantCulture. Don't forget that you may need to call String.Normalize() also for input character.
You can write something like this. Note the usage of DefaultIfEmpty, to not throw an exception if myStrings is empty, but revert to 0.
var maximum = myStrings.Select(e => e.Count(ee => ee == '#')).DefaultIfEmpty().Max();
You can do that with Linq combined to Regex:
myStrings.Select(x => Regex.Matches(x, "#").Count).max();

How to create series of string combination of Alphabet and Number?

I have a collection of data list for example:
List<String> Dummy = new List<String>()
{
"1001A",
"1003A",
"1002B",
"1002A",
"1003B",
"1001B",
"1003C",
"1002C",
"1001C",
};
I want to arrange this data list into series. The main series will focus on the Alphabet(the last char of the string) and the sub series will be base on the numbers left. The output will like this:
1001A
1002A
1003A
1001B
1002B
1003B
1001C
1002C
1003C
I already have function codes only for the series of numbers except about the example above. Thanks for the reading my post.
var result = Dummy
.OrderBy(p => p[p.Length - 1])
.ThenBy(p => p.Substring(0, p.Length - 1));
This will first order by the last character of the string and then by everything except the last character of the string.
If all strings have the same length, you can also leave the last part at .ThenBy(p => p), as the strings are already sorted by the last character. If string lengths differ, you need the substring as in my code though.
If it's possible for the strings to have different lengths then the following would be needed.
var result = data.OrderBy(d => d[d.Length - 1])
.ThenBy(d => int.Parse(d.Substring(0, d.Length - 1])));
You'd of course need to guard against possible parsing exceptions with bad data.
This assumes that you'd want "200A" to come before "1000A".
version a) (fastest)
Use built in Sort method (sorts in place), with a custom Comparision delegate/lambda
dummy.Sort((s1, s2) =>
{
// TODO: Handle null values, now supposing s1 and s2 are not null
// TODO: Handle variable length if needed. Supposing fixed 4+1 data
var result = s1[4].CompareTo(s2[4]);
if (result != 0)
{
return result;
}
return s1.Substring(0, 4).CompareTo(s2.Substring(0, 4));
});
To reuse the Comparision you can write it as a static method instead of an inline lambda, however this case I recommend to implement an IComparator instead. (Sort method has an overload which accepts IComparator)
version b):
Use LINQ:
// TODO: Handle variable length if needed, supposing fixed 4+1 data structure:
var orderedList = dummy.OrderBy(s => s[4]).ThenBy(s => s.SubString(0,4).ToList();
A solution based on grouping:
var res = Dummy.GroupBy(str => str.Last()).OrderBy(g => g.Key)
.SelectMany(g => g.OrderBy(str => str.Substring(0, str.Length - 1)))
.ToList();

lambda expressions for comparing whether or not a string[] contains a given string

My project was working just fine until I had to account for an array of strings and not just one... I don't know how to fix this. The Country is a property of the current class this method is in. It used to be a single string but now is an array.
Originally it looked like this:
private Expression<Func<Payment, bool>> CountryMatches()
{
if (Country.Length < 1) return Skip;
return payment => payment.Country.ToLower().Contains(Country.ToLower());
}
What I can't figure out is how to set it up so that if ANY of the strings in Country match payment.Country... and of course this is passing back an expression... This is my best guess (but obviously not correct) of how to do what I need to do:
private Expression<Func<Payment, bool>> CountryMatches()
{
if (Country.Length < 1) return Skip;
return payment => payment.Country.ToLower() == Country.Any().ToLower();
}
You want to check all the contents of Country against payment.Country, like this:
return payment => Country.Any(
c => payment.Country.ToLower().Contains(c.ToLower()));
That said, this is a rather bad way to check if one string is a substring of another mainly because it does a lot of unnecessary work by converting to lowercase again and again. Here's a better way to do it:
return payment => Country.Any(
c => payment.Country.IndexOf(c, StringComparison.OrdinalIgnoreCase) >= 0);
.Any Takes a lambda in, which can perform your comparison. The above code assums it returns some object which equates to true when compared, but Any returns a simple bool. Thus, try
return payment =>
Country.Any(c =>
string..Equals(payment.Country, c, StringComparison.InvariantCultureIgnoreCase));
Also note that .Equals is often preferable to the "==" operator, particularly with strings, wherin you can pass the StringComparison parameter.

c# contains case insensitive search

I have the following code
var returnData = DemoData.Books.AsQueryable();
if (criteria.Author != string.Empty)
{
returnData = returnData.Where(x => x.Author.Contains(criteria.Author));
}
How do I made the where clause case insensitive?
You can use ToLower() function. ToLower changes strings to be all lowercase. It converts an entire string—without changing letters that are already lowercased or digits. It copies a string and returns a reference to the new string. So it is always better option to declare criteria.Author.ToLower() outside the query.
string lowerAuthor = criteria.Author.ToLower();
returnData = returnData.Where
(x => x.Author.ToLower().Contains(lowerAuthor));
You could also use IndexOfoverload with the StringComparison enum. It would give you better performance than ToLower(). The signature of this overload is:
int string.IndexOf(string value, StringComparison comparisonType);
returnData = returnData.Where
(x => x.Author.IndexOf(criteria.Author, StringComparison.CurrentCultureIgnoreCase) != -1);
returnData = returnData.Where
(x => x.Author.IndexOf(criteria.Author, StringComparison.CurrentCultureIgnoreCase) != -1)
It will make no additional string allocation at all.
I assumed it's LINQ to Objects query.

How to Compare Values in Array

If you have a string of "1,2,3,1,5,7" you can put this in an array or hash table or whatever is deemed best.
How do you determine that all value are the same? In the above example it would fail but if you had "1,1,1" that would be true.
This can be done nicely using lambda expressions.
For an array, named arr:
var allSame = Array.TrueForAll(arr, x => x == arr[0]);
For an list (List<T>), named lst:
var allSame = lst.TrueForAll(x => x == lst[0]);
And for an iterable (IEnumerable<T>), named col:
var first = col.First();
var allSame = col.All(x => x == first);
Note that these methods don't handle empty arrays/lists/iterables however. Such support would be trivial to add however.
Iterate through each value, store the first value in a variable and compare the rest of the array to that variable. The instant one fails, you know all the values are not the same.
How about something like...
string numArray = "1,1,1,1,1";
return numArrray.Split( ',' ).Distinct().Count() <= 1;
I think using List<T>.TrueForAll would be a slick approach.
http://msdn.microsoft.com/en-us/library/kdxe4x4w.aspx
Not as efficient as a simple loop (as it always processes all items even if the result could be determined sooner), but:
if (new HashSet<string>(numbers.Split(',')).Count == 1) ...

Categories

Resources