Related
I have this string:
string input = "1,2,3,4,s,6";
Pay attention to the s character.
I just want to convert this string in a List<int> using LINQ. I initially tried in this way:
var myList = new List<int>();
input.Split(',').ToList().ForEach(n =>
myList.Add(int.TryParse(n, out int num) ? num : -1)
);
lista.RemoveAll(e => e == -1);
But I prefer not have any -1 instead of a no-number characters.
So now I try with this:
var myList = new List<int>();
input.Split(',').ToList()
.FindAll(n => int.TryParse(n, out int _))
.ForEach(num => myList.Add(int.Parse(num)));
I prefer this, but is really a shame that the parsing happening two times (TryParse at first and then Parse). But, from what I understand, the out variable in TryParse is useless (or not?).
Have you others suggests (using LINQ)?
public class ParsesStringsToIntsWithLinq
{
public IEnumerable<int> Parse(string input)
{
var i = 0;
return (from segment in input.Split(',')
where int.TryParse(segment, out i)
select i);
}
}
[TestClass]
public class Tests
{
[TestMethod]
public void IgnoresNonIntegers()
{
var input = "1,2,3,4,s,6";
var output = new ParsesStringsToIntsWithLinq().Parse(input);
Assert.IsTrue(output.SequenceEqual(new []{1,2,3,4,6}));
}
}
It doesn't return a List<int> but I have to draw the line somewhere. You can make a list out of it.
Using a nice extension method
public static IEnumerable<T> AsSingleton<T>(this T source) {
yield return source;
}
(which you can replace with new[] { n } if preferred)
input.Split(',').SelectMany(s => Int32.TryParse(s, out var n) ? n.AsSingleton() : Enumerable.Empty<int>()).ToList()
I prefer to make a nice helper function:
Func<string, int?> tryParse = s => int.TryParse(s, out int n) ? (int?)n : null;
Then it's a simple matter to parse:
string input = "1,2,3,4,s,6";
List<int> myList =
input
.Split(',')
.Select(s => tryParse(s))
.Where(n => n.HasValue)
.Select(n => n.Value)
.ToList();
That gives:
1
2
3
4
6
int i = 0;
var myList = (from s in input.Split(',') where int.TryParse(s, out i) select i).ToList();
If the numbers are always single ASCII digits:
var myList = "1,2,3,4,s,6".Select(c => c ^ 48).Where(i => i < 10).ToList();
Few slower RegEx alternatives for fun:
var myList2 = Regex.Split("1,2,3,4,s,6", "[^0-9]+").Select(int.Parse).ToList(); // if the string starts and ends with digits
var myList3 = Regex.Replace("1,2,3,4,s,6", "[^0-9]+", " ").Trim().Split(' ').Select(int.Parse).ToList();
var myList4 = Regex.Matches("1,2,3,4,s,6", "[0-9]+").Cast<Match>().Select(m => int.Parse(m.Value)).ToList();
Why does it have to be LINQ?
Try:
//Come up a better name...
public static List<int> ConvertToIntListNoLinq(string input)
{
List<int> output = new List<int>();
foreach(string s in input.Split(','))
{
if(int.TryParse(s, out int result))
{
output.Add(result);
}
}
return output;
}
Fiddle
Here's a generic LINQ extension, which utilizes a delegate. This will allow you to pass in a function returning a bool, while "retaining" the result of the out variable (like int.TryParse).
Usage:
string input = "1,2,3,4,s,6";
List<int> myList = input.Split(',').SelectTry<string, int>(int.TryParse).ToList();
Code:
using System.Collections.Generic;
public static class LINQExtensions
{
public delegate bool TryFunc<TSource, TResult>(TSource source, out TResult result);
public static IEnumerable<TResult> SelectTry<TSource, TResult>(
this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
{
foreach (TSource item in source)
{
TResult result;
if (selector(item, out result))
{
yield return result;
}
}
}
}
I think this is a clean way too. Even though it uses that extra variable, the benefit we get is it is clean and understandable.
string ids = "2,4,2,4,5,s"
const int inValidInt = -99;
var ids = ids.Split(',')
.Select(id =>
{
int parsedId = int.TryParse(id, out parsedId) ? parsedId : inValidInt;
return parsedId;
})
.Where(x => x != inValidInt).ToList();
You can do it like this:
List<int> numbers = input
.Split(',')
.Where(t => int.TryParse(t, out int a))
.Select(int.Parse)
.ToList();
You don't need to call .Split(...).ToList() as String[] is already enumerable.
You can use multiple statements in a lambda with braces.
The FindAll, ForEach and RemoveAll methods are not Linq methods, they're members of List<T>. Their Linq equivalent is Where.
Like so:
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',')
.Select( s => { Int32 val; return Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out val ) ? val : -1 } )
.Where( n => n != -1 )
.ToList();
You can make it more concise with a helper method:
static Int32 Parse(String s) {
Int32 ret;
if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
return ret;
}
return -1;
}
Becomes:
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',')
.Select( s => Parse( s ) )
.Where( n => n != -1 )
.ToList();
If you don't want to reserve -1 then you can use nullable ints:
static Int32? Parse(String s) {
Int32 ret;
if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
return ret;
}
return null;
}
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',') // String to String[]
.Select( s => Parse( s ) ) // String[] to IEnumerable<Int32?>
.Where( n => n != null ) // filter out nulls
.Select( n => n.Value ) // IEnumerable<Int32?> to IEnumerable<Int32>
.ToList(); // IEnumerable<Int32> to List<Int32>
I have the following unsorted list:
List<string> myUnsortedList = New List<string>();
myUnsortedList.Add("Alpha");
myUnsortedList.Add("(avg) Alpha");
myUnsortedList.Add("Zeta");
myUnsortedList.Add("Beta");
myUnsortedList.Add("(avg) Beta");
myUnsortedList.Add("(avg) Zeta");
I want to sort the list descending alphabetical order, then have the value with (avg) right after the normal value:
Final Result: Zeta, (avg) Zeta, Beta, (avg) Beta, Alpha, (avg) Alpha
My application is written in C# and I want to use LINQ to accomplish the sorting
This should work ok for what you need, assuming "(avg)" is the only special prefix
This will order all the stings descending not including the "(avg) " then it will order by the strings length this way the string with the "(avg)" prefix will come after the one without
var result = myUnsortedList.OrderByDescending(x => x.Replace("(avg) ", "")).ThenBy(x => x.Length);
Final Result:
Zeta
(avg) Zeta
Beta
(avg) Beta
Alpha
(avg) Alpha
Here are a couple of ways to pull this off with LINQ, while also correctly sorting the values should they occur in an order other than the one you've presented. For example, if "(avg) Zeta" occurs before "Zeta" then the latter should still come first once sorted.
Here's the sample list, reordered to match what I described above:
var myUnsortedList = new List<string>
{
"Alpha",
"(avg) Alpha",
"(avg) Zeta",
"Zeta",
"Beta",
"(avg) Beta"
};
Lambda syntax
string prefix = "(avg)";
var result = myUnsortedList.Select(s => new
{
Value = s,
Modified = s.Replace(prefix, "").TrimStart(),
HasPrefix = s.StartsWith(prefix)
})
.OrderByDescending(o => o.Modified)
.ThenBy(o => o.HasPrefix)
.Select(o => o.Value);
Zip / Aggregate
string prefix = "(avg)";
var avg = myUnsortedList.Where(o => o.StartsWith(prefix))
.OrderByDescending(o => o);
var regular = myUnsortedList.Where(o => !o.StartsWith(prefix))
.OrderByDescending(o => o);
var result = regular.Zip(avg, (f, s) => new { First = f, Second = s })
.Aggregate(new List<string>(), (list, o) =>
new List<string>(list) { o.First, o.Second });
Query syntax and string splitting
This one is similar to the lambda syntax, except I'm not using the prefix to determine which string has a prefix. Instead, I am splitting on a space, and if the split result has more than one item then I'm assuming that it has a prefix. Next, I order based on the value and the prefix's availability.
var result = from s in myUnsortedList
let split = s.Split(' ')
let hasPrefix = split.Length > 1
let value = hasPrefix ? split[1] : s
orderby value descending, hasPrefix
select s;
Split the lists into two lists, one normal, one average.
Sort them both.
Then, do a manual "Zipper Merge".
You should probably create your own custom IComparer<T>:
class MyCustomComparer : IComparer<string>
{
private readonly StringComparison StringComparer;
public static readonly MyCustomComparer Ordinal =
new MyCustomComparer(StringComparison.Ordinal);
public static readonly MyCustomComparer OrdinalIgnoreCase =
new MyCustomComparer(StringComparison.OrdinalIgnoreCase);
// etc.
private MyCustomComparer(StringComparison stringComparer)
{
StringComparer = stringComparer;
}
public int Compare(string x, string y)
{
bool isMatchedX = IsMatchedPattern(x);
bool isMatchedY = IsMatchedPattern(y);
if (isMatchedX&& !isMatchedY ) // x matches the pattern.
{
return String.Compare(Strip(x), y, StringComparer);
}
if (isMatchedY && !isMatchedX) // y matches the pattern.
{
return String.Compare(Strip(y), x, StringComparer);
}
return String.Compare(x, y, StringComparison.Ordinal);
}
private static bool isMatchedPattern(string str)
{
// Use some way to return if it matches your pattern.
// StartsWith, Contains, Regex, etc.
}
private static string Strip(string str)
{
// Use some way to return the stripped string.
// Substring, Replace, Regex, etc.
}
}
Check to see if x and y match your pattern. If neither or both do, then use a standard comparison operation. Basically, you only need the custom comparison operation if one (and only one) matches the pattern.
If x matches the pattern and y doesn't, then strip x and check the stripped version of x against y using the String.Compare(...) operation.
If y matches the pattern and x doesn't, then strip y and check the stripped version of y against x using the String.Compare(...) operation.
I updated my answer to show how you can copy the way StringComparison works by exposing static readonly instances of the custom comparer for case/culture options.
Finally, use LINQ with your custom comparer: myList.OrderBy(x => x, MyCustomComparer.Ordinal);
One final note... feel free to optimize this if necessary. This is untested code just off the whim of my mind. The logic is there, I hope. But, typos might have occurred.
Hope that helps.
Another way is to implement an some comparer say MyComparer that implements IComparer<string> and then:
var result = myUnsortedList.OrderBy(x => x, new MyComparer());
I feel like you're using the wrong data structure for this. Why don't you use a SortedDictionary and make it be "name => avg"
untested, probably working code:
SortedDictionary<string, int> dict = new SortedDictionary<string, int>();
dict.Add("Alpha", 10);
dict.Add("Beta", 20);
dict.Add("Zeta", 30);
foreach(string key in dict.Keys.Reverse())
{
int avg = dict[key];
}
To use Your own logic in linq ordering You should implement Your own Comparer and use it's instance as second parameter in OrderBy or OrderByDescending linq method like below:
namespace ConsoleApplication71
{
public class AVGComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// Null checkings are necessary to prevent null refernce exceptions
if((x == null) && (y == null)) return 0;
if(x == null) return -1;
if(y == null) return 1;
const string avg = #"(avg) ";
if(x.StartsWith(avg) || y.StartsWith(avg))
{
return x.Replace(avg, string.Empty).CompareTo(y.Replace(avg, string.Empty));
}
return x.CompareTo(y);
}
}
class Program
{
static void Main(string[] args)
{
List<string> myUnsortedList = new List<string>();
myUnsortedList.Add("Alpha");
myUnsortedList.Add("(avg) Alpha");
myUnsortedList.Add("Zeta");
myUnsortedList.Add("Beta");
myUnsortedList.Add("(avg) Beta");
myUnsortedList.Add("(avg) Zeta");
var mySortedList = myUnsortedList.OrderByDescending(s => s, new AVGComparer());
foreach (string s in mySortedList)
{
Console.WriteLine(s);
}
}
}
}
The output is:
Zeta
(avg) Zeta
Beta
(avg) Beta
Alpha
(avg) Alpha
In a line:
var sorted = myUnsortedList.OrderByDescending(x => x.Replace("(avg) ", "")).ThenBy(x=> x.Contains("(avg)")).ToList();
Here is a passing test (nunit):
[Test]
public void CustomSort()
{
var myUnsortedList = new List<string> { "Zeta", "Alpha", "(avg) Alpha", "Beta", "(avg) Beta", "(avg) Zeta" };
var EXPECTED_RESULT = new List<string> { "Zeta", "(avg) Zeta", "Beta", "(avg) Beta", "Alpha", "(avg) Alpha" };
var sorted = myUnsortedList.OrderByDescending(x => x.Replace("(avg) ", "")).ThenBy(x=> x.Contains("(avg)")).ToList();
for (int i = 0; i < myUnsortedList.Count; i++)
{
Assert.That(sorted[i], Is.EqualTo(EXPECTED_RESULT[i]));
}
}
I have some strings in a list
List<string> list = new List<string>{ "100-1", "100-11", "100-3", "100-20" }
I used following code to sort which is picked from this location
void Main()
{
string[] things= new string[] { "100-1", "100-11", "100-3", "100-20" };
foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
{
Console.WriteLine(thing);
}
}
public class SemiNumericComparer: IComparer<string>
{
public int Compare(string s1, string s2)
{
if (IsNumeric(s1) && IsNumeric(s2))
{
if (Convert.ToInt32(s1) > Convert.ToInt32(s2)) return 1;
if (Convert.ToInt32(s1) < Convert.ToInt32(s2)) return -1;
if (Convert.ToInt32(s1) == Convert.ToInt32(s2)) return 0;
}
if (IsNumeric(s1) && !IsNumeric(s2))
return -1;
if (!IsNumeric(s1) && IsNumeric(s2))
return 1;
return string.Compare(s1, s2, true);
}
public static bool IsNumeric(object value)
{
try {
int i = Convert.ToInt32(value.ToString());
return true;
}
catch (FormatException) {
return false;
}
}
}
My output is 100-1, 100-11, 100-20, 100-3
I believe it is taking - as decimal and comparing the values. Actually I was expecting the result to be
100-1, 100-3, 100-11, 100-20.
I just wanted to know on what basis it is actually performing sort. Any help is appreciated. Even I expect it to treat 100-2 and 100-20 differently.
Just on the fly, I have seen in Infragistic control grid that sorting in it produces the same result as I was expecting here to be.
I have many other string values in the list, some are integers, doubles and so on. Hyphen is just a case mentioned here.
var sorted = things.Select(s => s.Split('-'))
.OrderBy(x => double.Parse(x[0]))
.ThenBy(x => double.Parse(x[1]))
.Select(x=>String.Join("-",x))
.ToList();
This should work as expected:
string[] things= new string[] { "100-1", "100-11", "100-3", "100-20" };
IEnumerable<string> ordered = things
.Select(s => new
{
str = s,
firstPart = s.Split('-').ElementAtOrDefault(0),
secondPart = s.Split('-').ElementAtOrDefault(1)
})
.OrderBy(x => int.Parse(x.firstPart))
.ThenBy(x => int.Parse(x.firstPart))
.Select(x => x.str);
foreach (string s in ordered)
Console.WriteLine(s);
Although it assumes that your data is strict, otherwise you're open for exceptions, f.e at int.Parse(x.firstPart).
Demo: http://ideone.com/UJ5Yt4
If you want to sort the items by the 2nd number (after hyphen), You need to parse the string to a number then order by using it. you can try:
string[] things = new string[] { "100-1", "100-11", "100-3", "100-20" };
var test = things.OrderBy(r => int.Parse(r.Split('-')[1])).ToArray();
The reason your current code is not working is probably due to the fact that it can't parse the string 100- to an integer value and your function IsNumeric is returning false.
I've an object that is include property ID with values between 101 and 199. How to order it like 199,101,102 ... 198?
In result I want to put last item to first.
The desired ordering makes no sense (some reasoning would be helpful), but this should do the trick:
int maxID = items.Max(x => x.ID); // If you want the Last item instead of the one
// with the greatest ID, you can use
// items.Last().ID instead.
var strangelyOrderedItems = items
.OrderBy(x => x.ID == maxID ? 0 : 1)
.ThenBy(x => x.ID);
Depending whether you are interested in the largest item in the list, or the last item in the list:
internal sealed class Object : IComparable<Object>
{
private readonly int mID;
public int ID { get { return mID; } }
public Object(int pID) { mID = pID; }
public static implicit operator int(Object pObject) { return pObject.mID; }
public static implicit operator Object(int pInt) { return new Object(pInt); }
public int CompareTo(Object pOther) { return mID - pOther.mID; }
public override string ToString() { return string.Format("{0}", mID); }
}
List<Object> myList = new List<Object> { 1, 2, 6, 5, 4, 3 };
// the last item first
List<Object> last = new List<Object> { myList.Last() };
List<Object> lastFirst =
last.Concat(myList.Except(last).OrderBy(x => x)).ToList();
lastFirst.ForEach(Console.Write);
Console.WriteLine();
// outputs: 312456
// or
// the largest item first
List<Object> max = new List<Object> { myList.Max() };
List<Object> maxFirst =
max.Concat(myList.Except(max).OrderBy(x => x)).ToList();
maxFirst.ForEach(Console.Write);
Console.WriteLine();
// outputs: 612345
Edit: missed the part about you wanting the last item first. You could do it like this :
var objectList = new List<DataObject>();
var lastob = objectList.Last();
objectList.Remove(lastob);
var newList = new List<DataObject>();
newList.Add(lastob);
newList.AddRange(objectList.OrderBy(o => o.Id).ToList());
If you are talking about a normal sorting you could use linq's order by method like this :
objectList = objectList.OrderBy(ob => ob.ID).ToList();
In result I want to put last item to first
first sort the list
List<int> values = new List<int>{100, 56, 89..};
var result = values.OrderBy(x=>x);
add an extension method for swaping an elements in the List<T>
static void Swap<T>(this List<T> list, int index1, int index2)
{
T temp = list[index1];
list[index1] = list[index2];
list[index2] = temp;
}
after use it
result .Swap(0, result.Count -1);
You can acheive this using a single Linq statment.
var ordering = testData
.OrderByDescending(t => t.Id)
.Take(1)
.Union(testData.OrderBy(t => t.Id).Take(testData.Count() - 1));
Order it in reverse direction and take the top 1, then order it the "right way round" and take all but the last and union these together. There are quite a few variants of this approach, but the above should work.
This approach should work for arbitrary lists too, without the need to know the max number.
How about
var orderedItems = items.OrderBy(x => x.Id)
var orderedItemsLastFirst =
orderedItems.Reverse().Take(1).Concat(orderedItems.Skip(1));
This will iterate the list several times so perhaps could be more efficient but doesn't use much code.
If more speed is important you could write a specialised IEnumerable extension that would allow you to sort and return without converting to an intermediate IEnumerable.
var myList = new List<MyObject>();
//initialize the list
var ordered = myList.OrderBy(c => c.Id); //or use OrderByDescending if you want reverse order
Is it possible to get result1 as a single linq expression? I understand that it may not be the best practise but I would just like to know how to do so out of curiousity.
result2 has a different answer but it correct too. However, it has a complexity of O(NlogN) as opposed to O(N).
void Main()
{
A[] a = new A[4]{new A(0,0,0),new A(1,1,0),new A(1,2,1),new A(1,2,0)};
/*
//Grossly inefficient: replaced
var tmpList = a.Where(x => (x.one == a.Max(y => y.one)));
var result1 = tmpList.First(x => (x.two == tmpList.Max(y => y.two)));
*/
var maxOneValue = a.Max(x => x.one);
var tmpList = a.Where(x => (x.one == maxOneValue));
var maxTwoValueOfTmpList = tmpList.Max(x => x.two);
var result1 = tmpList.First(x => (x.two == maxTwoValueOfTmpList));
//A: 1, 2, 1
var result2 = a.OrderBy(x => x.one)
.ThenBy(x => x.two)
.Last();
//A: 1, 2, 0
}
class A
{
public int one;
public int two;
public int three;
public A(int one, int two, int three)
{
this.one = one;
this.two = two;
this.three = three;
}
}
edit: I have edited by question and hence some answers may not tally.
This query gives the same result :
var result = a.OrderByDescending(x => x.one + x.two)
.First();
But then you could get items without max 'one' field..
This one should work :
var result = a.OrderByDescending(x => x.two)
.Where(x => (x.one == a.Max(y => y.one)))
.First();
maybe this solves your problem:
a.OrderBy(x => x.one + x.two).Last()
One way to do it is to implement IComparable<A> on your A class. Then your solution simply becomes:
var result1 = a.Max(); // 1,2,1
Here's how you would implement IComparable<A>:
class A : IComparable<A>
{
...
public int CompareTo(A other)
{
return this.one == other.one ? this.two - other.two : this.one - other.one;
}
}
Here is a demo: http://ideone.com/ufIcgf. The nice thing about this is that it still has a complexity of O(N), and is also fairly concise.