C# Sort list, Logical Compare - c#

i have the following problem
I have a list with strings for example (100_1, 100_2 .... , 100_10)
I sort the list with following code
extraImgsRaw.Sort((photo1, photo2) => photo1.CompareTo(photo2));
the result of this is : 100_1, 100_10, 100_2, 100_3 and so on
the result that I want is a logical compare like 100_1, 100_2 and then 100_10 so I prefer a
Natural numeric sort not a Alphabetic sort.
Do I need to write my own compare class that implements the ICompare interface or there is a build method in LINQ that does that?
thank you in advance

There's nothing built-in, but if the data is exactly as shown in your question then it shouldn't be too difficult to knock up a Comparison<T> to do this for you:
extraImgsRaw.Sort((x, y) =>
{
// error checking etc removed for brevity
int[] xi = x.Split('_').Select(int.Parse).ToArray();
int[] yi = y.Split('_').Select(int.Parse).ToArray();
int c = xi[0].CompareTo(yi[0]);
return (c != 0) ? c : xi[1].CompareTo(yi[1]);
});

Split and compare elements,
Here is one I wrote for 'versions'.
/// <summary>
/// Only works for version numbers in the form a ( . b ( . c ( . d )? )? )?
/// </summary>
public class VersionComponents : IComparable<VersionComponents>
{
readonly int[] components;
int[] GetComponents(string cpnumber)
{
var tokens = cpnumber.Split(".".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries);
return tokens.Select(x => Convert.ToInt32(x)).ToArray();
}
public VersionComponents(string cpnumber)
{
components = GetComponents(cpnumber);
}
public int this[int index]
{
get { return components.Length > index ? components[index] : 0; }
}
public int CompareTo(VersionComponents other)
{
for (int i = 0; i < components.Length ||
i < other.components.Length; i++)
{
var diff = this[i].CompareTo(other[i]);
if (diff != 0)
{
return diff;
}
}
return 0;
}
}

Related

Merging two arrays in C# [duplicate]

This question already has answers here:
How do I concatenate two arrays in C#?
(23 answers)
Joining two lists together
(15 answers)
how to sort a string array by alphabet?
(3 answers)
Sort a list alphabetically
(5 answers)
Closed 4 years ago.
In my code I have 2 arrays and I want to merge the both using right sequence. and save value to 3rd array. I tried a lot but could not find perfect solution.
public void Toolchange_T7()
{
for (int T7 = 0; T7 < sModulename_listofsafetysensor.Length; T7++)
{
if (sModulename_listofsafetysensor[T7] != null && sModulename_listofsafetysensor[T7].Contains("IR") && sModulename_listofsafetysensor[T7].Contains("FS"))
{
sElement_toolchanger[iET7] = sModulename_listofsafetysensor[T7];
iET7++;
}
}
for (int T7 = 0; T7 < sDesignation_toolchanger_t7.Length; T7++)
{
if (sDesignation_toolchanger_t7[T7] != null && sDesignation_toolchanger_t7[T7].Contains("IR") && sDesignation_toolchanger_t7[T7].Contains("FW"))
{
sDesignation_toolchanger[iMT7] = sDesignation_toolchanger_t7[T7];
iMT7++;
}
}
}
sElement_toolchanger contains:
++ST010+IR001+FW001
++ST010+IR002+FW001
++ST010+IR006+FW001
sDesignation_toolchanger contains:
++ST010+IR001.FS001
++ST010+IR001.FS002
++ST010+IR002.FS001
++ST010+IR002.FS002
++ST010+IR006.FS001
++ST010+IR006.FS002
My desired output is:
++ST010+IR001+FW001
++ST010+IR001.FS001
++ST010+IR001.FS002
++ST010+IR002+FW001
++ST010+IR002.FS001
++ST010+IR002.FS002
++ST010+IR006+FW001
++ST010+IR006.FS001
++ST010+IR006.FS002
It will be very helpful if some one know perfect solution
using System.Collections;
var mergedAndSorted = list1.Union(list2).OrderBy(o => o);
Simplest would be to:
Convert the arrays to lists:
var List1 = new List<string>(myArray1);
var List2 = new List<string>(myArray2);
Merge the two lists together:
List1.AddRange(List2);
and sort them.
List1.Sort();
According to what you said in the comments, here is a small function that will take one item from the first array, then two from the second array and so on to make a third one.
This code could be improved...
static void Main(string[] args)
{
string[] t1 = new string[] { "a", "b", "c" };
string[] t2 = new string[] { "a1", "a2", "b1", "b2", "c1", "c2" };
List<string> merged = Merge(t1.ToList(), t2.ToList());
foreach (string item in merged)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
private static List<T> Merge<T>(List<T> first, List<T> second)
{
List<T> ret = new List<T>();
for (int indexFirst = 0, indexSecond = 0;
indexFirst < first.Count && indexSecond < second.Count;
indexFirst++, indexSecond+= 2)
{
ret.Add(first[indexFirst]);
ret.Add(second[indexSecond]);
ret.Add(second[indexSecond + 1]);
}
return ret;
}
An example here
From your given sample Output, it doesn't look to be alphabetically sorted. In that scenario, you would need to use a Custom Comparer along with your OrderBy Clause after taking the Union.
For example, (since your sorting algorithm is unknown, am assuming one here for showing the example)
var first = new[]{"++ST010+IR001+FW001","++ST010+IR002+FW001","++ST010+IR006+FW001"};
var second = new[]{"++ST010+IR001.FS001",
"++ST010+IR001.FS002",
"++ST010+IR002.FS001",
"++ST010+IR002.FS002",
"++ST010+IR006.FS001",
"++ST010+IR006.FS002"};
var customComparer = new CustomComparer();
var result = first.Union(second).OrderBy(x=>x,customComparer);
Where your custom comparer is defined as
public class CustomComparer : IComparer<string>
{
int IComparer<string>.Compare(string x, string y)
{
var items1 = x.ToCharArray();
var items2 = y.ToCharArray();
var temp = items1.Zip(items2,(a,b)=> new{Item1 = a, Item2=b});
var difference = temp.FirstOrDefault(item=>item.Item1!=item.Item2);
if(difference!=null)
{
if(difference.Item1=='.' && difference.Item2=='+')
return 1;
if(difference.Item1=='+' && difference.Item2=='.')
return -1;
}
return string.Compare(x,y);
}
public int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
Output
Here's a solution if the two input arrays are already sorted. Since I suspect your comparison function is non-straightforward, I include a separate comparison function (not very efficient, but it will do). The comparison function splits the string into two (the alpha part and the numeric part) and then compares them so that the numeric part is more "important" in the sort.
Note that it doesn't do any sorting - it relies on the inputs being sorted. It just picks the lower valued item from the current position in one of the two arrays for transfer to the output array. The result is O(N).
The code makes a single pass through the two arrays (in one loop). When I run this, the output is AA00, AA01, AB01, AB02, AC02, AA03, AA04. I'm sure there are opportunities to make this code cleaner, I just popped it off. However, it should give you ideas to continue:
public class TwoArrays
{
private string[] _array1 = {"AA01", "AB01", "AB02", "AC02"};
private string[] _array2 = {"AA00", "AA03", "AA04"};
public void TryItOut()
{
var result = ConcatenateSorted(_array1, _array2);
var concatenated = string.Join(", ", result);
}
private int Compare(string a, string b)
{
if (a == b)
{
return 0;
}
string a1 = a.Substring(0, 2);
string a2 = a.Substring(2, 2);
string b1 = b.Substring(0, 2);
string b2 = b.Substring(2, 2);
return string.Compare(a2 + a1, b2 + b1);
}
private string[] ConcatenateSorted(string[] a, string[] b)
{
string[] ret = new string[a.Length + b.Length];
int aIndex = 0, bIndex = 0, retIndex = 0;
while (true) //do this forever, until time to "break" and return
{
if (aIndex >= a.Length && bIndex >= b.Length)
{
return ret;
}
if (aIndex >= a.Length)
{
ret[retIndex++] = b[bIndex++];
continue;
}
if (bIndex >= b.Length)
{
ret[retIndex++] = a[aIndex++];
continue;
}
if (Compare(a[aIndex], b[bIndex]) > 0)
{
ret[retIndex++] = b[bIndex++];
}
else
{
ret[retIndex++] = a[aIndex++];
}
}
}
}
To get it to work with your data, you need to change the input arrays and provide your own comparison function (which you could pass in as a delegate).

IComparer for string that checks if x starts with y

I've got an array of strings and I need to get all strings, that start with some 'prefix'. I wanna use Array.BinarySearch(). Is it possible? And how should I write a comparer if so?
No, you cannot use BinarySearch in this case. You could use Enumerable.Where instead:
Dim query = From str In array Where str.StartsWith("prefix")
or with (ugly in VB.NET) method synatx:
query = array.Where(Function(str) str.StartsWith("prefix"))
Edit: whoops, C#
var query = array.Where(s => s.StartsWith("prefix"));
Use ToArray if you want to create a new filtered array.
It's easy to create your own StartsWithComparer:
class StartsWithComparer : IComparer<string>
{
public int Compare(string a, string b) {
if(a.StartsWith(b)) {
return 0;
}
return a.CompareTo(b);
}
}
As others pointed out, this will only return one index. You can have a couple of helpers to return all items:
IEnumerable<string> GetBefore(IList<string> sorted, int foundIndex, string prefix) {
for(var i = foundIndex - 1; i >= 0; i--) {
if(sorted[i].StartsWith(prefix)) {
yield return sorted[i];
}
}
}
IEnumerable<string> GetCurrentAndAfter(IList<string> sorted, int foundIndex, string prefix) {
for(var i = foundIndex; i < sorted.Count; i++) {
if(sorted[i].StartsWith(prefix)) {
yield return sorted[i];
}
}
}
Then to use it:
var index = sorted.BinarySearch("asdf", new StartsWithComparer());
var previous = GetBefore(sorted, index, "asdf");
var currentAndAfter = GetCurrentAndAfter(sorted, index, "asdf");
You can wrap the whole thing in your own class, with a single method that returns all items that start with your prefix.

Sort List which I use to fill ComboBox C#

Hello I've got code to fill ComboBox like this:
public ListBox fillComboBox(ListBox cb)
{
cb.Items.Clear();
foreach(string[] s in SO)
{
if (s[1].Split(',')[1].Equals("G5IDD"))
{
cb.Items.Add(s[1].Split(',')[3]);
}
}
cb.Sorted = true;
return cb;
}
In result I've got values sorted like this:
2.1
2.10
2.15
2.2
2.20
But I want it sorted like this
2.1
2.2
2.10
2.15
2.20
SO is ArrayList build by Arrays of string.
Can someone help me sort it way I want?
Thanks in advance
EDIT: Values can be like
4545_3434.2.1/1
4545_3434.2.1/2
4545_3434.2.2
4545_3434.2.2/1
Here is what I would suggest. No need for IComparer. This obviously assumes that the input will always be in the format of [int].[int].
public ListBox fillComboBox(ListBox cb)
{
cb.Items.Clear();
foreach(string[] s in SO.ToArray().OrderBy(s => Int32.Parse(s.ToString().Split('.')[0])).ThenBy(s => Int32.Parse(s.ToString().Split('.')[1])))
{
if (s[1].Split(',')[1].Equals("G5IDD"))
{
cb.Items.Add(s[1].Split(',')[3]);
}
}
return cb;
}
If you want the numbers treated as version, you can use the Version class.
public Version String2Version(string str)
{
string[] parts = str.Split('.');
return new Version(Convert.ToInt32(parts[0]), Convert.ToInt32(parts[1]));
}
public ListBox fillComboBox(ListBox cb)
{
cb.Items.Clear();
foreach(string[] s in SO)
{
if (s[1].Split(',')[1].Equals("G5IDD"))
{
cb.Items.Add( String2Version(s[1].Split(',')[3]));
}
}
cb.Sorted = true;
return cb;
}
You can use custom comparer(IComparer) in your code to achieve it,
I have provide an example.You have to change the logic of
public int Compare(object a, object b)
To achieve your specific requirement
class Program
{
private static ArrayList arl;
public static void Main(string[] args)
{
arl = new ArrayList();
arl.Add("2.1/1");
arl.Add("2.1/2");
arl.Add("2.2");
arl.Add("2.2/1");
arl.Sort(new IDDSort());
foreach (var value in arl)
{
Console.WriteLine(value);
}
Console.Read();
}
}
public class IDDSort : IComparer
{
public int Compare(object x, object y)
{
if (x == y) return 0;
var xparts = x.ToString().Replace("/","").Split('.');
var yparts = y.ToString().Replace("/", "").Split('.');
var length = new[] { xparts.Length, yparts.Length }.Max();
for (var i = 0; i < length; i++)
{
int xint;
int yint;
if (!Int32.TryParse(xparts.ElementAtOrDefault(i), out xint)) xint = 0;
if (!Int32.TryParse(yparts.ElementAtOrDefault(i), out yint)) yint = 0;
if (xint > yint) return 1;
if (yint > xint) return -1;
}
//they're equal value but not equal strings, eg 1 and 1.0
return 0;
}
}
This should work:
Array.Sort(SO, new AlphanumComparatorFast());
(if SO is the array with your version numbers)
Derive from ListBox and override Sort method implementing your own algorithm. For example the one suggested by Feroc.
Check link below:
http://msdn.microsoft.com/pl-pl/library/system.windows.forms.listbox.sort(v=vs.110).aspx
Hello I achieved my goal by coping part of array to list. (I needed only names on list not whole arrays). And used lambda expression for it.
list = list.OrderBy(x => Int32.Parse(x.Split('.')[2].Split('/')[0]))
.ThenBy(x =>
Int32.Parse(x.Split('.')[2].Split('/').Length > 1 ? x.Split('.')[2].Split('/')[1] : x.Split('.')[2].Split('/')[0])
).ToList();
So now I got it sorted like this:
0001.1
0001.2
0001.2/1
0001.2/2
0001.3
0001.3/1
etc.
Thanks everyone for help.

Get Max() of alphanumeric value

I have a dictionary containg ID which are alphanumeric (e.g. a10a10 & d10a9) from which I want the biggest ID, meaning 9 < 10 < a ...
When I use the following code, d10a9 is MAX since 9 is sorted before 10
var lsd = new Dictionary<string, string>();
lsd.Add("a", "d10a10");
lsd.Add("b", "d10a9");
string max = lsd.Max(kvp => kvp.Value);
How can I get the Max value of the IDs with the Longest string combined?
I think you may try to roll your own IComparer<string>
class HumanSortComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// your human sorting logic here
}
}
Usage:
var last = collection.OrderBy(x => x.Value, new HumanSortComparer()).LastOrDefault();
if (last != null)
string max = last.Value;
this works like a charm assuming IDs always start with "d10a":
int max = lsd.Max(kvp => Convert.ToInt32(kvp.Value.Substring(4)));
Console.Write(string.Format("d10a{0}", max));
One way would be to do this
string max =lsd.Where(kvp=>kvp.Value.Length==lsd.Max(k=>k.Value.Length)).Max(kvp => kvp.Value);
however I think that this method would evalute the max length for each item so you may be better to extract it to a variable first
int maxLength=lsd.Max(kvp=>kvp.Value.Length);
string max = lsd.Where(kvp=>kvp.Value.Length == maxLength).Max(kvp => kvp.Value);
If you are going to have null strings in there you may need to perform null checks too
int maxLength=lsd.Max(kvp=>(kvp.Value??String.Empty).Length);
string max = lsd.Where(kvp=>(kvp.Value??String.Empty).Length == maxLength).Max(kvp => kvp.Value);
Alternatively treat your string as Base36 number and convert to long for the max function and then convert back again to get the max string.
string max =lsd.Max(tvp=>tvp.Value.FromBase36()).ToBase36();
public static class Base36 {
public static long FromBase36(this string src) {
return src.ToLower().Select(x=>(int)x<58 ? x-48 : x-87).Aggregate(0L,(s,x)=>s*36+x);
}
public static string ToBase36(this long src) {
StringBuilder result=new StringBuilder();
while(src>0) {
var digit=(int)(src % 36);
digit=(digit<10) ? digit+48 :digit+87;
result.Insert(0,(char)digit);
src=src / 36;
}
return result.ToString();
}
}
Finally just just the Agregate extension method instead of Max as this lets you do all the comparison logic....
lsd.Agregate(string.Empty,(a,b)=> a.Length == b.Length ? (a>b ? a:b) : (a.Length>b.Length ? a:b));
This could doesn't have null checks but you easily add them in.
I think if you did this:
var max = lsd.OrderByDescending(x => x.Value)
.GroupBy(x => x.Value.Length)
.OrderByDescending(x => x.Key)
.SelectMany(x => x)
.FirstOrDefault();
It may give you what you want.
You need StringComparer.OrdinalIgnoreCase.
Without the need to use linq, the function that do that is quite simple.
Complexity is, of course, O(n).
public static KeyValuePair<string, string> FindMax(IEnumerable<KeyValuePair<string, string>> lsd)
{
var comparer = StringComparer.OrdinalIgnoreCase;
var best = default(KeyValuePair<string, string>);
bool isFirst = true;
foreach (KeyValuePair<string, string> kvp in lsd)
{
if (isFirst || comparer.Compare(kvp.Value, best.Value) > 0)
{
isFirst = false;
best = kvp;
}
}
return best;
}
Okay - I think you need to first turn each key into a series of strings and numbers - since you need the whole number to be able to determine the comparison. Then you implement an IComparer - I've tested this with your two input strings as well as with a few others and it appears to do what you want. The performance could possibly be improved - but I was brainstorming it!
Create this class:
public class ValueChain
{
public readonly IEnumerable<object> Values;
public int ValueCount = 0;
private static readonly Regex _rx =
new Regex("((?<alpha>[a-z]+)|(?<numeric>([0-9]+)))",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public ValueChain(string valueString)
{
Values = Parse(valueString);
}
private IEnumerable<object> Parse(string valueString)
{
var matches = _rx.Matches(valueString);
ValueCount = matches.Count;
foreach (var match in matches.Cast<Match>())
{
if (match.Groups["alpha"].Success)
yield return match.Groups["alpha"].Value;
else if (match.Groups["numeric"].Success)
yield return int.Parse(match.Groups["numeric"].Value);
}
}
}
Now this comparer:
public class ValueChainComparer : IComparer<ValueChain>
{
private IComparer<string> StringComparer;
public ValueChainComparer()
: this(global::System.StringComparer.OrdinalIgnoreCase)
{
}
public ValueChainComparer(IComparer<string> stringComparer)
{
StringComparer = stringComparer;
}
#region IComparer<ValueChain> Members
public int Compare(ValueChain x, ValueChain y)
{
//todo: null checks
int comparison = 0;
foreach (var pair in x.Values.Zip
(y.Values, (xVal, yVal) => new { XVal = xVal, YVal = yVal }))
{
//types match?
if (pair.XVal.GetType().Equals(pair.YVal.GetType()))
{
if (pair.XVal is string)
comparison = StringComparer.Compare(
(string)pair.XVal, (string)pair.YVal);
else if (pair.XVal is int) //unboxing here - could be changed
comparison = Comparer<int>.Default.Compare(
(int)pair.XVal, (int)pair.YVal);
if (comparison != 0)
return comparison;
}
else //according to your rules strings are always greater than numbers.
{
if (pair.XVal is string)
return 1;
else
return -1;
}
}
if (comparison == 0) //ah yes, but were they the same length?
{
//whichever one has the most values is greater
return x.ValueCount == y.ValueCount ?
0 : x.ValueCount < y.ValueCount ? -1 : 1;
}
return comparison;
}
#endregion
}
Now you can get the max using OrderByDescending on an IEnumerable<ValueChain> and FirstOrDefault:
[TestMethod]
public void TestMethod1()
{
List<ValueChain> values = new List<ValueChain>(new []
{
new ValueChain("d10a9"),
new ValueChain("d10a10")
});
ValueChain max =
values.OrderByDescending(v => v, new ValueChainComparer()).FirstOrDefault();
}
So you can use this to sort the string values in your dictionary:
var maxKvp = lsd.OrderByDescending(kvp => new ValueChain(kvp.Value),
new ValueChainComparer()).FirstOrDefault();

A shorter way to write the following function in C#?

I have this function -
public int GetAvgResult()
{
var weeklyvalues=GetWeeklyValues();//gets list of weekly values.
if (weeklyvalues.Count == 0)
return 0;
return (weeklyvalues.Sum() / weeklyvalues.Count);
}
Is there a shorter way to write this using ?: or maybe something else ?
public double GetAvgResult()
{
// Assumes GetWeeklyValues() never returns null.
return GetWeeklyValues().DefaultIfEmpty().Average();
}
Do note that this returns a double , which I assume is what you really want (the average of a bunch of integers is logically not an integer). You can cast it to int if necessary, or if you want to stick with integer math all the way:
var seq = GetWeeklyValues().DefaultIfEmpty();
return seq.Sum() / seq.Count();
public int GetAvgResult()
{
var weeklyvalues = GetWeeklyValues();
return (weeklyvalues.Count != 0) ? (weeklyvalues.Sum() / weeklyvalues.Count) : 0;
}
or:
public int GetAvgResult()
{
return GetWeeklyValues().DefaultIfEmpty().Average();
}
public int GetAvgResult()
{
var weeklyvalues = GetWeeklyValues(); //gets list of weekly values.
return weeklyvalues.Count == 0 ? 0 : weeklyvalues.Sum() / weeklyvalues.Count;
}
That's as short as I'd attempt to make it. Is there a specific reason (other than code golf) you're trying for a low character count?
public int GetAvgResult()
{
var weeklyvalues = GetWeeklyValues();//gets list of weekly values.
return (weeklyvalues.Count == 0) ? 0 : (weeklyvalues.Sum() / weeklyvalues.Count );
}
public int GetAvgResult()
{
var weeklyvalues=GetWeeklyValues();//gets list of weekly values.
return weeklyvalues.Count == 0
? 0
: (weeklyvalues.Sum() / weeklyvalues.Count);
}

Categories