complex fetch with linq - c#

My task: You are given a sequence of positive numbers and a sequence of strings stringList. Get a new sequence of strings according to the following rule: for each value n from the sequence numbers, select a string from the sequence stringList that starts with a digit and has length n. If there are several required strings in the stringList sequence, return the first; if there are none, then return the string "Not found" (To handle the situation related to the absence of required strings, use the ?? operation)
everything needs to be done in one line via linq
I tried this: numbers.Select(x => stringList.First(y => char.IsDigit(y.First()) && y.Length == x) ?? "Not found");
but when compiling errors take off

Try this one:
numbers.Select(n => stringList.FirstOrDefault(y => y.Length == n && y.Length > 0 && char.IsDigit(y[0])) ?? "Not found");

Related

How to find index of a value in array of char?

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.

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();

How do I check if a number string is in running sequence

Is there a regex (or any other way) to check if a numbers in a string is in running sequence?
E.g.,
"123456" will return true
"456789" will return true
"345678" will return true
"123467" will return false
"901234" will return false
If all your sequences are composed of single-digit numbers, then you can solve this by observing that all correct sequences must be substrings of the longest such sequence, i.e. of "0123456789". So the check can be done like this:
bool res = "0123456789".Contains(str);
Demo on ideone.
How about this:
text.Skip(1).Zip(text, (c1, c0) => new { c1, c0 }).All(c => c.c1 - c.c0 == 1)

Filtering lambda expression that compares two elements of the same collection

I was wondering if it is possible to write an expression for a Linq extension (or a custom extension) to filter a collection using a lambda expression that compares two elements of the collection.
In other words, if I have a List<DateTime> and some value, var v = DateTime.Today, then I am wondering if it is possible to write create a method that will return the first element of the collection that is less than or equal to the value, current <= v, with the next element of the collection being greater than or equal to the value, next >= v.
Please note that the above is just an example, and may or may not be the final implementation.
The following would be a valid solution, were the .First() method to accept Func<DateTime, DateTime, bool> with the two DateTime parameters being consecutive elements of the sequence:
dateCollection.First((current, next) => current <= v && next >= v);
Please also note that with the example given, a valid workaround could be to use .OrderBy and then find the first index that is greater than d and subtract 1. However, this type of comparison is not the only one that I am looking for. I may have a situation in which I am checking a List<string> for the first situation where the current element starts with the first letter of my value, v, and the next element ends with the last letter of my value, v.
I am looking for something that would be just a few of code. My goal is to find the simplest solution to this possible, so brevity carries a lot of weight.
What I am looking for is something of the form:
public static T First (...)
{
...
}
I believe that this will also require two or more lambda expressions as parameters. One thing that may also provide a good solution is to be able to select into all possible, consecutive pairs of elements of the sequence, and call the .First() method on that.
For example:
//value
var v = 5;
//if my collection is the following
List<int> stuff = { a, b, c, d };
//select into consecutive pairs, giving:
var pairs = ... // { { a, b }, { b, c }, { c, d } };
//then run comparison
pairs.First(p => p[0] <= v && p[1] >= v).Select(p => p[0]);
Thanks and happy coding! :)
What we can create is a Pairwise method that can map a sequence of values into a sequence of pairs representing each value and the value that comes before it.
public static IEnumerable<Tuple<T, T>> Pairwise<T>(this IEnumerable<T> source)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
T prev = iterator.Current;
while (iterator.MoveNext())
{
yield return Tuple.Create(prev, iterator.Current);
prev = iterator.Current;
}
}
}
Now we can write out:
var item = data.Pairwise()
.First(pair => pair.Item1 <= v && pair.Item2 >= v)
.Item1;
If this is something you're going to use a fair bit, it may be worth creating a new custom type to replace Tuple, so that you can have Current and Next properties, instead of Item1 and Item2.
List<int> list = new List<int>();
list.Add(3);
list.Add(2);
list.Add(8);
list.Add(1);
list.Add(4);
var element = list
.Where((elem, idx) => idx < list.Count-1 && elem<=list[idx+1])
.FirstOrDefault();
Console.WriteLine(element);
rsult: 2
where 'elem' is the current element, and 'idx' is an index of the current element
Not really sure what you want to return here is my main question, but to take your example and put it into a LINQ statement, it would be like this:
DateTime? Firstmatch = dateCollection.DefaultIfEmpty(null).FirstOrDefault(a => a <= d && ((dateCollection.IndexOf(a) + 1) < (dateCollection.Count) && dateCollection[dateCollection.IndexOf(a) + 1] >= d));
Strictly following the description, you could combine linq and list indexes to find the first index that matches your criterium, and returning its element:
DateTime d= DateTime.Today;
var res = dateCollection[Enumerable.Range(0, dateCollection.Count - 1).First(i => dateCollection[i] <= d && dateCollection[i + 1] >= d)];
Servy's answer can be handled without an extension method:
var first = items
.Select((current, index) => index > 0 ? new { Prev = items.ElementAt(index-1), Current = current } : null)
.First(pair => pair != null && pair.Prev <= v && pair.Current >= v)
.Prev;

Categories

Resources