Select two characters from string in LINQ - c#

I have to convert an hexadecimal string starting with 0x or an hexadecimal string converted with BitConverter to a byte array. For this I use this function which works very well:
public static byte[] ConvertToByteArray(this string s)
{
if (s.StartsWith("0x"))
{
var ret = new byte[(s.Length - 2) / 2];
for (int i = 2; i < s.Length; i += 2)
{
ret[(i - 2) / 2] = Convert.ToByte(string.Concat(s[i], s[i + 1]), 16);
}
return ret;
}
else
return s.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray();
}
example input, coming from a sort of a network device (think of it as a message logged with wireshark ) :
byte[] data1 = "0x020206000000022800A601585E40".ConvertToByteArray();
byte[] data2 = "02-02-06-00-00-00-02-28-00-A6-01-58-5E-40".ConvertToByteArray();
CollectionAssert.AreEqual(data1, data2);
Now I would like to understand how to write the first possibility (starts with 0x) in LINQ to get rid of this 1990ish for loop.
Is there a way to Select two characters at the same time, or is there a more elegant way then mine?

This is the linq equivalent of your loop regardless of any other consideration:
if (s.StartsWith("0x"))
{
return
s.Skip(2)
.Select((x,i) => new {index = i, value = x})
.GroupBy(pair => pair.index / 2)
.Select(grp => string.Join("", grp.Select(x=>x.value)))
.Select(x => Convert.ToByte(x,16))
.ToArray();
}
But this seems to be a solution to your consideration of not having a 90ish code:
public static byte[] ConvertToByteArray(this string s)
{
string tmp = s.Replace("0x","").Replace("-","");
tmp = Regex.Replace(tmp, ".{2}", "$0-");
return tmp.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray();
}

Well i think first you can have a look at this
Using #jdweng sample input.
string input = "0x0123456789ABCDE".Replace("0x", string.Empty);
long intValue = long.Parse(s, System.Globalization.NumberStyles.HexNumber);
Now if you have long you can convert it to byte[] easly.
byte[] array = BitConverter.GetBytes(intValue);
I know it it's not LINQ solution to your problem but it's clean and simple.

Update:
After reading OP's comment, I see he wants to be able to consume hex strings of an arbitrary length. I'd be tempted to use an iterator function to return your hex pairs, to match the result of your split. Then you can feed either enumerable through the same conversion, like so:
public byte[] ConvertToByteArray(string s)
{
IEnumerable<string> query = Enumerable.Empty<string>();
if (s.StartsWith("0x"))
{
query = IterateHexPairs(s.Substring(2));
}
else
{
query = s.Split('-');
}
return query.Select(b => Convert.ToByte(b, 16)).ToArray();
IEnumerable<string> IterateHexPairs(string hexLiteral)
{
char? previousNibble = null;
foreach (var nibble in hexLiteral)
{
if (previousNibble != null)
{
yield return new string(new char[] { previousNibble.Value, nibble });
previousNibble = null;
}
else
{
previousNibble = nibble;
}
}
}
}
This avoids having to duplicate your conversion logic, as they both get fed from an IEnumerable. The only difference is the source of the IEnumerable. Change the code that gives you the enumerable as you see fit. I thought an Iterator function would be more maintainable, but you could bodge a Linq query to achieve the same result, like this:
public byte[] ConvertToByteArray(string s)
{
IEnumerable<string> query = Enumerable.Empty<string>();
if (s.StartsWith("0x"))
{
// omit the 0x
query = s.Skip(2)
// get the char and index, so we can pair them up
.Select((c, i) => new { Char = c, Index = i })
// group them into pairs
.GroupBy(o => o.Index / 2)
// select them as new strings, so they can be converted
.Select(g => new string(g.Select(o => o.Char).ToArray()));
}
else
{
query = s.Split('-');
}
return query.Select(b => Convert.ToByte(b, 16)).ToArray();
}

Related

Retrieving Numeric value before a decimal in a string value

I am working on a routine in C#
I have a list of alphanumeric sheet numbers that I would like to retrieve the numbers before the decimal to use in my routine.
FP10.01-->10
M1.01-->1
PP8.01-->8
If possible, how can something like this be achieved as either a string or integer?
You could use a regex:
Regex r = new Regex("([0-9]+)[.]");
string s = "FP10.01";
var result = Convert.ToInt32(r.Match(s).Groups[1].ToString()); //10
string input = "FP10.01";
string[] _input = input.Split('.');
string num = find(_input[0]);
public string find(string input)
{
char[] _input = input.ToArray();
int number;
string result = null;
foreach (var item in _input)
{
if (int.TryParse(item.ToString(), out number) == true)
{
result = result + number;
}
}
return result;
}
To accumulate the resulting elements into a list, you can do something like:
List<string> myList = new List<string>(){ "FP10.01","M1.01", "PP8.01"};
List<int> resultSet =
myList.Select(e =>
Regex.Replace(e.Substring(0, e.IndexOf('.')), #"[^\d]", string.Empty))
.Select(int.Parse)
.ToList();
This will take each element in myList and in turn, take a substring of each element from index 0 until before the . and then replace all the non-numeric data with string.Empty and then finally parse the string element into an int and store it into a list.
another variant would be:
List<int> resultSet =
myList.Select(e => e.Substring(0, e.IndexOf('.')))
.Select(e => string.Join(string.Empty, e.Where(char.IsDigit)))
.Select(int.Parse)
.ToList();
or if you want the elements to be strings then you could do:
List<string> resultSet =
myList.Select(e => e.Substring(0, e.IndexOf('.')))
.Select(e => string.Join(string.Empty, e.Where(char.IsDigit)))
.ToList();
To retrieve a single element of type string then you can create a helper function as such:
public static string GetValueBeforeDot(string input){
return input.Substring(0, input.IndexOf('.'))
.Where(char.IsDigit)
.Aggregate(string.Empty, (e, a) => e + a);
}
To retrieve a single element of type int then the helper function should be:
public static int GetValueBeforeDot(string input){
return int.Parse(input.Substring(0, input.IndexOf('.'))
.Where(char.IsDigit)
.Aggregate(string.Empty, (e, a) => e + a));
}
This approach removes alphabet characters by replacing them with an empty string. Splitting on the '.' character will leave you with a two element array consisting of numbers at index 0 and after decimal values at index 1.
string input = "FP10.01";
var result = Regex.Replace(input, #"([A-Za-z]+)", string.Empty).Split('.');
var beforeDecimalNumbers = result[0]; // 10
var afterDecimalNumbers = result[1]; // 01

c# ordering strings with different formats

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!

Int.Parse and Sorting

I have the following code..
var strings = new[] {
"FD1","FD5","FD10","FD102","FD105","FD10","FD32","FD80", "FD31", "FD21", "FDnon"
};
strings = strings.Select(str => new
{
str,
num = int.Parse(String.Concat(str.Trim('F', 'D'))),
})
.OrderBy(x => x.num)
.Select(x => x.str)
.ToArray();
However this fails when it gets to "FDnon" as there are no numbers in it,
How do I get this to work with "FDnon" sorted at the top?
var result = strings.OrderBy(x =>
{
int y = int.MinValue;
int.TryParse(x.Substring(2), out y);
return y;
});
If you want custom ordering, supply your custom SortMethod
var sorted = strings
.OrderBy(SpecialSort)
.ToList();
public static int SpecialSort(string value)
{
int sortOrder = 0;
string numberPart = value.Trim('F', 'D');
int.TryParse(numberPart, out sortOrder);
return sortOrder;
}
Edit: Changed solution to account for the sorting of numbers in String.
If you want FDnon to be presented at the top you can use something like:
strings = strings.Select(str => new
{
str,
num = str=="FDnon" ? Int32.MaxValue : Int32.Parse(String.Concat(str.Trim('F', 'D')))
})
.OrderBy(x => x.num)
.Select(x => x.str)
.ToArray();
This code just skips FDnon conversion. If you want to allow other values you should be more specific on what are you going to accept.
You need a more sophisticated parse that converts FDnon into an integral value that will sort last (in this case Int32.MaxValue would do).
Something like:
var res = strings.Select(s => {
var numPart = s.Trim('F', 'D');
var i;
if (!Int32.TryParse(numPart, out i)) {
i = Int32.MaxValue;
}
return new {
str = s,
num = i
};
}.OrderBy …
If the strings always start with two letters, you could also use Substring(2). To check if there's a numeric part, you can use Enumerable.All(Char.IsDigit) (assumes that all chars are digits after the first two lewtters):
strings = strings.Select(str => new
{
str,
num = str.Substring(2).All(Char.IsDigit) ? int.Parse(str.Substring(2)) : int.MinValue
})
.OrderBy(x => x.num)
.Select(x => x.str)
.ToArray();
DEMO

Comparing strings with hyphen

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.

Is there a LINQ function for getting the longest string in a list of strings?

Is there a LINQ function for this is or would one have to code it themselves like this:
static string GetLongestStringInList()
{
string longest = list[0];
foreach (string s in list)
{
if (s.Length > longest.Length)
{
longest = s;
}
}
return longest;
}
This will do it with only one loop iteration:
list.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur);
You can use this: list.OrderByDescending(s => s.Length).First();
var list = new List<string>(); // or string[] or any
list.Add("a");
list.Add("ccc");
list.Add("bb");
list.Add("eeeee");
list.Add("dddd");
// max-length
var length = list.Max(s => s.Length);
// biggest one
var biggest = list.FirstOrDefault(s => s.Length == length);
// if there is more that one by equal length
var biggestList = list.Where(s => s.Length == length);
// by ordering list
var biggest = list.OrderByDescending(s => s.Length).FirstOrDefault();
// biggest-list by LINQ
var bigList2 = from s in list where s.Length == list.Max(a => a.Length) select s;
// biggest by LINQ
var biggest2 = bigList2.FirstOrDefault();
The method you want is typically called "MaxBy" and it is unfortunately not included in the standard set of sequence operators. Fortunately it is very easy to write yourself. See this answer for an implementation:
Linq group by with a sub query
I thought there was a better way to get the longest string in a list of strings using the lambda expressions. One such way is as below.
string longestString = list.Max(arr => arr);
Hope this works good for the answer seekers.
To get the longest string in list of object/string try this:
List<String> list = new List<String>();
list.Add("HELLO");
list.Add("HELLO WORLD");
String maxString = list.OrderByDescending(x => x.Length).First();
The variable maxString will contain the value "HELLO WORLD"
Add a ThenBy() to guarantee a return order if there are multiple strings with the same length
var longest = list.OrderByDescending(s => s.Length)
.ThenBy(s => s)
.FirstOrDefault();

Categories

Resources