Alphanumeric string sorting with prifix and suffix - c#

I have a alphanumeric string of array which contains some characters at end of string also. The problem is i'm able to sort only till before the last character.
Here is the my array
string[] ar = new string[] { "DV00154A", "DV00144A", "DV00111B", "DV00100A", "DV00199B", "DV00001A" };
i have tried some method but the sorted array skipping the sorting of last character. here is one of the approach which i have tried.
public static string ArraySort(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10,'0'));
}
public static void Main(string[] args)
{
string[] ar = new string[] { "DV00154A", "DV00144A", "DV00111B", "DV00100A",
"DV00199B", "DV00001A" };
var result = ar.OrderBy(x => ArraySort(x));
Console.WriteLine(string.Join(",",result.ToArray()));
}
which returning the below output
DV00001A,DV00100A,DV00111B,DV00144A,DV00154A,DV00199B
but the output i need should be like this
DV00001A,DV00100A,DV00144A,DV00154A,DV00111B,DV00199B

What about this solution:
public static string ArraySort(string input)
{
return $"{input.Substring(0, 2)}{input.Substring(7, 1)}{input.Substring(2, 5)}";
}
public static void Main(string[] args)
{
string[] ar = new string[] { "DV00154A", "DV00144A", "DV00111B", "DV00100A", "DV00199B", "DV00001A" };
Array.Sort(ar, (a, b) => StringComparer.OrdinalIgnoreCase.Compare(ArraySort(a), ArraySort(b)));
Console.WriteLine(string.Join(",", ar));
Console.ReadKey();
}
The ArraySort method rearranges the values in a sortable format: DV00154A -> DVA00154. That values are used the by the Array.Sort comparer method.
The only disadvantage that the ar array is sorted in-place...
Edit:
I found an even better solution: just take my ArraySort method with your Main method. Should work just fine. :) And it will not affect your ar array.
Edit 2:
Just fixed a little bug in my ArraySort method. (Sorry.)
Edit 3:
If you can ignore the DV-prefix, you could change the ArraySort method to this:
public static string ArraySort(string input)
{
return $"{input.Substring(7, 1)}{input.Substring(2, 5)}";
}
The logic is less complex and the resulting values are shorter. It should have a (marginal) better performance.

Related

How to use .OrderBy for multiple conditions, one only sometimes used?

Sorry if the title, is confusing, I had some trouble putting my problem into words.
I have a List, where every string is composed of 2 words, delimited by space.
For example:
{ "word1 word2", "wordA wordB", "dog cat", "mouse cat" }
I want to use OrderBy to sort the list by the 2nd word, if any words are equal, I then want to sort those by the 1st word. I'm having trouble figuring out how to handle the 2nd condition for this (sorting by 1st word only if 2nd words are equal).
I originally tried:
public List<string> SpecialSort(List<string> text)
{
return text.OrderBy(x => x.Split(' ')[1]).ThenBy(x => x.Split(' ')[0]);
}
but this seems to just sort first by the 2nd word, and then re-sort everything by the 1st word. Is there a way for me to do this where I only sort by 1st word if the 2nd words are equal?
Thanks!
My advice would be to split the text into words, while keeping the original text in a Select. Then sort the sequence and finally remove the split words.
Requirement
Input: a sequence of strings, every string has exactly one space.
This space is neither the first nor the last character.
The characters before this one and only space are defined as the first word.
The characters after the space are defined as the second word.
Output: Sort the sequence by 2nd word, then by 1st word.
IEnumerable<string> inputTexts = ...
const string splitChar = ' ';
// first add the split words
var sortedSequence = inputTexts.Select(txt => new
{
Original = txt,
Split = txt.Split(splitChar, StringSplitOptions.None),
})
// then sort by the split words
.OrderBy(splitTxt => splitTxt.Split[1])
.ThenBy(splitTxt => splitTxt.Split[0])
// finally remove the split words
.Select(splitTxt => splitTxt.Original);
Create intermediate results within an .OrderBy() statement can be painful, cause the comparer needs to possibly call them multiple times on each object. Also to make it better maintainable I would write a class that gets the original value, creates the desired elements and feeding these intermediate objects into a specific comparer that can sort them. At the end just get the original value out of the intermediate class and you're done.
A rough sketch for your example would look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Program
{
private static void Main(string[] args)
{
var words = new List<string>{"word1 word2", "wordA wordB", "dog cat", "mouse cat"};
var ordered = words
.Select(SpecialComparerInstance.Create)
.OrderBy(special => special, SpecialComparer.Default)
.Select(special => special.Value);
foreach (var item in ordered)
{
Console.WriteLine(item);
}
}
}
public class SpecialComparerInstance
{
public static SpecialComparerInstance Create(string value) => new SpecialComparerInstance(value);
public SpecialComparerInstance(string value)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException(nameof(value));
var elements = value.Split(' ');
if (elements.Length != 2)
throw new ArgumentException("Must contain exactly one space character", nameof(value));
Value = value;
FirstOrderValue = elements[1];
SecondOrderValue = elements[0];
}
public string Value { get; }
public string FirstOrderValue { get; }
public string SecondOrderValue { get; }
}
public class SpecialComparer : IComparer<SpecialComparerInstance>
{
public static readonly IComparer<SpecialComparerInstance> Default = new SpecialComparer(StringComparer.Ordinal);
private readonly StringComparer _comparer;
public SpecialComparer(StringComparer comparer)
{
_comparer = comparer;
}
public int Compare(SpecialComparerInstance x, SpecialComparerInstance y)
{
if (ReferenceEquals(x, y))
return 0;
if (ReferenceEquals(x, null))
return 1;
if (ReferenceEquals(y, null))
return -1;
var result = _comparer.Compare(x.FirstOrderValue, y.FirstOrderValue);
if (result == 0)
result = _comparer.Compare(x.SecondOrderValue, y.SecondOrderValue);
return result;
}
}

Sorting array by length

I'm wanting to sort this array by length but how will I do that? I searched the web for a while but couldn't find anything. The code is supposed to sort an array given at random by each strings length in the string array, but I can't get anyway of declaring how I want it to sort.
public class Kata
{
public static string[] SortByLength (string[] array)
{
Array.Sort(array);
return array;
}
}
Well, in order to sort the array in place you should provide rule of sorting:
Array.Sort(array, (x, y) => x.Length.CompareTo(y.Length));
The method can be
public static string[] SortByLength (string[] array) {
//TODO: validate array and its items here; they must be not null
Array.Sort(array, (x, y) => x.Length.CompareTo(y.Length));
return array;
}
Use linq...
public static string[] SortByLength (string[] array) =>
array.OrderBy( x => x?.Length ?? 0 ).ToArray();
Dude, this is how it should work.
Array.Sort(array, StringComparer.InvariantCulture);

Custom List<string[]> Sort

I have a list of string[].
List<string[]> cardDataBase;
I need to sort that list by each list-item's second string value (item[1]) in custom order.
The custom order is a bit complicated, order by those starting characters:
"MW1"
"FW"
"DN"
"MWSTX1CK"
"MWSTX2FF"
then order by these letters following above starting letters:
"A"
"Q"
"J"
"C"
"E"
"I"
"A"
and then by the numbers following above.
a sample, unordered list left, ordered right:
MW1E10 MW1Q04
MWSTX2FFI06 MW1Q05
FWQ02 MW1E10
MW1Q04 MW1I06
MW1Q05 FWQ02
FWI01 FWI01
MWSTX2FFA01 DNC03
DNC03 MWSTX1CKC02
MWSTX1CKC02 MWSTX2FFI03
MWSTX2FFI03 MWSTX2FFI06
MW1I06 MWSTX2FFA01
I tried Linq but I am not that good in it right now and cannot solve this on my own. Do I need a dictionary, regex or a dictionary with regex in it? What would be the best approach?
I think you're approaching this incorrectly. You're not sorting strings, you're sorting structured objects that are misrepresented as strings (somebody aptly named this antipattern "stringly typed"). Your requirements show that you know this structure, yet it's not represented in the datastructure List<string[]>, and that's making your life hard. You should parse that structure into a real type (struct or class), and then sort that.
enum PrefixCode { MW1, FW, DN, MWSTX1CK, MWSTX2FF, }
enum TheseLetters { Q, J, C, E, I, A, }
struct CardRecord : IComparable<CardRecord> {
public readonly PrefixCode Code;
public readonly TheseLetters Letter;
public readonly uint Number;
public CardRecord(string input) {
Code = ParseEnum<PrefixCode>(ref input);
Letter = ParseEnum<TheseLetters>(ref input);
Number = uint.Parse(input);
}
static T ParseEnum<T>(ref string input) { //assumes non-overlapping prefixes
foreach(T val in Enum.GetValues(typeof(T))) {
if(input.StartsWith(val.ToString())) {
input = input.Substring(val.ToString().Length);
return val;
}
}
throw new InvalidOperationException("Failed to parse: "+input);
}
public int CompareTo(CardRecord other) {
var codeCmp = Code.CompareTo(other.Code);
if (codeCmp!=0) return codeCmp;
var letterCmp = Letter.CompareTo(other.Letter);
if (letterCmp!=0) return letterCmp;
return Number.CompareTo(other.Number);
}
public override string ToString() {
return Code.ToString() + Letter + Number.ToString("00");
}
}
A program using the above to process your example might then be:
static class Program {
static void Main() {
var inputStrings = new []{ "MW1E10", "MWSTX2FFI06", "FWQ02", "MW1Q04", "MW1Q05",
"FWI01", "MWSTX2FFA01", "DNC03", "MWSTX1CKC02", "MWSTX2FFI03", "MW1I06" };
var outputStrings = inputStrings
.Select(s => new CardRecord(s))
.OrderBy(c => c)
.Select(c => c.ToString());
Console.WriteLine(string.Join("\n", outputStrings));
}
}
This generates the same ordering as in your example. In real code, I'd recommend you name the types according to what they represent, and not, for example, TheseLetters.
This solution - with a real parse step - is superior because it's almost certain that you'll want to do more with this data at some point, and this allows you to actually access the components of the data easily. Furthermore, it's comprehensible to a future maintainer since the reason behind the ordering is somewhat clear. By contrast, if you chose to do complex string-based processing it's often very hard to understand what's going on (especially if it's part of a larger program, and not a tiny example as here).
Making new types is cheap. If your method's return value doesn't quite "fit" in an existing type, just make a new one, even if that means 1000's of types.
A bit spoonfeeding, but I found this question pretty interesting and perhaps it will be useful for others, also added some comments to explain:
void Main()
{
var cardDatabase = new List<string>{
"MW1E10",
"MWSTX2FFI06",
"FWQ02",
"MW1Q04",
"MW1Q05",
"FWI01",
"MWSTX2FFA01",
"DNC03",
"MWSTX1CKC02",
"MWSTX2FFI03",
"MW1I06",
};
var orderTable = new List<string>[]{
new List<string>
{
"MW1",
"FW",
"DN",
"MWSTX1CK",
"MWSTX2FF"
},
new List<string>
{
"Q",
"J",
"C",
"E",
"I",
"A"
}
};
var test = cardDatabase.Select(input => {
var r = Regex.Match(input, "^(MW1|FW|DN|MWSTX1CK|MWSTX2FF)(A|Q|J|C|E|I|A)([0-9]+)$");
if(!r.Success) throw new Exception("Invalid data!");
// for each input string,
// we are going to split it into "substrings",
// eg: MWSTX1CKC02 will be
// [MWSTX1CK, C, 02]
// after that, we use IndexOf on each component
// to calculate "real" order,
// note that thirdComponent(aka number component)
// does not need IndexOf because it is already representing the real order,
// we still want to convert string to integer though, because we don't like
// "string ordering" for numbers.
return new
{
input = input,
firstComponent = orderTable[0].IndexOf(r.Groups[1].Value),
secondComponent = orderTable[1].IndexOf(r.Groups[2].Value),
thirdComponent = int.Parse(r.Groups[3].Value)
};
// and after it's done,
// we start using LINQ OrderBy and ThenBy functions
// to have our custom sorting.
})
.OrderBy(calculatedInput => calculatedInput.firstComponent)
.ThenBy(calculatedInput => calculatedInput.secondComponent)
.ThenBy(calculatedInput => calculatedInput.thirdComponent)
.Select(calculatedInput => calculatedInput.input)
.ToList();
Console.WriteLine(test);
}
You can use the Array.Sort() method. Where your first parameter is the string[] you're sorting and the second parameter contains the complicated logic of determining the order.
You can use the IEnumerable.OrderBy method provided by the System.Linq namespace.

VB.Net / C# Spintax help?

Can anyone give me an example of spintax snippet for C# / VB.NET programming language. If you don't know what that is (the spintax), well basically it is a way of putting different values of strings and then randomly choosing one. For instance:
{Hello|Hi|Greetings} my name is {Tom|John|Eaven} and I like {turtles|programming|ping pong}.
And it would choose between { } splitting those strings inside of the {} string with delimiter of | so it randomly outputs the final string.
Here is a class for C# which handles that snippet:
public class Spinner
{
private static Random rnd = new Random();
public static string Spin(string str)
{
string regex = #"\{(.*?)\}";
return Regex.Replace(str, regex, new MatchEvaluator(WordScrambler));
}
public static string WordScrambler(Match match)
{
string[] items = match.Value.Substring(1, match.Value.Length - 2).Split('|');
return items[rnd.Next(items.Length)];
}
}
And try it:
Console.WriteLine(Spinner.Spin("{Hello|Greetings|Merhaba} World, My name is {Beaver|Michael} Obama"));
Here is the complete article: http://jeremy.infinicastonline.com/2010/11/spintax-class-for-c-net/

Finding duplicates in List<string>

In a list with some hundred thousand entries, how does one go about comparing each entry with the rest of the list for duplicates?
For example, List fileNames contains both "00012345.pdf" and "12345.pdf" and are considered duplicte. What is the best strategy to flagging this kind of a duplicate?
Thanks
Update: The naming of files is restricted to numbers. They are padded with zeros. Duplicates are where the padding is missing. Thus, "123.pdf" & "000123.pdf" are duplicates.
You probably want to implement your own substring comparer to test equality based on whether a substring is contained within another string.
This isn't necessarily optimised, but it will work. You could also possibly consider using Parallel Linq if you are using .NET 4.0.
EDIT: Answer updated to reflect refined question after it was edited
void Main()
{
List<string> stringList = new List<string> { "00012345.pdf","12345.pdf","notaduplicate.jpg","3453456363234.jpg"};
IEqualityComparer<string> comparer = new NumericFilenameEqualityComparer ();
var duplicates = stringList.GroupBy (s => s, comparer).Where(grp => grp.Count() > 1);
// do something with grouped duplicates...
}
// Not safe for null's !
// NB do you own parameter / null checks / string-case options etc !
public class NumericFilenameEqualityComparer : IEqualityComparer<string> {
private static Regex digitFilenameRegex = new Regex(#"\d+", RegexOptions.Compiled);
public bool Equals(string left, string right) {
Match leftDigitsMatch = digitFilenameRegex.Match(left);
Match rightDigitsMatch = digitFilenameRegex.Match(right);
long leftValue = leftDigitsMatch.Success ? long.Parse(leftDigitsMatch.Value) : long.MaxValue;
long rightValue = rightDigitsMatch.Success ? long.Parse(rightDigitsMatch.Value) : long.MaxValue;
return leftValue == rightValue;
}
public int GetHashCode(string value) {
return base.GetHashCode();
}
}
I understand you are looking for duplicates in order to remove them?
One way to go about it could be the following:
Create a class MyString which takes care of duplication rules. That is, overrides Equals and GetHashCode to recreate exactly the duplication rules you are considering. (I'm understanding from your question that 00012345.pdf and 12345.pdf should be considered duplicates?)
Make this class explicitly or implictly convertible to string (or override ToString() for that matter).
Create a HashCode<MyString> and fill it up iterating through your original List<String> checking for duplicates.
Might be dirty but it will do the trick. The only "hard" part here is correctly implementing your duplication rules.
I have a simple solution for everyone to find a duplicate string word and cahracter
For word
public class Test {
public static void main(String[] args) {
findDuplicateWords("i am am a a learner learner learner");
}
private static void findDuplicateWords(String string) {
HashMap<String,Integer> hm=new HashMap<>();
String[] s=string.split(" ");
for(String tempString:s){
if(hm.get(tempString)!=null){
hm.put(tempString, hm.get(tempString)+1);
}
else{
hm.put(tempString,1);
}
}
System.out.println(hm);
}
}
for character use for loop, get array length and use charAt()
Maybe somthing like this:
List<string> theList = new List<string>() { "00012345.pdf", "00012345.pdf", "12345.pdf", "1234567.pdf", "12.pdf" };
theList.GroupBy(txt => txt)
.Where(grouping => grouping.Count() > 1)
.ToList()
.ForEach(groupItem => Console.WriteLine("{0} duplicated {1} times with these values {2}",
groupItem.Key,
groupItem.Count(),
string.Join(" ", groupItem.ToArray())));

Categories

Resources