C# Order Items By Leading Zeros - c#

I need to order items in an IEnumerable based on a string property that contains leading 0s. The items need to be ordered by 000. then 00. and then it goes to 0.
A simplified example:
Unordered - ["0.4","0.1", "0.3", "00.5", "000.1", "1.2", "00.2", "2.1"]
Ordered - ["000.1", "00.2", "00.5", "0.1", "0.3", "0.4", "1.2", "2.1"]
The current implementation is:
items.OrderBy(x => x.age);
which orders the items as ["0.1", "0.3", "0.4", "00.2", "00.5", "000.1", "1.2", "2.1"]
Since these are strings, I thought I might try to convert them to decimals, but I'm still not getting the order I need.
items.OrderBy(x => decimal.Parse(x.age))
[ 0.1, 000.1, 00.2, 0.3, 0.4, 00.5, 1.2, 2.1]
Any advice on how to get the order I need would be greatly appreciated!

I suggest comparing strings; the difficulty is that '.' < '0'; to have a required order we can try changing '.' into some other delimiter that's lexicographically bigger then any digit. Let it be a letter, say 'x':
var result = items.OrderBy(item => item.Replace('.', 'x'));
Demo:
string[] items = new string[] {
"0.4","0.1", "0.3", "00.5", "000.1", "1.2", "00.2", "2.1"
};
var result = items
.OrderBy(item => item.Replace('.', 'x'));
Console.Write(string.Join(", ", result));
Outcome:
000.1, 00.2, 00.5, 0.1, 0.3, 0.4, 1.2, 2.1

Welcome to Stack Overflow,
I think this snippet may help you:
items.OrderBy(x => x.age.Split('.')[0].Length).ThenBy(x => decimal.Parse(x.age));
With this snippet, we split the value on the decimal dot, and then OrderBy the count of 0's on the first part, and if we have two values with the same count of leading zeros, we compare them as ordinary decimals.

Try the following:
items.OrderBy(x => x.Split(".")[0]).ThenBy(x => x.Split(".")[1]);

Related

Lists, String Split Options and logic

I am in need of help a little help from someone more advanced. The code i have questions about is the following :
static void Main(string[] args)
{
List<string> numbersAsStrings = Console.ReadLine()
.Split('|')
.Reverse()
.ToList();
List<int> numbers = new List<int>();
foreach (var str in numbersAsStrings)
{
numbers.AddRange(str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
.Select(int.Parse)
.ToList()
);
// Zapiswam stoinostite ot stariq List w nov List
// Smeneni sa oshte gore s .Reverse
}
Console.WriteLine(string.Join(" ", numbers));
}
The exercise was as following: Write a program to append several array of numbers.
arrays are separated by ‘|’.
Values are separated by spaces (‘ ’,one or several)
Order the arrays from the last to the first, and their values from left to right.
Can someone explain to me how the code reads the entries on this particular code please. I could not find the solution myself.
Kind regards
Console.ReadLine() will read all the characters typed in after the program has been run, until the user presses enter.
List<string> numbersAsStrings =
Console.ReadLine() // read the input as a string
.Split('|') // split the string into an array (delimited by |)
.Reverse() // reverse the array
.ToList(); // convert the array into a List

Format unstructured string

I have tried several methods (by position, by white space, regex) but cannot figure how to best parse the following lines as a table. For e.g. let's say the two lines I want to parse are:
Bonds Bid Offer (mm) (mm) Chng
STACR 2015-HQA1 M1 125 120 5 x 1.5 0
STACR 2015-HQA12 2M2 265 5 x -2
I want that it should parse as follows for [BondName] [Bid] [Offer]:
[STACR 2015-HQA1 M1] [125] [120]
[STACR 2015-HQA12 2M2] [265] [null]
Notice the null which is an actual value and also the spaces should be retained in the bond name. FYI, the number of spaces in the Bond Name will be 2 as in the above examples.
Edit: Since many of you have asked for code here it is. The spaces between the points can range from 1-5 so I cannot reply on spaces (it was straightforward then).
string bondName = quoteLine.Substring(0, 19);
string bid = quoteLine.Substring(19, 5).Trim();
string offer = quoteLine.Substring(24, 6).Trim();
The only way I can see this working is that:
1st data point is STACR (Type)
2nd data point is the year and Series
(e.g. 2015-HQA1)
3rd data point is Tranche (M1)
4th data point is bid
(e.g. 125 ** bid is always available **)
5th data point is offer (e.g. 120 but can be blank
or whitespace which introduces complexity)
With the current set of requirements, I'm assuming the following
1. String starts with 3 part bond name
2. Followed by bid
3. Followed by offer (optional)
4. After that, we'll have something like ... x ... ... (we'll use x as reference)
Given they are valid, you can use the following code
var str = "STACR 2015-HQA1 M1 125 120 5 x 1.5 0"; //your data
var parts = str.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
//we'll use this pattern : <3 part bond name> <bid> <offer/null> <something x ....>
var xIsAt = parts.IndexOf("x"); //we'll use x as reference
if (xIsAt > 2) //first three are BondName
parts.RemoveRange(xIsAt - 1, parts.Count - xIsAt + 1); //remove "5 x 1.5 ..."
var bond = string.Join(" ", parts.Take(3)); //first 3 parts are bond
var bid = parts.Count > 3 ? parts.ElementAt(3) : null; //4th is bid
var offer = parts.Count > 4 ? parts.ElementAt(4) : null; //5th is offer
[EDIT]
I did not account for the blank 'Offer' so this method will fail on a blank 'Offer'. Looks like someone already has a working answer, but i'll leave the linq example for anyone that finds it useful.
[END EDIT]
Linq based option.
Split the string by spaces, and remove empty spaces. Then reverse the order so you can start from the back and work your way forward. The data appears more normalized at the end of the string.
For each successive part of the line, you skip the previous options and only take what you need. For the last part which is the long string, you skip what you don't need, then reverse the order back to normal, and join the segments together with spaces.
string test = "STACR 2015-HQA1 M1 125 120 5 x 1.5 0";
var split_string_remove_empty = test.Split(new char[]{ ' ' }, StringSplitOptions.RemoveEmptyEntries).Reverse();
var change = split_string_remove_empty.Take(1)
.SingleOrDefault();
var mm2 = split_string_remove_empty.Skip(1)
.Take(1)
.SingleOrDefault();
var mm3 = split_string_remove_empty.Skip(3)
.Take(1)
.SingleOrDefault();
var offer = split_string_remove_empty.Skip(4)
.Take(1)
.SingleOrDefault();
var bid = split_string_remove_empty.Skip(5)
.Take(1)
.SingleOrDefault();
var bonds = string.Join(" ", split_string_remove_empty.Skip(6)
.Reverse());
Output:

Groupby Linq C#

You guys are ruthless, makes a new programmer like me feel real welcome. :)
Alright let me try this one more time if I can correctly explain my situation. Like one of answer below I have a string that contains the following information. This was created using a while loop where each line ends with an Environment.Newline (There is no mistake in the first line, there is actually a blank line).
var s = #"
ABC-123, 80000, 1400
ABC-123, 70000, 1250
ABC-123, 65000, 1200
BCD-234, 90000, 1300
BCD-234, 95000, 1100
XYZ-111, 24000, 1000
XYZ-111, 24000, 1000"
I originally asked if there is a way to group by the first column ie. all ABC-123 are grouped together, the second is summed and the third column is averaged. Please ignore the sum and average, I just need to understand how to group first.
Here's where I get confused, by using this statement for one of the answers below:
var ss = s.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
.GroupBy(y => y[0]);
I understand what the answer is trying to do but I need help writing the result back into a string (maybe that's not the right choice, I don't know, always open to suggestions)so that I can use StreamWriter to save the result as a csv.
I've tried to understand IEnumerables but all the videos/websites just confuse the hell out of me. I've also tried outputting the results of ss so that maybe if I got a visual representation, then I could rewrite it but when I do I get the following results:
Console.WriteLine(ss);
Console.Read();
System.Linq.GroupedEnumerable3[<>f__AnonymousType01[System.Char],System.Char,<
f__AnonymousType0`1[System.Char]]
The output I would want would be a string that looked like this.
output = "ABC-123, 215000, 1283
BCD-234, 185000, 1200
XYZ-111, 48000, 1000"
Assuming all your inputdata is one long string:
var s = #"ABC-123, 80000, 1400
ABC-123, 70000, 1250
ABC-123, 65000, 1200
BCD-234, 90000, 1300
BCD-234, 95000, 1100
XYZ-111, 24000, 1000
XYZ-111, 24000, 1000";
var ss = s.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries) //split by newlines
.Select(x => x.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)) //Split each line by ,
.GroupBy(y => y[0]); //group by first element of array
If it is an array of string already, you can ignore the first split by newlines
EDIT: Well, we cannot explain the theory behind IEnumerable, LINQ and grouping here. There are plenty of excellent tutorials on that. Maybe you should learn some basics first before jumping into such stuff.
But for your particular problem this should do it:
var lines = ss.Select(z => new {cKey = z.Key,
c2sum = z.Select(a=> Convert.ToInt32(a[1])).Sum(),
c3avg = z.Select(a=> Convert.ToInt32(a[2])).Average()});
foreach (var l in lines)
Console.WriteLine("{0}, {1}, {2}", l.cKey, l.c2sum, l.c3avg); //or whatever stream you want to write to
Assuming that your output is an array of strings, one way would be to split the strings by commas and group by the first result:
list.Select(s => s.Split(','))
.GroupBy(a => a[0])
Note that the output will be an IEnumerable<string[]> - if you want the original string just keep it in the original select:
list.Select(s => new {S = s, Parts = s.Split(',')})
.GroupBy(a => a.Parts[0])
.Select(g => new {Key = g.Key, lines = g.Select(a => a.S) } );

C# - How to parse text file (space delimited numbers)?

Given a data file delimited by space,
10 10 10 10 222 331
2 3 3 4 45
4 2 2 4
How to read this file and load into an Array
Thank you
var fileContent = File.ReadAllText(fileName);
var array = fileContent.Split((string[])null, StringSplitOptions.RemoveEmptyEntries);
if you have numbers only and need a list of int as a result, you can do this:
var numbers = array.Select(arg => int.Parse(arg)).ToList();
It depends on the kind of array you want. If you want to flatten everything into a single-dimensional array, go with Alex Aza's answer, otherwise, if you want a 2-dimensional array that maps to the lines and elements within the text file:
var array = File.ReadAllLines(filename)
.Select(line => line.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
.Where(line => !string.IsNullOrWhiteSpace(line)) // Use this to filter blank lines.
.Select(int.Parse) // Assuming you want an int array.
.ToArray();
Be aware that there is no error handling, so if parsing fails, the above code will throw an exception.
You will be interested in StreamReader.ReadLine() and String.Split()
I couldn't get Quick Joe Smith's answer to work, so I modified it. I put the modified code into a static method within a "FileReader" class:
public static double[][] readWhitespaceDelimitedDoubles(string[] input)
{
double[][] array = input.Where(line => !String.IsNullOrWhiteSpace(line)) // Use this to filter blank lines.
.Select(line => line.Split((string[])null, StringSplitOptions.RemoveEmptyEntries))
.Select(line => line.Select(element => double.Parse(element)))
.Select(line => line.ToArray())
.ToArray();
return array;
}
For my application, I was parsing for double as opposed to int. To call the code, try using something like this:
string[] fileContents = System.IO.File.ReadAllLines(openFileDialog1.FileName);
double[][] fileContentsArray = FileReader.readWhitespaceDelimitedDoubles(fileContents);
Console.WriteLine("Number of Rows: {0,3}", fileContentsArray.Length);
Console.WriteLine("Number of Cols: {0,3}", fileContentsArray[0].Length);

substring with linq?

I've got collection of words, and i wanna create collection from this collection limited to 5 chars
Input:
Car
Collection
Limited
stackoverflow
Output:
car
colle
limit
stack
word.Substring(0,5) throws exception (length)
word.Take(10) is not good idea, too...
Any good ideas ??
LINQ to objects for this scenario? You can do a select as in this:
from w in words
select new
{
Word = (w.Length > 5) ? w.Substring(0, 5) : w
};
Essentially, ?: gets you around this issue.
var words = new [] { "Car", "Collection", "Limited", "stackoverflow" };
IEnumerable<string> cropped = words.Select(word =>
word[0..Math.Min(5, word.Length)]);
Ranges are available in C# 8, otherwise you'll need to do:
IEnumerable<string> cropped = words.Select(word =>
word.Substring(0, Math.Min(5, word.Length)));
Something you can do, is
string partialText = text.Substring(0, Math.Min(text.Length, 5));
I believe the kind of answer you were looking for would look like this:
var x = new string[] {"car", "Collection", "Limited", "stackoverflow" };
var output = x.Select(word => String.Join("", word.Take(5).ToList()));
The variable "output" contains the result:
car
Colle
Limit
stack
and the string "car" doesn't throw an exception.
But while Join and Take(5) works, it's generally much simpler to use, as was suggested in another answer,
subString = word.Substring(0,Math.Min(5,word.Length));
The latter code is more human-readable and lightweight, though there is definitely a slight coolness factor to using Linq on a string to take the first five characters, without having to check the length of the string.

Categories