Find duplicate of array of Bitarray - c#

I have array of Bitarray (each element consists of 128 bits) and I want to find the duplication of each element of it.
I've tried this code:
for (int i = 0; i < final.Length; i++)
{
final[i] = BitArrayToStr(sub_string[i]);
}
var duplicates = final
.GroupBy(s => s)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
var dict = new Dictionary<string,int>();
foreach (var num in final)
{
if (!dict.ContainsKey(num))
{
dict.Add(num, 0);
}
dict[num]++;
}
bool dup=false;
foreach (var kvp in dict)
{
if (kvp.Value != 1)
{
Console.WriteLine("{0} repeats {1} times", kvp.Key, kvp.Value);
dup = true;
}
}
if (dup == false)
{ Console.WriteLine("NO DUPLICATE"); }
Console.WriteLine("END");
static String BitArrayToStr(BitArray ba) //**** **** convert bit
array to string of chars ****
{
byte[] strArr = new byte[ba.Length / 8];
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
for (int i = 0; i < ba.Length / 8; i++)
{
for (int index = i * 8, m = 1; index < i * 8 + 8; index++, m *= 2)
{
strArr[i] += ba.Get(index) ? (byte)m : (byte)0;
}
}
return encoding.GetString(strArr);
}
but I don't want to convert it to string because the results will be false.
I want to know the duplication of each element of array of bitarray.
Can anyone help?

It's pretty easy to convert a BitArray to a string of 1s and 0s:
static public string ToBitString(this BitArray This)
{
return string.Join("", This.Cast<bool>().Select( bit => bit ? 1 : 0 ));
}
With this knowledge, we can write an EqualityComparer for BitArray, like this:
public class BitArrayComparer : IEqualityComparer<BitArray>
{
public bool Equals(BitArray a, BitArray b)
{
return a.ToBitString() == b.ToBitString();
}
public int GetHashCode(BitArray o)
{
return o.ToBitString().GetHashCode();
}
}
If we pass the EqualityComparer to GroupBy we can now get our counts very easily:
var list = new List<BitArray>
{
new BitArray(new bool[] { true, true } ),
new BitArray(new bool[] { true, false } ),
new BitArray(new bool[] { true, false } ),
new BitArray(new bool[] { false, false } )
};
var groups = list.GroupBy( item => item, new BitArrayComparer() );
foreach (var g in groups)
{
Console.WriteLine("Value '{0}' occurs {1} times", g.Key.ToBitString(), g.Count());
}
Output:
Value '11' occurs 1 times
Value '10' occurs 2 times
Value '00' occurs 1 times
Link to example code on DotNetFiddle

Related

How to sort on 2 distinct rules in List<string>

I need to sort a List<string> following 2 rules.
My string element will always be formatted as XXXnnnnE or XXXnnnnD where X are capitalized letters from A to Z and n are digit from 0 to 9.
I want to sort my list alphabetically, but I want E string to come before D string as shown below
DFG0001D
AWK0007E
ORK0127E
AWK0007D
DFG0001E
ORK0127D
need to be sorted as
AWK0007E
AWK0007D
DFG0001E
DFG0001D
ORK0127E
ORK0127D
How could I achieve this ?
Thanks for help
Here is snippet how you can do this with Linq OrderBy and ThenByDescending operations:
string[] arr = { "DFG0001D", "AWK0007E", "ORK0127E", "AWK0007D", "DFG0001E", "ORK0127D" };
arr = arr
.OrderBy(r => r.Substring(0, 7))
.ThenByDescending(s => s.Substring(7, 1))
.ToArray();
you can use a custom delegate and compare the 1st 3 chars and the last one:
List<string> x = new List<string>();
x.Add("DFG0001D");
x.Add("AWK0007E");
x.Add("ORK0127E");
x.Add("AWK0007D");
x.Add("DFG0001E");
x.Add("ORK0127D");
x.Sort(delegate(string c1, string c2) {
string a = c1.Substring(0, 3)+c1.Substring(c1.Length-1, 1);
string b = c2.Substring(0, 3)+c2.Substring(c2.Length-1, 1);
return (a.CompareTo(b));
});
Console.WriteLine("After sort...");
foreach (string i in x)
{
Console.WriteLine(i);
}
Fiddle example : https://dotnetfiddle.net/YAzvB4
var list = new List<string>{
"DFG0001D",
"AWK0007E",
"ORK0127E",
"AWK0007D",
"DFG0001E",
"ORK0127D"
};
list.Sort((str1, str2) => {
var eq = string.Compare(str1.Substring(0, str1.Length - 1), str2.Substring(0, str2.Length - 1));
if (eq == 0)
eq = string.Compare(str2[str2.Length - 1].ToString(), "E");
return eq;
});
foreach (var str in list)
Console.WriteLine(str);
Output:
AWK0007E
AWK0007D
DFG0001E
DFG0001D
ORK0127E
ORK0127D
just implement your own comparer like this:
class CustomStringComparer : IComparer<string>
{
private readonly IComparer<string> _baseComparer;
public CustomStringComparer(IComparer<string> baseComparer)
{
_baseComparer = baseComparer;
}
public int Compare(string x, string y)
{
// strings are completely same
if (_baseComparer.Compare(x, y) == 0)
{
return 0;
}
// strings are completely same except last char
if (_baseComparer.Compare(x.Substring(0, x.Length - 2), y.Substring(0, y.Length - 2)) == 0)
{
// if last char is E then win
return x.Last() == 'E' ? -1 : 1;
}
// defaut compare everything else
return _baseComparer.Compare(x, y);
}
}
Then you are able doing this:
static void Main(string[] args)
{
List<string> list = new List<string>()
{
"DFG0001D",
"AWK0007E",
"ORK0127E",
"AWK0007D",
"DFG0001E",
"ORK0127D"
};
list.Sort(new CustomStringComparer(StringComparer.CurrentCulture));
foreach (var item in list)
{
Console.WriteLine(item);
}
}
And output is this:
AWK0007E
AWK0007D
DFG0001E
DFG0001D
ORK0127E
ORK0127D

How to split a item in array to two item with conditional?

I have an example above.
Have array like:
array[0] = TODO 06:15PMJoin Michael
array[1] = WakeUp
array[2] = Going to schools
I want it become like:
array[0] = TODO 06:15PM
array[1] = Join Michael
array[2] = WakeUp
array[3] = Going to schools
In this example, I split item have content TODO 06:15PMJoin Michael to new two item.
Have two separate questions here:
How to create a role for creating a new item in an array?
I tried with my code:
var splitList = words.SelectMany(x => x.Contains("AM") || x.Contains("PM"))
But I don't know how to split from text AM or PM to the new item in arrays.
You can try finding AM/PM and get substrings:
String[] array = new String[] {
"TODO 06:15PMJoin Michael",
"WakeUp",
"Going to schools"
};
var result = array
.SelectMany(line => {
int p = line.IndexOf("AM");
if (p >= 0)
return new String[] {
line.Substring(0, p + "AM".Length),
line.Substring(p + "AM".Length) };
p = line.IndexOf("PM");
if (p >= 0)
return new String[] {
line.Substring(0, p + "PM".Length),
line.Substring(p + "PM".Length) };
return new String[] { line };
}
);
//.ToArray(); // if you want to have array representation
// Test
Console.Write(String.Join(Environment.NewLine, result));
You can also try this:
string[] array = new string[] {"TODO 06:15PMJoin Michael",
"WakeUp",
"Going to schools"};
string[] SplitArray(string[] array)
{
List<string> returnArray = new List<string>();
foreach (string item in array)
{
int index = GetIndex(item);
if (index >= 0)
{
string s1 = item.Substring(0, index + 2);
string s2 = item.Substring(index + 2);
returnArray.Add(s1);
returnArray.Add(s2);
}
else
{
returnArray.Add(item);
}
}
return returnArray.ToArray();
}
int GetIndex(string s)
{
int index = GetIndexOf(s, "AM");
if (index == -1)
{
index = GetIndexOf(s, "PM");
}
return index;
}
int GetIndexOf(string s, string delim)
{
int index = -1;
int tempIndex = 0;
do
{
tempIndex = s.IndexOf(delim, tempIndex);
if (tempIndex > 0)
{
if (char.IsDigit(s[tempIndex-1]))
{
index = tempIndex;
break;
}
}
}
while(tempIndex >= 0);
return index;
}

string sorting in C#

I have an array of strings like the following:
"access"
"Addition"
"account"
"base"
"Brick"
"zammer"
"Zilon"
I want them to sort them witht the following rules"
Capital letters for a given character should come first.
The capital and small letters should be sorted in their own groups.
Thus, the output should be:
"Addition"
"access"
"account"
"Brick"
"base"
"Zilon"
"zammer"
The language I am using is C# and .Net 4.0.
Proper set of OrderBy/ThenBy calls will do the trick.
Order by first letter lowercased, to get all as and As first, then bs and Bs, etc.
Then by IsLower(firstCharacter), which will get the uppercased items for each letter first.
Then by the entire string.
var sorted = source.OrderBy(s => char.ToLower(s[0]))
.ThenBy(s => char.IsLower(s[0]))
.ThenBy(s => s)
.ToList();
Try like this
List<string> list = new List<string>();
list.Add("access");
list.Add("Addition");
list.Add("account");
list.Add("base")
list.Add("Brick")
list.Add("zammer")
list.Add("Zilon")
list = list.Where(r => char.IsLower(r[0])).OrderBy(r => r)
.Concat(list.Where(r => char.IsUpper(r[0])).OrderBy(r => r)).ToList();
for (int i = 0; i < list.Count; i++)
Console.WriteLine(list[i]);
Below solution works for more than one Caps.
static void Main(string[] args)
{
var names = new List<String>() {
"access",
"Addition",
"ADDition",
"ADdition",
"account",
"base",
"Brick",
"zammer",
"Zilon"
};
names.Sort((one, two) =>
{
int result = 0;
var oneArray = one.ToCharArray();
var twoArray = two.ToCharArray();
var minLength = Math.Min(oneArray.Length, twoArray.Length) - 1;
var i = 0;
while (i < minLength)
{
//Diff Letter
if (Char.ToUpper(one[i]) != Char.ToUpper(two[i]))
{
result = Char.ToUpper(one[i]) - Char.ToUpper(two[i]);
break;
}
// Same Letter, same case
if (oneArray[i] == twoArray[i])
{
i++;
continue;
}
// Same Letter, diff case
result = one[i] - two[i];
break;
}
return result;
});
foreach (string s in names)
Console.WriteLine(s);
Console.WriteLine("done");
If you want to go beyond the first character, I should implement a comparer:
class MyComparer : IComparer<string>
{
public int Compare(string x, string y)
{
if ((x == null) && (y == null))
{
return 0;
}
if (x == null)
{
return 1;
}
if (y == null)
{
return -1;
}
var l = Math.Min(x.Length, y.Length);
for (var i = 0; i < l; i++)
{
var c = x[i];
var d = y[i];
if (c != d)
{
if (char.ToLowerInvariant(c) == char.ToLowerInvariant(d))
{
return StringComparer.Ordinal.Compare(new string(c, 1), new string(d, 1));
}
else
{
return StringComparer.OrdinalIgnoreCase.Compare(new string(c, 1), new string(d, 1));
}
}
}
return x.Length == y.Length ? 0 : x.Length > y.Length ? 1 : -1;
}
}
And then use it:
var myComparer = new MyComparer();
source.OrderBy(s => s, myComparer);

Distinct list<string> of chars

I have this:
List<string> list = new List<string>();
list.Add("a-b-c>d");
list.Add("b>c");
list.Add("f>e");
list.Add("f>e-h");
list.Add("a-d>c-b");
I want to delete duplicates. In this case duplicates are "a-b-c>d" and "a-d>c-b". Both have same chars but in diferente order.
I tried with:
list.Distinct().ToList();
But didn't work!
It looks like you want:
var distinct = list
.Select((str, idx) => new { Str = str, Idx = idx })
.GroupBy(pair => new HashSet<char>(pair.Str), HashSet<char>.CreateSetComparer())
.Select(grp => grp.OrderBy(p => p.Idx).First())
.ToList();
This will keep the first element and remove any later strings in the sequence which contains the same characters.
You can also use Aggregate to track the character sets you've already seen:
var distinct = list
.Aggregate(new Dictionary<HashSet<char>, string>(HashSet<char>.CreateSetComparer()), (dict, str) =>
{
var set = new HashSet<char>(str);
if (!dict.ContainsKey(set))
dict.Add(set, str);
return dict;
})
.Values
.ToList();
You'll have to define a custom IEqualityComparer that allows the system to understand when you consider two strings to be "equal". For example:
List<string> list = new List<string>();
list.Add("a-b-c>d");
list.Add("b>c-d-f");
list.Add("c-d-f>e");
list.Add("a-d>c-b");
var distinctItems = list.Distinct(new KeyFuncEqualityComparer<string>(
s => new String(s.AsEnumerable().OrderBy(c => c).ToArray())));
Result:
a-b-c>d
b>c-d-f
c-d-f>e
... using this generic IEqualityComparer implementation:
public class KeyFuncEqualityComparer<T> :IEqualityComparer<T>
{
private readonly Func<T, object> _getKey;
public KeyFuncEqualityComparer(Func<T, object> getKey)
{
_getKey = getKey;
}
public bool Equals(T x, T y)
{
return _getKey(x).Equals(_getKey(y));
}
public int GetHashCode(T obj)
{
return _getKey(obj).GetHashCode();
}
}
untested
list<int> dups = new list<int>();
for(int i = 0; i < list.count - 1; i++)
{
for(int j = 1; j < list.count; j++)
{
int len = list[j].length;
bool b = false;
for(int k = 0; k < list[i].length; k++)
{
if(k < len && (list[i][k] != list[j][k]))
{
b = false;
break;
}
b = true;
}
if(b == true)
{
dups.add(i);
}
}
}
then remove all dups from list

How do I sort strings alphabetically while accounting for value when a string is numeric?

I'm trying to sort an array of numbers that are strings and I'd like them to sort numerically.
The catch is that I cannot convert the numbers into int.
Here is the code:
string[] things= new string[] { "105", "101", "102", "103", "90" };
foreach (var thing in things.OrderBy(x => x))
{
Console.WriteLine(thing);
}
Output:
101, 102, 103, 105, 90
I'd like:
90, 101, 102, 103, 105
EDIT:
The output can't be 090, 101, 102...
Updated the code sample to say "things" instead of "sizes". The array can be something like this:
string[] things= new string[] { "paul", "bob", "lauren", "007", "90" };
That means it needs to be sorted alphabetically and by number:
007, 90, bob, lauren, paul
Pass a custom comparer into OrderBy. Enumerable.OrderBy will let you specify any comparer you like.
This is one way to do that:
void Main()
{
string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "101"};
foreach (var thing in things.OrderBy(x => x, new SemiNumericComparer()))
{
Console.WriteLine(thing);
}
}
public class SemiNumericComparer: IComparer<string>
{
/// <summary>
/// Method to determine if a string is a number
/// </summary>
/// <param name="value">String to test</param>
/// <returns>True if numeric</returns>
public static bool IsNumeric(string value)
{
return int.TryParse(value, out _);
}
/// <inheritdoc />
public int Compare(string s1, string s2)
{
const int S1GreaterThanS2 = 1;
const int S2GreaterThanS1 = -1;
var IsNumeric1 = IsNumeric(s1);
var IsNumeric2 = IsNumeric(s2);
if (IsNumeric1 && IsNumeric2)
{
var i1 = Convert.ToInt32(s1);
var i2 = Convert.ToInt32(s2);
if (i1 > i2)
{
return S1GreaterThanS2;
}
if (i1 < i2)
{
return S2GreaterThanS1;
}
return 0;
}
if (IsNumeric1)
{
return S2GreaterThanS1;
}
if (IsNumeric2)
{
return S1GreaterThanS2;
}
return string.Compare(s1, s2, true, CultureInfo.InvariantCulture);
}
}
Just pad with zeroes to the same length:
int maxlen = sizes.Max(x => x.Length);
var result = sizes.OrderBy(x => x.PadLeft(maxlen, '0'));
Value is a string
List = List.OrderBy(c => c.Value.Length).ThenBy(c => c.Value).ToList();
Works
And, how about this ...
string[] sizes = new string[] { "105", "101", "102", "103", "90" };
var size = from x in sizes
orderby x.Length, x
select x;
foreach (var p in size)
{
Console.WriteLine(p);
}
There is a native function in windows StrCmpLogicalW that will compare in strings numbers as numbers instead of letters. It is easy to make a comparer that calls out to that function and uses it for it's comparisons.
public class StrCmpLogicalComparer : Comparer<string>
{
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string x, string y);
public override int Compare(string x, string y)
{
return StrCmpLogicalW(x, y);
}
}
It even works on strings that have both text and numbers. Here is a example program that will show the diffrence between the default sort and the StrCmpLogicalW sort
class Program
{
static void Main()
{
List<string> items = new List<string>()
{
"Example1.txt", "Example2.txt", "Example3.txt", "Example4.txt", "Example5.txt", "Example6.txt", "Example7.txt", "Example8.txt", "Example9.txt", "Example10.txt",
"Example11.txt", "Example12.txt", "Example13.txt", "Example14.txt", "Example15.txt", "Example16.txt", "Example17.txt", "Example18.txt", "Example19.txt", "Example20.txt"
};
items.Sort();
foreach (var item in items)
{
Console.WriteLine(item);
}
Console.WriteLine();
items.Sort(new StrCmpLogicalComparer());
foreach (var item in items)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
which outputs
Example1.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example2.txt
Example20.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example1.txt
Example2.txt
Example3.txt
Example4.txt
Example5.txt
Example6.txt
Example7.txt
Example8.txt
Example9.txt
Example10.txt
Example11.txt
Example12.txt
Example13.txt
Example14.txt
Example15.txt
Example16.txt
Example17.txt
Example18.txt
Example19.txt
Example20.txt
try this
sizes.OrderBy(x => Convert.ToInt32(x)).ToList<string>();
Note:
this will helpful when all are string convertable to int.....
You say you cannot convert the numbers into int because the array can contain elements that cannot be converted to int, but there is no harm in trying:
string[] things = new string[] { "105", "101", "102", "103", "90", "paul", "bob", "lauren", "007", "90" };
Array.Sort(things, CompareThings);
foreach (var thing in things)
Debug.WriteLine(thing);
Then compare like this:
private static int CompareThings(string x, string y)
{
int intX, intY;
if (int.TryParse(x, out intX) && int.TryParse(y, out intY))
return intX.CompareTo(intY);
return x.CompareTo(y);
}
Output: 007, 90, 90, 101, 102, 103, 105, bob, lauren, paul
This site discusses alphanumeric sorting and will sort the numbers in a logical sense instead of an ASCII sense. It also takes into account the alphas around it:
http://www.dotnetperls.com/alphanumeric-sorting
EXAMPLE:
C:/TestB/333.jpg
11
C:/TestB/33.jpg
1
C:/TestA/111.jpg
111F
C:/TestA/11.jpg
2
C:/TestA/1.jpg
111D
22
111Z
C:/TestB/03.jpg
1
2
11
22
111D
111F
111Z
C:/TestA/1.jpg
C:/TestA/11.jpg
C:/TestA/111.jpg
C:/TestB/03.jpg
C:/TestB/33.jpg
C:/TestB/333.jpg
The code is as follows:
class Program
{
static void Main(string[] args)
{
var arr = new string[]
{
"C:/TestB/333.jpg",
"11",
"C:/TestB/33.jpg",
"1",
"C:/TestA/111.jpg",
"111F",
"C:/TestA/11.jpg",
"2",
"C:/TestA/1.jpg",
"111D",
"22",
"111Z",
"C:/TestB/03.jpg"
};
Array.Sort(arr, new AlphaNumericComparer());
foreach(var e in arr) {
Console.WriteLine(e);
}
}
}
public class AlphaNumericComparer : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
I guess this will be much more good if it has some numeric in the string.
Hope it will help.
PS:I'm not sure about performance or complicated string values but it worked good something like this:
lorem ipsum
lorem ipsum 1
lorem ipsum 2
lorem ipsum 3
...
lorem ipsum 20
lorem ipsum 21
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
int s1r, s2r;
var s1n = IsNumeric(s1, out s1r);
var s2n = IsNumeric(s2, out s2r);
if (s1n && s2n) return s1r - s2r;
else if (s1n) return -1;
else if (s2n) return 1;
var num1 = Regex.Match(s1, #"\d+$");
var num2 = Regex.Match(s2, #"\d+$");
var onlyString1 = s1.Remove(num1.Index, num1.Length);
var onlyString2 = s2.Remove(num2.Index, num2.Length);
if (onlyString1 == onlyString2)
{
if (num1.Success && num2.Success) return Convert.ToInt32(num1.Value) - Convert.ToInt32(num2.Value);
else if (num1.Success) return 1;
else if (num2.Success) return -1;
}
return string.Compare(s1, s2, true);
}
public bool IsNumeric(string value, out int result)
{
return int.TryParse(value, out result);
}
}
This seems a weird request and deserves a weird solution:
string[] sizes = new string[] { "105", "101", "102", "103", "90" };
foreach (var size in sizes.OrderBy(x => {
double sum = 0;
int position = 0;
foreach (char c in x.ToCharArray().Reverse()) {
sum += (c - 48) * (int)(Math.Pow(10,position));
position++;
}
return sum;
}))
{
Console.WriteLine(size);
}
The answer given by Jeff Paulsen is correct but the Comprarer can be much simplified to this:
public class SemiNumericComparer: IComparer<string>
{
public int Compare(string s1, string s2)
{
if (IsNumeric(s1) && IsNumeric(s2))
return Convert.ToInt32(s1) - Convert.ToInt32(s2)
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)
{
int result;
return Int32.TryParse(value, out result);
}
}
This works because the only thing that is checked for the result of the Comparer is if the result is larger, smaller or equal to zero. One can simply subtract the values from another and does not have to handle the return values.
Also the IsNumeric method should not have to use a try-block and can benefit from TryParse.
And for those who are not sure:
This Comparer will sort values so, that non numeric values are always appended to the end of the list. If one wants them at the beginning the second and third if block have to be swapped.
public class NaturalSort: IComparer<string>
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogicalW(string x, string y);
public int Compare(string x, string y)
{
return StrCmpLogicalW(x, y);
}
}
arr = arr.OrderBy(x => x, new NaturalSort()).ToArray();
The reason I needed it was to get filed in a directory whose filenames started with a number:
public static FileInfo[] GetFiles(string path)
{
return new DirectoryInfo(path).GetFiles()
.OrderBy(x => x.Name, new NaturalSort())
.ToArray();
}
Try this :
string[] things= new string[] { "105", "101", "102", "103", "90" };
int tmpNumber;
foreach (var thing in (things.Where(xx => int.TryParse(xx, out tmpNumber)).OrderBy(xx => int.Parse(xx))).Concat(things.Where(xx => !int.TryParse(xx, out tmpNumber)).OrderBy(xx => xx)))
{
Console.WriteLine(thing);
}
Expanding on Jeff Paulsen answer. I wanted to make sure it didn't matter how many number or char groups were in the strings:
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
if (int.TryParse(s1, out var i1) && int.TryParse(s2, out var i2))
{
if (i1 > i2)
{
return 1;
}
if (i1 < i2)
{
return -1;
}
if (i1 == i2)
{
return 0;
}
}
var text1 = SplitCharsAndNums(s1);
var text2 = SplitCharsAndNums(s2);
if (text1.Length > 1 && text2.Length > 1)
{
for (var i = 0; i < Math.Max(text1.Length, text2.Length); i++)
{
if (text1[i] != null && text2[i] != null)
{
var pos = Compare(text1[i], text2[i]);
if (pos != 0)
{
return pos;
}
}
else
{
//text1[i] is null there for the string is shorter and comes before a longer string.
if (text1[i] == null)
{
return -1;
}
if (text2[i] == null)
{
return 1;
}
}
}
}
return string.Compare(s1, s2, true);
}
private string[] SplitCharsAndNums(string text)
{
var sb = new StringBuilder();
for (var i = 0; i < text.Length - 1; i++)
{
if ((!char.IsDigit(text[i]) && char.IsDigit(text[i + 1])) ||
(char.IsDigit(text[i]) && !char.IsDigit(text[i + 1])))
{
sb.Append(text[i]);
sb.Append(" ");
}
else
{
sb.Append(text[i]);
}
}
sb.Append(text[text.Length - 1]);
return sb.ToString().Split(' ');
}
}
I also took SplitCharsAndNums from an SO Page after amending it to deal with file names.
Example of short IComparer class.
if both string arguments can be converted to integer then arguments
are parsed to integers and compared
if only one argument can be converted to integer, then integer is
prioritized (has lower value) and are inserted before string.
If no one of arguments can be converted into integer then ordinary
string comparison is used.
Code:
public class CompareIntegerStrings : IComparer<string>
{
public int Compare(string x, string y)
{
if (int.TryParse(x, out int xOut) && int.TryParse(y, out int yOut))
return xOut.CompareTo(yOut);
else if (int.TryParse(x, out _))
return -1;
else if (int.TryParse(y, out _))
return 1;
else
return x.CompareTo(y);
}
}
In this example
List<string> intStrings = new List<string> { "01","0022","abba", "11", "deep purple", "02", };
List<string> orderedIntStrings = intStrings.OrderBy(i=>i,new CompareIntegerStrings()).ToList();
ordered list orderedIntString are { "01","02","11","0022","abba","deep purple"}.
Recommend using NaturalSort.Extension(nuget/github), as it is a reasonably difficult operation as you can see from the answer.
using NaturalSort.Extension;
var ordered = things.OrderBy(x => x, StringComparison.OrdinalIgnoreCase.WithNaturalSort());
Try this out..
string[] things = new string[] { "paul", "bob", "lauren", "007", "90", "-10" };
List<int> num = new List<int>();
List<string> str = new List<string>();
for (int i = 0; i < things.Count(); i++)
{
int result;
if (int.TryParse(things[i], out result))
{
num.Add(result);
}
else
{
str.Add(things[i]);
}
}
Now Sort the lists and merge them back...
var strsort = from s in str
orderby s.Length
select s;
var numsort = from n in num
orderby n
select n;
for (int i = 0; i < things.Count(); i++)
{
if(i < numsort.Count())
things[i] = numsort.ElementAt(i).ToString();
else
things[i] = strsort.ElementAt(i - numsort.Count());
}
I jsut tried to make a contribution in this interesting question...
My preferred solution (if all strings are numeric only):
// Order by numerical order: (Assertion: all things are numeric strings only)
foreach (var thing in things.OrderBy(int.Parse))
{
Console.Writeline(thing);
}
public class Test
{
public void TestMethod()
{
List<string> buyersList = new List<string>() { "5", "10", "1", "str", "3", "string" };
List<string> soretedBuyersList = null;
soretedBuyersList = new List<string>(SortedList(buyersList));
}
public List<string> SortedList(List<string> unsoredList)
{
return unsoredList.OrderBy(o => o, new SortNumericComparer()).ToList();
}
}
public class SortNumericComparer : IComparer<string>
{
public int Compare(string x, string y)
{
int xInt = 0;
int yInt = 0;
int result = -1;
if (!int.TryParse(x, out xInt))
{
result = 1;
}
if(int.TryParse(y, out yInt))
{
if(result == -1)
{
result = xInt - yInt;
}
}
else if(result == 1)
{
result = string.Compare(x, y, true);
}
return result;
}
}
Using Regex.Replace is so simple yet efficient. Note that the number "3" just has to be a number equal-to or larger than your longest string, so for anyone else, increase as needed.
using System.Text.RegularExpressions;
string[] things = new string[] { "105", "101", "102", "103", "90" };
foreach (var thing in things.OrderBy(x => Regex.Replace(x, #"\d+", i =>
i.Value.PadLeft(3, '0'))))
{
Console.WriteLine(thing);
}
I would have commented under recursive's answer, but my reputation is too low for that.
Because recursive's answer only works with numeric strings (if You have a string like "I am just a damn long string", it would be sorted after "Not so long string") and OP edited his answer, my Idea for the question would be to sort the strings by differentiating them into numbers and not numbers:
int maxlen = items.Max(x => x.Length);
var items = items.OrderBy(x => long.TryParse(x, out _) == true ? x.PadLeft(maxlen, '0') : x);
The underscore is for discarding the output
namespace X
{
public class Utils
{
public class StrCmpLogicalComparer : IComparer<Projects.Sample>
{
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string x, string y);
public int Compare(Projects.Sample x, Projects.Sample y)
{
string[] ls1 = x.sample_name.Split("_");
string[] ls2 = y.sample_name.Split("_");
string s1 = ls1[0];
string s2 = ls2[0];
return StrCmpLogicalW(s1, s2);
}
}
}
}
Even though this is an old question, I'd like to give a solution:
string[] things= new string[] { "105", "101", "102", "103", "90" };
foreach (var thing in things.OrderBy(x => Int32.Parse(x) )
{
Console.WriteLine(thing);
}
Woha quite simple right? :D

Categories