lambda string order by dates with number - c#

How to sort this?
I have List of string with values like this
11-03-2013
11-03-2013 -Count=2
11-03-2013 -count=1
11-04-2013 -Count=1
11-04-2013 -Count=2
11-04-2013
Output should be, The one without the count should be on the last and the top most should be 1 followed by 1 and dates should be ordered by ascending.
11-03-2013 -Count=2
11-03-2013 -count=1
11-03-2013
11-04-2013 -Count=2
11-04-2013 -Count=1
11-04-2013
I tried this code but this is sorting this by descending
var edates= edates.OrderBy(e => e.Replace("-count=1", string.Empty).Replace("-count=2", string.Empty)).ToList();
I know that a simple class with properties can do the trick but doing that so would need to change other methods which would require a lot of work.
Regards

it is because you compare strings not dates. Create two functions: first substrings date part and parses it and second substrings the count part and returns parsed count(or 0 if length < 11) and then yourList.OrderBy(s=> f1(s)).ThenByDescending(s=> f2(s))

Here is #Guru Stron's solution in code
private static void sortList()
{
var dates = getDates();
var sorted = dates.OrderBy(f1).ThenByDescending(f2);
}
private static DateTime f1(string parse)
{
return DateTime.Parse(parse.Substring(0, 10));
}
private static int f2(string parse)
{
int sort;
if (parse.Length > 10) int.TryParse(parse.Substring(18), out sort);
else sort = 0;
return sort;
}

You first need to order by the date, then by the rest of the string, descending;
edates =
edates.OrderBy(x =>
DateTime.ParseExact(x.Substring(0, 10), "MM-dd-yyyy",
CultureInfo.InvariantCulture))
.ThenByDescending(x => x.Substring(10))
.ToList();

You ought to use a real class to simplify things and have a proper representation. That said, you can use the LINQ query syntax and take advantage of the let clause to store the result of splitting the text on a space and the equal symbol. If the split result has more than one element we can assume the count exists. Next, we order by the date (after parsing it) and then by parsing the count (descending).
Try this approach out:
string[] inputs =
{
"11-03-2013",
"11-03-2013 -Count=2",
"11-03-2013 -Count=1",
"11-04-2013 -Count=1",
"11-04-2013 -Count=2",
"11-04-2013"
};
var query = from input in inputs
let split = input.Split(' ', '=')
let count = split.Length > 1 ? int.Parse(split[2]) : 0
orderby DateTime.Parse(split[0]), count descending
select input;
foreach (var item in query)
{
Console.WriteLine(item);
}

The following should order as desired across different years and counts >= 10.
This won't be the "fastest" method (and it will most certainly not work with L2S/EF LINQ providers), but it should sort the given format correctly and fail-fast on invalid values. I would likely write the code like this (or use an IComparer with an equivalent setup) - mainly because it "reads simple" to me.
DateTime OrderableItem (string e) {
var date = e.Substring(0, 10);
return DateTime.ParseExact(date, "MM-dd-yyyy", CultureInfo.InvariantCulture)
}
int OrderableCount (string e) {
var m = Regex.Match(e, #"-count=(\d+)$", RegexOptions.IgnoreCase);
return m.Success
? int.Parse(m.Groups[1].Value)
: 0;
}
var res = seq.OrderBy(OrderableDate)
.ThenBy(OrderableCount);
I much prefer to take data, instead of excluding data.

Related

Is it good practice to compare string values of two comboboxes c#

I want to compare values of two comboboxes. The variables are of type var and they come like this: 27-12-2018
I want to compare these two values and for this purpose I have converted the value in date format and in string format.
This is meteorologycal charts.
var formattedDates = string.Join("_", Path.GetFileName(file).Split('_', '.').Skip(1).Take(3));
var formattedDates2 = string.Join("_", Path.GetFileName(file).Split('_', '.').Skip(1).Take(3));
if (!comboBox2.Items.Contains(formattedName))
{
comboBox2.Items.Add(formattedName);
}
if (!comboBox3.Items.Contains(formattedDates))
{
comboBox3.Items.Add(formattedDates);
}
if (!comboBox4.Items.Contains(formattedDates2))
{
comboBox4.Items.Add(formattedDates2);
}
listBox1.Items.Add(Path.GetFileName(file));
}
}
else
{
MessageBox.Show("Директорията Meteo не е октирта в системен диск 'C:\'");
Application.ExitThread();
}
var result = Directory
.EnumerateFiles(#"C:\Meteo", "*.dat")
.SelectMany(file => File.ReadLines(file))
.Select(line => line.Split(new char[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries))
.Select(items => new {
id = items[0],
date = DateTime.ParseExact(items[1], "dd-MM-yyyy", CultureInfo.InvariantCulture).ToString(),
date2 = items[1],
hours = items[2],
temperature = items[3],
presure = items[4],
windSpeed = items[5],
windDirect = items[6],
rain = items[7],
rainIntensity = items[8],
sunRadiation = items[12],
/* etc. */
})
.ToList();
var dates = result
.Select(item => item.date)
.ToArray();
I have the same values in two formats - String and Date, but I dont know how to compare two comboboxes (if firstCombo > secondCombo){ messagebox.show("")}
Convert your string to a DateTime type.
DateTime DT1 = DateTime.ParseExact("18/08/2015 06:30:15.006542", "dd/MM/yyyy HH:mm:ss.ffffff", CultureInfo.InvariantCulture);
Rearrange the format string to match whatever date format you are using.
Then you can just do:
if(DT1 > DT2)
Also FYI, VAR isnt a type, it just sets the type of the variable to whatever type is on the right side of the equals.
To compare values of two comboboxes, you first need to convert both values into a type that can be compared. In your case, you seem to want to compare dates.
Ex: 07-06-2019 > 06-06-2019 = True.
I would recommend you to get the current values from both comboboxes (combobox.Text) and create a DateTime object with them.
Then, you'll be able to compare them like you want.
DateTime date0 = Convert.ToDateTime(combobox0.Text);//"07-06-2019"
DateTime date1 = Convert.ToDateTime(combobox1.Text);//"06-06-2019"
int value = DateTime.Compare(date0, date1);
if (value > 0)
{
//date0 > date1
}
else
{
if (value < 0)
{
//date0 < date1
}
else
{
//date0 == date1
}
}
Finally, to answer your question, the best practice to compare combobox values is depending on what value you're trying to compare... If you want to compare dates like in your example, it is better to convert the values to DateTime. The only comparison you can make with strings, correct me if I'm wrong, is to check if the strings are equal or have the same value.
Another good practice is to use the TryParse method associated to the type of value you want to convert/compare. Most of, if not all, basic types in c# have this method associated to them.
https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tryparse?view=netframework-4.8
DateTime date0;
//Take text from combobox0, convert it and put the result in date0
bool success = DateTime.TryParse(combobox0.Text, out date0);
if(success)
{
//Your date has been converted properly
//Do stuff
}

C# orderby string along with int [duplicate]

This question already has answers here:
Alphanumeric sorting using LINQ
(12 answers)
Closed 6 years ago.
I have a list containing data with string and number. I have to order it in ascending and descending order.
id data
------------------
1 data1#%
2 data10
3 data
4 #$data
5 data2
I fetch the record and store it in list "List". Then I order it in ascending, but "data10" is coming in b/w "data1" and "data2". Below is my code
var o/p = List.OrderBy(x => x.data);
expected output - data1, data2 and data10
You are currently sorting it based on the string values, which will sort it by dictionary value. In a dictionary, "10" will appear between "1" and "2" because that is alphabetical order - it does not recognize that it is sorting numbers.
True alphanumeric sorting can get pretty complex, but based on your data you might be able to simplify it. Assuming your string "data1", "data2", and "data10" is a consistent pattern, you can do something like this:
var op = List.OrderBy(x => int.Parse(x.data.substring(4)));
Alternatively, if the value before the number isn't a constant length, you can use Regex to pull the number value out:
var op = List.OrderBy(x => int.Parse(Regex.Match(x.data, "\\d+").Value));
To get what you want, you need to pad the numeric portion in your order by clause, something like:
var o/p = List.OrderBy(x => PadNumbers(x.Data));
where PadNumbers could be defined as StackOverflow user Nathan has written here:
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}
This pads zeros for any number (or numbers) that appear in the input string so that OrderBy sees:
data0000000001
data0000000010
data0000000002
The padding only happens on the key used for comparison. The original strings (without padding) are preserved in the result.
Note : This approach assumes a maximum number of digits for numbers in the input.

Selecting max number from a list of string in LINQ?

trying to select the max digit from a list of strings:
int maxDigit = this.myList.Where(x=> x.Name.Any(Char.IsDigit))
.Select(x => int.Parse(x.Name)).DefaultIfEmpty(0).Max();
It is int.Parse(x.Name) which is causing an exception as this is returning the entire name string e.g. 'myValue99' which of course cannot be parsed to an int. I just want to return 99. I don't know where the digits will be in the string therefore cannot for example take the last two.
I need the DefaultIfEmpty for cases where the string does not contain a number.
Assuming you want the max number and not the max digit, all you need is a function to convert "stuff99" to 99. Then the Linq part becomes child's play:
int maxNumber = myList.Max(ExtractNumberFromText);
or, to be closer to your specs:
int maxNumber = myList
.Select(ExtractNumberFromText)
.DefaultIfEmpty(0)
.Max();
#Codecaster already pointed to a few applicable answers on this site for the second part. I adopted a simple one. No error checking.
// the specs: Only ever going to be my stuff1, stuff99
int ExtractNumberFromText(string text)
{
Match m = Regex.Match(text, #"\d*");
return int.Parse(m.Groups[0].Value); // exception for "abc"
// int.Parse("0" + m.Groups[0].Value); // use this for default to 0
}
you should only select and parse the Digit characters out of your string
int maxDigit = this.myList.Where(x => x.Name.Any(Char.IsDigit))
.Select(x => int.Parse(new string(x.Name.Where(Char.IsDigit).ToArray())))
.DefaultIfEmpty(0).Max();
Assuming the input can contain the following categories:
nulls
Empty strings
Strings with only alphabetical characters
Strings with mixed alphabetical and numerical characters
Strings with only numerical characters
You want to introduce a method that extracts the number, if any, or returns a meaningful value if not:
private static int? ParseStringContainingNumber(string input)
{
if (String.IsNullOrEmpty(input))
{
return null;
}
var numbersInInput = new String(input.Where(Char.IsDigit).ToArray());
if (String.IsNullOrEmpty(numbersInInput))
{
return null;
}
int output;
if (!Int32.TryParse(numbersInInput, out output))
{
return null;
}
return output;
}
Note that not all characters for which Char.IsDigit returns true can be parsed by Int32.Parse(), hence the TryParse.
Then you can feed your list to this method:
var parsedInts = testData.Select(ParseStringContainingNumber)
.Where(i => i != null)
.ToList();
And do whatever you want with the parsedInts list, like calling IEnumerable<T>.Max() on it.
With the following test data:
var testData = new List<string>
{
"۱‎", // Eastern Arabic one, of which Char.IsDigit returns true.
"123",
"abc456",
null,
"789xyz",
"foo",
"9bar9"
};
This returns:
123
456
789
99
Especially note the latest case.
To find the max digit (not number) in each string:
static void Main(string[] args)
{
List<string> strList = new List<string>() { "Value99", "46Text" };
List<int> resultList = new List<int>();
foreach (var str in strList)
{
char[] resultString = Regex.Match(str, #"\d+").Value.ToCharArray();
int maxInt = resultString.Select(s => Int32.Parse(s.ToString())).Max();
resultList.Add(maxInt);
}
}
It can be simple using Regex.
You stated 99, so you need to span more than one digit:
var maxNumber = myTestList.SelectMany(x => getAllNumnbersFromString(x.Name)).DefaultIfEmpty(0).Max();
static List<int> getAllNumnbersFromString(string str)
{
List<int> results = new List<int>();
var matchesCollection = Regex.Matches(str, "[0-9]+");
foreach (var numberMatch in matchesCollection)
{
results.Add(Convert.ToInt32(numberMatch.ToString()));
}
return results;
}
One digit only check:
int maxNumber = myTestList.SelectMany(x => x.Name.ToCharArray().ToList())
.Select(x => Char.IsDigit(x) ? (int)Char.GetNumericValue(x) : 0)
.DefaultIfEmpty(0).Max();
Probably there's a slicker way to do this but I would just do:
int tmp = 0;
int maxDigit = this.myList.Where(x=> x.Name.Any(Char.IsDigit))
.Select(x =>
(int.TryParse(x.Name,out tmp ) ? int.Parse(x.Name) : 0 ) ).Max();
You have to remember that Parse will error out if it can't parse the value but TryParse will just give you false.

Use Regex in this Linq query?

I am using this Linq to find the highest int in a list, so that I can increment it and add it to the end of the next string:
var CableNumber = CList.Select(v => int.Parse(v.CableNumber.Substring(n))).Max();
However, because the strings aren't a fixed length, I was thinking of inserting a Regex.Match in there, possibly something like:
n = Regex.Match(CableNumber, #"\d{3}", RegexOptions.RightToLeft);
To specify; the only format the input strings follow is that it will always have a 3 digit number on the end, possibly followed by a single letter. Some examples:
CP1-P-CP2-001 (001)
MOT1PSP2023A (023)
TKSP3-C-FLT2-234-A (234)
How could I implement this? Is there a better way?
The following uses the regex pattern inside the linq query:
string[] strings = { "CP1-P-CP2-001 (001)","MOT1PSP2023A (023)", "TKSP3-C-FLT2-234-A (234)",
"InvalidString" };
int? maxValue = strings.Max(x =>
{
var match = Regex.Match(x, #"\d{3}(?=\D*$)");
return match.Success ? (int?) int.Parse(match.Value) : null;
});
The int? is so we can bypass any string.Empty coming back from an invalid match and only parse valid matches. If none matched, will return null.
How about this?
var CableNumber = CList.Select(v =>
int.Parse(v.CableNumber.Substring(v.CableNumber.Length - 3))).Max();
Or to be safe (to prevent string that is less than 3 characters).
var CableNumber = CList.Select(v =>
int.Parse(("000" + v.CableNumber).Substring(("000" + v.CableNumber).Length - 3))).Max();
update
Use LastIndexOfAny
var CableNumber = CList.Select(v =>
int.Parse(v.CableNumber.Substring(v.CableNumber
.LastIndexOfAny("0123456789".ToCharArray()) - 2, 3))).Max();
You can use the next regex to get last 3 digits in each string:
(\d{3})\D*$

Sorting array of formatted time strings

I am trying to sort my arraylist.
The array list consists of data in time format.
Array:
9:15 AM, 10:20 AM
How should I sort it?
The result i get from below code is :
10:20 AM
9:15 AM
Below is my code:
String timeText = readFileTime.ReadLine();
timeSplit = timeText.Split(new char[] { '^' });
Array.Sort(timeSplit);
foreach (var sortedArray in timeSplit)
{
sortedTimeListBox.Items.Add(sortedArray);
}
Yes, since you simply split a string, you're merely sorting an array of strings (meaning 1 comes before 9 and it doesn't care about the decimal point). To get the sorting you desire, you need to first convert it into a DateTime like this:
timeSplit = timeText
.Split(new char[] { '^' });
.Select(x => new { Time = DateTime.Parse(x), String = x })
.OrderBy(x => x.Time)
.Select(x => x.String)
.ToArray();
Here, what we've done is:
Split the string as you had done before
Create a new anonymous type that contains the original string and also that string converted into a DateTime.
Ordered it by the DateTime property
Select'ed back to the original string
Converted it into an array
timeSplit now contains the strings sorted as you wanted.
Array.Sort(timeSplit, delegate(string first, string second)
{
return DateTime.Compare(Convert.ToDateTime(first), Convert.ToDateTime(second));
});

Categories