I have a list containing integer or string-integer
like this
TagNo FTerminal
1000 1
1000 5
1000 2S6
how can i get the result like this
TagNo FTerminal
1000 1
5
6
I have this , but definately it gives me error on 2s6.
how can i change it to cover all?
var terminalList = sourceLists.Where(t => t.TagNo == tagList)
.Where(t=>t.FromTerminal.Length>0)
.Select(t => int.Parse(t.FromTerminal))
.OrderBy(t=>t)
.ToList();
Instead of using int.Parse in your LINQ statement, you need to write your own function.
Something like this:
int parseTerminal(string input) {
int result = -1;
if (!int.TryParse(input, out result)) {
result = -99;
}
return result;
}
That would make your LINQ to
var terminalList = sourceLists
.Where( t => t.TagNo == tagList && t.FromTerminal.Length > 0 )
.Select( t => parseTerminak(t.FromTerminal) )
.OrderBy( t=>t )
.ToList();
Result:
TagNo FTerminal
1000 -99
1
5
You need to handle the special case where FromTerminal is not a number yourself.
A naive implementation of the requirement one could think of is something like this:
int parseTerminal(string input) {
int result = -1;
if (!int.TryParse(input, out result)) {
var temporaryString = string.Empty;
var lastInt = -1;
input.ToList().ForEach( aChar => {
if ( aChar >= '0' && aChar <= '9' ) {
temporaryString += aChar;
} else {
if ( temporaryString.Length >= 0 ) {
int.TryParse( temporaryString, out lastInt );
temporaryString = string.Empty;
}
}
} );
if ( temporaryString.Length >= 0 ) {
if (!int.TryParse( temporaryString, out lastInt )) {
lastInt = -98;
}
}
result = lastInt;
}
return result;
}
Note: I would not consider this production ready and you should think about edge cases.
Without knowing much about your data structure I have written a code using some system types.
var tuples = new List<Tuple<int, string>>
{
new Tuple<int, string>(1000, "1"),
new Tuple<int, string>(1000, "5"),
new Tuple<int, string>(1000,"2s6")
};
var enumerable = tuples.GroupBy(t => t.Item1).
Select(g => new Tuple<int, List<int>>(g.Key, g.Select(e => int.Parse(Regex.Match(e.Item2, #"(?<=(\D|^))\d+(?=\D*$)").Value)).ToList()));
The error occurs because you try to handle the string '2S6' as an integer, using int.Parse. This would naturally cause an exception.
I would suggest following another approach, using regular expressions. I think regular expressions are better solution since the data you ask come after string manipulation of the already retrieved query results.
Using regular expressions to do this kind of staff, would make it also easier for you to maintain in the future. Think of the case, that in a week you don't want to retrieve the last digit of the string, but the second digit of the string.
You can use this online regular expression tester to test your regular expression. I suppose the regular expression \d(?!.*\d) would be a good choice, since it returns the last digit.
This article is a good guide in using regular expressions in .NET, including examples.
Hope I helped!
if last symbol always is int you may change your code like this
var terminalList = sourceLists.Where(t => t.TagNo == tagList)
.Where(t=>t.FromTerminal.Length>0)
.Select(t => int.Parse(t.FromTerminal.Last()))
.OrderBy(t=>t)
.ToList();
UPDATE
if last not only one numer that can use regex like this
var terminalList = sourceLists.Where(t => t.TagNo == tagList)
.Where(t=>t.FromTerminal.Length>0)
.Select(t => int.Parse(Regex.Match(t.FromTerminal, #"(\d+)$").Groups[1].Value))
.OrderBy(t=>t)
.ToList();
I am quiet confused what do you wants ... as your picture says you wants the combination of TagNO and FTerminal and in the other hand your query says you wants only FTerminals in certain order ..
Now if you wants the first one then
void Abc(int tagList)
{
var sourceLists = new List<Demo>
{
new Demo { FTerminal = "200", TagNo = 1000 },
new Demo { FTerminal = "300", TagNo = 1000 },
new Demo { FTerminal = "400", TagNo = 1000 }
};
var terminalList = sourceLists
.Where(t => t.TagNo == tagList && t.FTerminal.Length > 0)
.OrderBy(i=>i.FTerminal).GroupBy(i=>i.TagNo);
}
And the second one
void Abc(int tagList)
{
var sourceLists = new List<Demo>
{
new Demo { FTerminal = "200", TagNo = 1000 },
new Demo { FTerminal = "300", TagNo = 1000 },
new Demo { FTerminal = "400", TagNo = 1000 }
};
var terminalList =
from Demo d in sourceLists
where d.TagNo == tagList
let number = int.Parse(d.FTerminal)
orderby number ascending
select number).ToList();
}
But till if you did not get the your desired answer then please knock!!!!
Hmm ... instead of jumping through hoops, just use IsInt ... problem solved ...
:)
var terminalList = sourceLists.Where(t => t.TagNo == tagList)
.Where(t=>t.FromTerminal.Length>0)
.Where(t => t.FromTerminal.IsInt() )
.Select(t => int.Parse(t.FromTerminal))
.OrderBy(t=>t)
.ToList();
(So, just added this condition .Where(t => t.FromTerminal.IsInt() ) to your selection process)
Related
I have Licence plate numbers which I return to UI and I want them ordered in asc order:
So let's say the input is as below:
1/12/13/2
1/12/11/3
1/12/12/2
1/12/12/1
My expected output is:
1/12/11/3
1/12/12/1
1/12/12/2
1/12/13/2
My current code which is working to do this is:
var orderedData = allLicenceNumbers
.OrderBy(x => x.LicenceNumber.Length)
.ThenBy(x => x.LicenceNumber)
.ToList();
However for another input sample as below:
4/032/004/2
4/032/004/9
4/032/004/3/A
4/032/004/3/B
4/032/004/11
I am getting the data returned as:
4/032/004/2
4/032/004/9
4/032/004/11
4/032/004/3/A
4/032/004/3/B
when what I need is:
4/032/004/2
4/032/004/3/A
4/032/004/3/B
4/032/004/9
4/032/004/11
Is there a better way I can order this simply to give correct result in both sample inputs or will I need to write a custom sort?
EDIT
It wont always be the same element on the string.
This could be example input:
2/3/5/1/A
1/4/6/7
1/3/8/9/B
1/3/8/9/A
1/5/6/7
Expected output would be:
1/3/8/9/A
1/3/8/9/B
1/4/6/7
1/5/6/7
2/3/5/1/A
You should split your numbers and compare each part with each other. Compare numbers by value and strings lexicographically.
var licenceNumbers = new[]
{
"4/032/004/2",
"4/032/004/9",
"4/032/004/3",
"4/032/004/3/A",
"4/032/004/3/B",
"4/032/004/11"
};
var ordered = licenceNumbers
.Select(n => n.Split(new[] { '/' }))
.OrderBy(t => t, new LicenceNumberComparer())
.Select(t => String.Join("/", t));
Using the following comparer:
public class LicenceNumberComparer: IComparer<string[]>
{
public int Compare(string[] a, string[] b)
{
var len = Math.Min(a.Length, b.Length);
for(var i = 0; i < len; i++)
{
var aIsNum = int.TryParse(a[i], out int aNum);
var bIsNum = int.TryParse(b[i], out int bNum);
if (aIsNum && bIsNum)
{
if (aNum != bNum)
{
return aNum - bNum;
}
}
else
{
var strCompare = String.Compare(a[i], b[i]);
if (strCompare != 0)
{
return strCompare;
}
}
}
return a.Length - b.Length;
}
}
If we can assume that
Number plate constist of several (one or more) parts separated by '/', e.g. 4, 032, 004, 2
Each part is not longer than some constant value (3 in the code below)
Each part consist of either digits (e.g. 4, 032) or non-digits (e.g. A, B)
We can just PadLeft each number plate's digit part with 0 in order to compare not "3" and "11" (and get "3" > "11") but padded "003" < "011":
var source = new string[] {
"4/032/004/2",
"4/032/004/9",
"4/032/004/3/A",
"4/032/004/3/B",
"4/032/004/11",
};
var ordered = source
.OrderBy(item => string.Concat(item
.Split('/') // for each part
.Select(part => part.All(char.IsDigit) // we either
? part.PadLeft(3, '0') // Pad digit parts e.g. 3 -> 003, 11 -> 011
: part))); // ..or leave it as is
Console.WriteLine(string.Join(Environment.NewLine, ordered));
Outcome:
4/032/004/2
4/032/004/3/A
4/032/004/3/B
4/032/004/9
4/032/004/11
You seem to be wanting to sort on the fourth element of the string (delimited by /) in numeric rather than string mode.. ?
You can make a lambda more involved/multi-statement by putting it like any other method code block, in { }
var orderedData = allLicenceNumbers
.OrderBy(x =>
{
var t = x.Split('/');
if(t.Length<4)
return -1;
else{
int o = -1;
int.TryParse(t[3], out o);
return o;
}
)
.ToList();
If you're after sorting on more elements of the string, you might want to look at some alternative logic, perhaps if the first part of the string will always be in the form N/NNN/NNN/??/?, then do:
var orderedData = allLicenceNumbers
.OrderBy(w => w.Remove(9)) //the first 9 are always in the form N/NNN/NNN
.ThenBy(x => //then there's maybe a number that should be parsed
{
var t = x.Split('/');
if(t.Length<4)
return -1;
else{
int o = -1;
int.TryParse(t[3], out o);
return o;
}
)
.ThenBy(y => y.Substring(y.LastIndexOf('/'))) //then there's maybe A or B..
.ToList();
Ultimately, it seems that more and more outliers will be thrown into the mix, so you're just going to have to keep inventing rules to sort with..
Either that or change your strings to standardize everything (int an NNN/NNN/NNN/NNN/NNA format for example), and then sort as strings..
var orderedData = allLicenceNumbers
.OrderBy(x =>
{
var t = x.Split('/');
for(int i = 0; i < t.Length; i++) //make all elements in the form NNN
{
t[i] = "000" + t[i];
t[i] = t[i].Substring(t[i].Length - 3);
}
return string.Join(t, "/");
}
)
.ToList();
Mmm.. nasty!
Please have a look in the below code, I need an output in OrderedListDesc = {7,6,5,4,1,2,3,8,9} instead of {4,5,6,7,1,2,3,8,9}.
List<long> List = new List<long>() { 1,2,4,5,3,8,6,7,9 };
List<long> ListAsc = new List<long>() { 4,5,6,7 };
List<long> ListDesc = new List<long>() { 7,6,5,4 };
var OrderedListAsc = List.OrderBy(b => ListAsc.FindIndex(a => a == b)).ToList();
foreach (var l in OrderedListAsc)
{
Console.Write(l+" ,");
}
Console.WriteLine();
var OrderedListDesc = List.OrderByDescending(b => ListDesc.FindIndex(a => a == b)).ToList();
foreach (var l in OrderedListDesc)
{
Console.Write(l + " ,");
}
It is really simple if you think about it:
The order of the elements found in ListDesc should be the number itself, then you got your result:
var OrderedListDesc = List.OrderByDescending(b => ListDesc.Any(a => a == b) ? b : 0).ToList();
foreach (var l in OrderedListDesc)
{
Console.Write(l + " ,");
}
If you want to see what's happening, that is, why you're getting things in the wrong order, run this:
foreach (var i in List)
{
Console.WriteLine("{0}, {1}", i, ListDesc.FindIndex(a => a == i));
}
There's no need for ListDesc anyway. Just use ListAsc:
var OrderedListDesc = List.OrderByDescending(b => ListAsc.FindIndex(a => a == b)).ToList();
Or, use ListDesc and call OrderBy rather than OrderByDescending:
var OrderedListDesc = List.OrderBy(b => ListDesc.FindIndex(a => a == b)).ToList();
If you notice the problem is, when an element(value) not found FindIndex returns -1, which will appear first in order. Assign the maximum value when element is not found.
var OrderedListDesc = List.OrderBy(b =>
{
var index = ListDesc.FindIndex(a => a == b);
return index==-1? int.MaxValue : index;
}).ToList();
A small tip (not relating to issue), if you want to print , separated values you could simply use string.Join as below.
Console.WriteLine(string.Join(",", OrderedListDesc));
Output:
7 ,6 ,5 ,4 ,1 ,2 ,3 ,8 ,9 ,
Check this Fiddle
Ok. The title might be a little confusing but here is what I am trying to do
I have a series of natural numbers
var series = Enumerable.Range(1, 100)
Now I want to use GroupBy to put numbers into these 3 groups, Prime, Even, Odd
series.Select(number => {
var type = "";
if (MyStaticMethods.IsPrime(number))
{
Type = "prime";
}
else if (number % 2 == 0)
{
type = "Even";
}
else
{
type = "Odd";
}
return new { Type=type, Number = number };
}).GroupBy(n => n.Type);
Now the above query will miss categorizing Prime numbers that are even or odd into both categories and they will just be in 'prime' group. Is there any way for the above select to yield multiple numbers?
I could try something like the following, but it requires an additional flattening of the sequence.
series.Select(number => {
var list = new List<int>();
if (MyStaticMethods.IsPrime(number))
{
list.Add(new { Type="prime", Number = number });
}
if (number % 2 == 0)
{
list.Add(new { Type="even", Number = number });
}
else
{
list.Add(new { Type="odd", Number = number });
}
return list;
})
.SelectMany(n => n)
.GroupBy(n => n.Type);
The above code solves my issue, is there any better way that could make my code look more "functional" ?
You can use linq here, but you'll need to duplicate some values that can exist in different groups. GroupBy only works for disjoint groups so you need a way to distinguish 2 the even number and 2 the prime number. The approach you did is essentially what you need to do, but it could be done a little more efficiently.
You can define a set of categories that can help classify the numbers. You don't necessarily need to define new classes to get this to work, but it helps to keep things clean and organized.
class Category<T>
{
public Category(string name, Predicate<T> predicate)
{
Name = name;
Predicate = predicate;
}
public string Name { get; }
public Predicate<T> Predicate { get; }
}
Then to group the numbers, you'd do this:
var series = Enumerable.Range(1, 100);
var categories = new[]
{
new Category<int>("Prime", i => MyStaticMethods.IsPrime(i)),
new Category<int>("Odd", i => i % 2 != 0),
new Category<int>("Even", i => i % 2 == 0),
};
var grouped =
from i in series
from c in categories
where c.Predicate(i)
group i by c.Name;
This is a good case to use Reactive Extensions, as you will avoid to duplicate values.
In the code below , "series" is parsed only once, because it's a hot source thanks to the Publish().
The actual parsing is done during the "Connect()".
using System.Reactive.Linq;
var list = new List<KeyValuePair<string, int>>();
var series= Observable.Range(1, 100).Publish();
series.Where(e => e % 2 == 0).Subscribe(e=>list.Add(new KeyValuePair<string, int>("Even",e)));
series.Where(e => e % 2 == 1).Subscribe(e => list.Add(new KeyValuePair<string, int>("Odd", e)));
series.Where(e => MyStaticMethods.IsPrime(e) ).Subscribe(e => list.Add(new KeyValuePair<string, int>("Prime", e)));
series.Connect();
var result = list.GroupBy(n => n.Key);
How can I perform a conditional select on a column value, where I have a preference over which value is returned. If I can't find the top choice, I settle on the next, if available, and then if not the next, etc. As it looks right now, it would take 3 total queries. Is there a way to simplify this further?
var myResult = string.Empty;
if (myTable.Where(x => x.ColumnValue == "Three").Any())
{
myResult = "Three"; // Can also be some list.First().Select(x => x.ColumnValue) if that makes it easier;
}
else if (myTable.Where(x => x.ColumnValue == "One").Any())
{
myResult = "One";
}
else if (myTable.Where(x => x.ColumnValue == "Two").Any())
{
myResult = "Two";
}
else
{
myResult = "Four";
}
You could use a string[] for your preferences:
string[] prefs = new[]{ "One", "Two", "Three" };
string myResult = prefs.FirstOrDefault(p => myTable.Any(x => x.ColumnValue == p));
if(myResult == null) myResult = "Four";
Edit Enumerable.Join is a very efficient hash table method, it also needs only one query:
string myResult = prefs.Select((pref, index) => new { pref, index })
.Join(myTable, xPref => xPref.pref, x => x.ColumnValue, (xPref, x) => new { xPref, x })
.OrderBy(x => x.xPref.index)
.Select(x => x.x.ColumnValue)
.DefaultIfEmpty("Four")
.First();
Demo
I wrote an extension method that effectively mirrors Tim Schmelter's answer (was testing this when he posted his update. :-()
public static T PreferredFirst<T>(this IEnumerable<T> data, IEnumerable<T> queryValues, T whenNone)
{
var matched = from d in data
join v in queryValues.Select((value,idx) => new {value, idx}) on d equals v.value
orderby v.idx
select new { d, v.idx };
var found = matched.FirstOrDefault();
return found != null ? found.d : whenNone;
}
// usage:
myResult = myTable.Select(x => x.ColumnValue)
.PreferredFirst(new [] {"Three", "One", "Two"}, "Four");
I've written one that will quit a little more early:
public static T PreferredFirst<T>(this IEnumerable<T> data, IList<T> orderBy, T whenNone)
{
// probably should consider a copy of orderBy if it can vary during runtime
var minIndex = int.MaxValue;
foreach(var d in data)
{
var idx = orderBy.IndexOf(d);
if (idx == 0) return d; // best case; quit now
if (idx > 0 && idx < minIndex) minIndex = idx;
}
// return the best found or "whenNone"
return minIndex == int.MaxValue ? whenNone : orderBy[minIndex];
}
I use a weighted approach in SQL where I assign a weight to each conditional value. The solution would then be found by finding the highest or lowest weight depending on your ordering scheme.
Below would be the equivalent LINQ query. Note that in this example I am assigning a lower weight a higher priority:
void Main()
{
// Assume below list is your dataset
var myList =new List<dynamic>(new []{
new {ColumnKey=1, ColumnValue ="Two"},
new {ColumnKey=2, ColumnValue ="Nine"},
new {ColumnKey=3, ColumnValue ="One"},
new {ColumnKey=4, ColumnValue ="Eight"}});
var result = myList.Select(p => new
{
ColVal = p.ColumnValue,
OrderKey = p.ColumnValue == "Three" ? 1 :
p.ColumnValue == "One" ? 2 :
p.ColumnValue == "Two" ? 3 : 4
}).Where(i=> i.OrderKey != 4)
.OrderBy(i=>i.OrderKey)
.Select(i=> i.ColVal)
.FirstOrDefault();
Console.WriteLine(result ?? "Four");
}
How about something like this:
var results = myTable.GroupBy(x => x.ColumnValue).ToList();
if (results.Contains("Three")) {
myResult = "Three";
} else if (results.Contains("One")) {
myResult = "One";
} else if (results.Contains("Two")) {
myResult = "Two";
} else {
myResult = "Four";
}
I am trying to use LINQ to return the an element which occurs maximum number of times AND the number of times it occurs.
For example:
I have an array of strings:
string[] words = { "cherry", "apple", "blueberry", "cherry", "cherry", "blueberry" };
//...
Some LINQ statement here
//...
In this array, the query would return cherry as the maximum occurred element, and 3 as the number of times it occurred. I would also be willing to split them into two queries if that is necessary (i.e., first query to get the cherry, and second to return the count of 3.
The solutions presented so far are O(n log n). Here's an O(n) solution:
var max = words.GroupBy(w => w)
.Select(g => new { Word = g.Key, Count = g.Count() })
.MaxBy(g => g.Count);
Console.WriteLine(
"The most frequent word is {0}, and its frequency is {1}.",
max.Word,
max.Count
);
This needs a definition of MaxBy. Here is one:
public static TSource MaxBy<TSource>(
this IEnumerable<TSource> source,
Func<TSource, IComparable> projectionToComparable
) {
using (var e = source.GetEnumerator()) {
if (!e.MoveNext()) {
throw new InvalidOperationException("Sequence is empty.");
}
TSource max = e.Current;
IComparable maxProjection = projectionToComparable(e.Current);
while (e.MoveNext()) {
IComparable currentProjection = projectionToComparable(e.Current);
if (currentProjection.CompareTo(maxProjection) > 0) {
max = e.Current;
maxProjection = currentProjection;
}
}
return max;
}
}
var topWordGroup = words.GroupBy(word => word).OrderByDescending(group => group.Count()).FirstOrDefault();
// topWordGroup might be a null!
string topWord = topWordGroup.Key;
int topWordCount = topWordGroup.Count;
And in case if we don't like O(N log N):
var topWordGroup = words.GroupBy(word => word).Aggregate((current, acc) => current.Count() < acc.Count() ? acc : current);
First thing that comes to mind (meaning there is probably a more efficient way)
var item = words.GroupBy(x => x).OrderByDescending(x => x.Count()).First()
//item.Key is "cherry", item.Count() is 3
EDIT: forgot op wanted the name and the count
string[] words = { "cherry", "apple", "blueberry", "cherry", "cherry", "blueberry" };
var topWordAndCount=words
.GroupBy(w=>w)
.OrderByDescending(g=>g.Count())
.Select(g=>new {Word=g.Key,Count=g.Count()})
.FirstOrDefault();
//if(topWordAndCount!=null)
//{
// topWordAndCount.Word
// topWordAndCount.Count
Try this one:
Converting SQL containing top, count, group and order to LINQ (2 Entities)
string[] words = { "cherry", "apple", "blueberry", "cherry", "cherry", "blueberry" };
var r = words.GroupBy (x => x)
.OrderByDescending (g => g.Count ())
.FirstOrDefault ();
Console.WriteLine (String.Format ("The element {0} occurs {1} times.", r.Key, r.Count ()));
A simpler O(n) solution:
var groups = words.GroupBy(x => x);
var max = groups.Max(x => x.Count());
var top = groups.First(y => y.Count() == max).Key;
Here's a very fast O(n) solution in one line(!):
s.GroupBy(x => x).Aggregate((IGrouping<string,string>)null, (x, y) => (x != null && y != null && x.Count() >= y.Count()) || y == null ? x : y, x => x);
Or this:
s.GroupBy(x => x).Select(x => new { Key = x.Key, Count = x.Count() }).Aggregate(new { Key = "", Count = 0 }, (x, y) => x.Count >= y.Count ? x : y, x => x);