问题
I have a list of strings, each containing a number substring, that I'd like to be reordered based on the numerical value of that substring. The set will look something like this, but much larger:
List<string> strings= new List<string>
{
"some-name-(1).jpg",
"some-name-(5).jpg",
"some-name-(5.1).jpg",
"some-name-(6).jpg",
"some-name-(12).jpg"
};
The number will always be surrounded by parentheses, which are the only parentheses in the string, so using String.IndexOf
is reliable. Notice that not only may there be missing numbers, but there can also be decimals, not just integers.
I'm having a really tough time getting a reordered list of those same strings that has been ordered on the numerical value of that substring. Does anyone have a way of doing this, hopefully one that performs well? Thanks.
回答1:
This will check if the items between the parenthesis is convertible to a double
, if not it will return -1 for that case.
var numbers = strings.Select( x => x.Substring( x.IndexOf( "(" ) + 1,
x.IndexOf( ")" ) - x.IndexOf( "(" ) - 1 ) ).Select( x =>
{
double val;
if( double.TryParse( x, out val ) ) {
return val;
}
// Or whatever you want to do
return -1;
} ).OrderBy( x => x ); // Or use OrderByDescending
If you are sure there will always be a number between the parenthesis, then use this as it is shorter:
var numbers = strings.Select(
x => x.Substring( x.IndexOf( "(" ) + 1, x.IndexOf( ")" ) - x.IndexOf( "(" ) - 1 ) )
.Select( x => double.Parse(x))
.OrderBy( x => x ); // Or use OrderByDescending
EDIT
I need the original strings, just ordered on those numbers.
Basically what you need to do is to pass a predicate to the OrderBy
and tell it to order by the number:
var items = strings.OrderBy(
x => double.Parse( x.Substring( x.IndexOf( "(" ) + 1,
x.IndexOf( ")" ) - x.IndexOf( "(" ) - 1 ) ));
How about an OO approach?
We are ordering string but we need to treat them like numbers. Wouldn't it be nice if there was a way we can just call OrderBy
and it does the ordering for us? Well there is. The OrderBy
method will use the IComparable<T>
if there is one. Let's create a class to hold our jpg paths and implement the IComparable<T>
interface.
public class CustomJpg : IComparable<CustomJpg>
{
public CustomJpg(string path)
{
this.Path = path;
}
public string Path { get; private set; }
private double number = -1;
// You can even make this public if you want.
private double Number
{
get
{
// Let's cache the number for subsequent calls
if (this.number == -1)
{
int myStart = this.Path.IndexOf("(") + 1;
int myEnd = this.Path.IndexOf(")");
string myNumber = this.Path.Substring(myStart, myEnd - myStart);
double myVal;
if (double.TryParse(myNumber, out myVal))
{
this.number = myVal;
}
else
{
throw new ArgumentException(string.Format("{0} has no parenthesis or a number between parenthesis.", this.Path));
}
}
return this.number;
}
}
public int CompareTo(CustomJpg other)
{
if (other == null)
{
return 1;
}
return this.Number.CompareTo(other.Number);
}
}
What is nice about the above approach is if we keep calling OrderBy
, it will not have to search for the opening (
and ending )
and doing the parsing of the number every time. It caches it the first time it is called and then keeps using it. The other nice thing is that we can bind to the Path
property and also to the Number
(we would have to change the access modifier from private). We can even introduce a new property to hold the thumbnail image and bind to that as well. As you can see, this approach is far more flexible, clean and an OO approach. Plus the code for finding the number is in one place so if we switch from ()
to another symbol, we would just change it in one place. Or we can modify to look for ()
first and if not found look for another symbol.
Here is the usage:
List<CustomJpg> jpgs = new List<CustomJpg>
{
new CustomJpg("some-name-(1).jpg"),
new CustomJpg("some-name-(5).jpg"),
new CustomJpg("some-name-(5.1).jpg"),
new CustomJpg("some-name-(6).jpg"),
new CustomJpg("some-name-(12).jpg")
};
var ordered = jpgs.OrderBy(x => x).ToList();
You can use this approach for any object.
回答2:
In the above example code return a list of numbers ordered by numbers but if you want have list of file names that ordered by name better you put in same zero to beginning of the numbers like "some-name-(001).jpg" and you can simply order that
List<string> strings = new List<string>
{
"some-name-(001).jpg",
"some-name-(005.1).jpg",
"some-name-(005).jpg",
"some-name-(004).jpg",
"some-name-(006).jpg",
"some-name-(012).jpg"
};
var orederedByName =strings.Select(s =>s ).OrderBy(s=>s);
回答3:
You can make the Substring selection easier, if you first cut off the part starting at the closing parenthesis ")"
. I.e., from "some-name-(5.1).jpg"
you first get "some-name-(5.1"
. Then take the part after "("
. This saves a length calculation, as the 2nd Substring
automatically takes everything up to the end of string.
strings = strings
.OrderBy(x => Decimal.Parse(
x.Substring(0, x.IndexOf(")"))
.Substring(x.IndexOf("(") + 1)
)
)
.ToList();
This is probably not of great importance here, but generally, decimal
stores numbers given in decimal notation more accurately than double
. double
can convert 17.2
as 17.19999999999999
.
来源:https://stackoverflow.com/questions/41625535/how-to-order-list-of-strings-on-number-substring-using-c-sharp