I have a list/collection of objects with multiple fields. One of them being filename.
I am sorting based on filename but not getting the correct results.
List:
"552939_VVIDEO9.mp4"
"552939_VVIDEO8.mp4"
"552939_VVIDEO13.mp4"
"552939_VVIDEO12.mp4"
"552939_VVIDEO7.mp4"
"552939_VVIDEO6.mp4"
"552939_VVIDEO2.mp4"
"552939_VVIDEO16.mp4"
"552939_VVIDEO10.mp4"
"552939_VVIDEO3.mp4"
"552939_VVIDEO11.mp4"
"552939_VVIDEO4.mp4"
"552939_VVIDEO1.mp4"
"552939_VVIDEO15.mp4"
"552939_VVIDEO14.mp4"
"552939_VVIDEO17.mp4"
List<WfVideo> orderVideo = ProductData.Videos.OrderBy(o => o.Filename, StringComparer.InvariantCultureIgnoreCase).ToList();
Result I am getting:
VOD1
VOD2
VVIDEO1
VVIDEO10
VVIDEO11
VVIDEO12
VVIDEO13
VVIDEO14
VVIDEO15
VVIDEO16
VVIDEO17
VVIDEO2
VVIDEO3
VVIDEO4
VVIDEO5
VVIDEO6
Is the sorting incorrect?
If you want to sort these files after the number only, you could pass a Comparer to Sort that implements the rules you want. This sorts the filenames according to their number:
List<string> files = new List<string>
{
"552939_VVIDEO9.mp4",
"552939_VVIDEO8.mp4",
"552939_VVIDEO13.mp4",
"552939_VVIDEO12.mp4",
"VOD1.mp4",
"552939_VVIDEO6.mp4",
"VOD2.mp4",
"552939_VVIDEO2.mp4",
"552939_VVIDEO16.mp4",
"552939_VVIDEO10.mp4",
"552939_VVIDEO3.mp4",
"552939_VVIDEO11.mp4",
"552939_VVIDEO4.mp4",
"552939_VVIDEO1.mp4",
"552939_VVIDEO15.mp4",
"552939_VVIDEO14.mp4",
"552939_VVIDEO17.mp4"
};
files.Sort((a, b) => {
int an = 0;
int bn = 1;
var regex = new Regex("([0-9]+).mp4", RegexOptions.IgnoreCase);
var aGroups = regex.Match(a).Groups;
var bGroups = regex.Match(b).Groups;
var aidx = aGroups.Count > 1 ? 1 : 0;
var bidx = bGroups.Count > 1 ? 1 : 0;
an = int.Parse(aGroups[aidx].Value);
bn = int.Parse(bGroups[bidx].Value);
if (an == bn)
return 0;
if (an < bn)
return -1;
return 1;
});
foreach (var file in files)
{
Console.WriteLine(file);
}
Console.ReadKey();
Output:
VOD1.mp4
552939_VVIDEO1.mp4
VOD2.mp4
552939_VVIDEO2.mp4
552939_VVIDEO3.mp4
552939_VVIDEO4.mp4
552939_VVIDEO6.mp4
552939_VVIDEO8.mp4
552939_VVIDEO9.mp4
552939_VVIDEO10.mp4
552939_VVIDEO11.mp4
552939_VVIDEO12.mp4
552939_VVIDEO13.mp4
552939_VVIDEO14.mp4
552939_VVIDEO15.mp4
552939_VVIDEO16.mp4
552939_VVIDEO17.mp4
Note some additional error checking may be needed. You can offcourse extend this Comparer function to work for whatever rules you wish.
Related
I have a folder list,
want to get the longest common substring as output.
Here's my code, seems not very good,
How to improve these?
These files all from My RecursiveSearch function, It may be D:\123, E:\456, F:\a\e\eff, I save to xml.
Later read back from xml, want to get previous decision.
Files could be ten-thousands.
I do care about the speed.
var li = new List<string>()
{
#"C:\Users\jared\Desktop\fld1\eumaps\4.jfif",
#"C:\Users\jared\Desktop\fld2\eumaps - (2)\4.jfif",
#"C:\Users\jared\Desktop\fld4\ade\4.jfif",
#"C:\Users\jared\Desktop\fld4\abc\S.png",
#"C:\Users\jared\Desktop\fld1\file\Snipaste_2021-07-07_03-03-45.png",
};
//var shortest1 = li.OrderBy(name => name.Length).FirstOrDefault();
string shortest = li.Aggregate((a1, a2) => a1.Length < a2.Length ? a1 : a2);
string longest = li.Aggregate((a1, a2) => a1.Length > a2.Length ? a1 : a2);
string common = string.Concat(shortest.TakeWhile((c, i) => c == longest[i]));
// C:\Users\jared\Desktop\fld NOT MY WANT CAUSE THERE IS NO SUCH FOLDER.
if (!Directory.Exists(common))
{
common = common.Replace(common.Split("\\").Last(), "");
}
Your approach would fail as you are just comparing the shortest and the longest, if there is any other path with different length and have different path in middle.
As per my understanding you want the output to be till the matched folder (not the substring of the folder or filename) i.e. C:\Users\jared\Desktop\
Here is my solution which runs in O(Nk) solution, Where N is number of paths and k is the shortest available length.
Working Sample Code here.
This can also be done using the Trie Data structure in more optimized way (O(N + k)) but it would take extra space to build the Trie
var folders = new List<string>()
{
#"C:\Users\jared\Desktop\fld1\eumaps\4.jfif",
#"C:\Users\jared\Desktop\fld2\eumaps - (2)\4.jfif",
#"C:\Users\jared\Desktop\fld4\ade\4.jfif",
#"C:\Users\jared\Desktop\fld4\abc\S.png",
#"C:\Users\jared\Desktop\fld1\file\Snipaste_2021-07-07_03-03-45.png",
};
var minPathLength = folders.Min(x => x.Length);
var maxCommonPath = new StringBuilder();
var currentCommonPath = new StringBuilder();
for (int i = 0; i < minPathLength; i++)
{
var boolAllSame = true;
var c = folders[0][i];
boolAllSame = folders.All(x => x[i] == c);
if (boolAllSame)
{
currentCommonPath.Append(c);
if (c == '\\')
{
maxCommonPath.Append(currentCommonPath.ToString());
currentCommonPath = new StringBuilder();
}
}
else
break;
}
var result = maxCommonPath.ToString();
Console.WriteLine(result);
Here is a succinct, but not particularly performant implementation that uses Linq. It also has a dependency on the MoreLinq package, for the Transpose operator:
var paths = new List<string>()
{
#"C:\Users\jared\Desktop\fld1\eumaps\4.jfif",
#"C:\Users\jared\Desktop\fld2\eumaps - (2)\4.jfif",
#"C:\Users\jared\Desktop\fld4\ade\4.jfif",
#"C:\Users\jared\Desktop\fld4\abc\S.png",
#"C:\Users\jared\Desktop\fld1\file\Snipaste_2021-07-07_03-03-45.png",
};
string[] longestCommonPathComponents = paths
.Select(path => path.Split(Path.DirectorySeparatorChar))
.Transpose()
.Select(parts => parts.Distinct(StringComparer.OrdinalIgnoreCase))
.TakeWhile(distinct => distinct.Count() == 1)
.Select(distinct => distinct.First())
.ToArray();
string longestCommonPath = Path.Combine(longestCommonPathComponents);
Console.WriteLine($"Longest common path: {longestCommonPath}");
Output:
Longest common path: C:/Users/jared/Desktop
Try it on fiddle.
The signature of the Transpose operator:
// Transposes a sequence of rows into a sequence of columns.
public static IEnumerable<IEnumerable<T>> Transpose<T>(
this IEnumerable<IEnumerable<T>> source);
var li = new List<string>()
{
#"C:\Users\jared\Desktop\fld1\eumaps\4.jfif",
#"C:\Users\jared\Desktop\fld2\eumaps - (2)\4.jfif",
#"C:\Users\jared\Desktop\fld4\ade\4.jfif",
#"C:\Users\jared\Desktop\fld4\abc\S.png",
#"C:\Users\jared\Desktop\fld1\file\Snipaste_2021-07-07_03-03-45.png",
};
string first = li.First();
int n = 0;
while (li.All(x => x.Length > n && x[n] == first[n]))
n++;
string longestCommon = first.Substring(0, n); // C:\Users\jared\Desktop\fld
You say C:\Users\jared\Desktop\fld is not what you want, but what do you want? The most nested valid folder? Just pull to the last slash, or use Path methods to get the bit you want.
suggestion:
use this code for your image paths
var li = new List<string>()
{
Application.StartupPath + "/Images/1.png",
Application.StartupPath + "/Images/2.png",
Application.StartupPath + "/Images/3.png",
}
I have a problem finding the next integer match in a list of strings, there are some other aspects to consider:
single string contains non relevant trailing and leading chars
numbers are formatted "D6" example 000042
there are gaps in the numbers
the list is not sorted, but it could be if there is a fast way to ignore the leading chars
Example:
abc-000001.file
aaac-000002.file
ab-002010.file
abbc-00003.file
abbbc-00004.file
abcd-00008.file
abc-000010.file
x-902010.file
The user input is 7 => next matching string would be abcd-000008.file
My attempt is :
int userInput = 0;
int counter = 0;
string found = String.Empty;
bool run = true;
while (run)
{
for (int i = 0; i < strList.Count; i++)
{
if(strList[i].Contains((userInput + counter).ToString("D6")))
{
found = strList[i];
run = false;
break;
}
}
counter++;
}
It's bad because it's slow and it can turn into a infinite loop. But I really don't know how to do this (fast).
You can parse numbers from strings with Regex and created a sorted collection which you can search with Where clause:
var strings = new[] { "abc-000001.file", "x-000004.file"};
var regP = "\\d{6}"; // simplest option in my example, maybe something more complicated will be needed
var reg = new Regex(regP);
var collection = strings
.Select(s =>
{
var num = reg.Match(s).Captures.First().Value;
return new { num = int.Parse(num), str = s};
})
.OrderBy(arg => arg.num)
.ToList();
var userInput = 2;
var res = collection
.Where(arg => arg.num >= userInput)
.FirstOrDefault()?.str; // x-000004.file
P.S.
How 9002010, 0000010, 0002010 should be treated? Cause they have 7 characters. Is it [9002010, 10, 2010] or [900201, 1, 201]?
If you don't want regex, you can do something like that:
List<string> strings = new List<string>
{
"abc-000001.file",
"aaac-000002.file",
"ab-0002010.file",
"abbc-000003.file",
"abbbc-000004.file",
"abcd-000008.file"
};
int input = 7;
var converted = strings.Select(s => new { value = Int32.Parse(s.Split('-', '.')[1]), str = s })
.OrderBy(c => c.value);
string result = converted.FirstOrDefault(v => v.value >= input)?.str;
Console.WriteLine(result);
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!
Assume that I have a list of items from 1 - 3.
I could order them by 1,1,2,2,3,3.
But instead, I would like to order them by 1,2,3,1,2,3....
Is there an already exist function to achieve that?
This approach separates each number into groups, then iterates through the groups in order while conditionally adding them to a result list. There's probably ways to make this safer and more efficient, but this should give you a start. (It assumes that if there aren't equal counts of each number in the source array, it will skip those numbers as it runs out of them during the iteration phase.)
int[] arr = new[] { 1,1,1,2,2,2,3,3,3,4,4,4,5,5,5 };
var orderList = arr.OrderBy(x => x).Distinct().ToArray();
var refList = arr.GroupBy(x => x).ToDictionary(k => k.Key, v => v.Count());
var result = new List<int>();
int i = 0;
while (result.Count < arr.Length)
{
if (refList.Values.Sum() == 0)
break;
if (refList[orderList[i]] > 0)
{
result.Add(orderList[i]);
refList[orderList[i]]--;
}
i++;
if (i >= orderList.Length)
i = 0;
}
// Result: [1,2,3,4,5,1,2,3,4,5,1,2,3,4,5]
I have class like:
class SortNode
{
public Int32 m_valRating = 0;
public SortNode(Int32 valRating)
{
this.m_valRating = valRating;
}
}
and some list refSortNodeList:
List<SortNode> refSortNodeList = new List<SortNode>();
Random refRandom = new Random();
for (int i = 0; i < 100; ++i)
{
refSortNodeList.Add(new SortNode(refRandom.Next(-10, 30)));
}
foreach (var varSortNode in refSortNodeList)
{
Console.WriteLine("SortNode rating is {0}", varSortNode.m_valRating);
}
How to sort easily my refSortNodeList by m_valRating field? Or maybe I need to use some another List class?
list.Sort((x,y) =>
x.m_valRating.CompareTo(y.m_valRating));
In-place:
refSortNodeList.Sort(
(x, y) =>
x == null ? (y == null ? 0 : -1)
: (y == null ? 1 : x.m_valRating.CompareTo(y.m_valRating))
);
Creating a new enumeration:
var newEnum = refSortNodeList.OrderBy(x => x.m_valRating);
Creating a new list:
var newList = refSortNodeList.OrderBy(x => x.m_valRating).ToList();
In-place is fastest and most memory efficient, but no good if you want to also retain the old list.
The next is faster than the last and gives results as they go, but you have to re-do the sort to use it again, in which case the third is the one to go for.
Use Linq order by.
var mySortedList = refSortNodeList.OrderBy(x => x.m_valRating);
Here is a real live example where I am pulling a list from a database but it is exactly the same concept.
vendorProducts = (from vp in db.COMPANIES_VND_PRODUCTS
join p in db.CT_CT_INV_CLASSES on vp.CLASS_ID equals p.CLASS_ID
join m in db.CT_CT_MODALITY_CODES on vp.MODALITY_ID equals m.MODALITY_ID
where vp.COMPANY_ID == companyId
select new ProductTypeModality
{
Active = p.ACTIVE.Equals("Y") ? true : false,
BioMedImaging = p.BIOMED_IMAGING,
Code = p.CLASS_CODE,
Description = p.DESCRIPTION,
Id = p.CLASS_ID,
PricingMargin = p.PRICING_MARGIN,
ModalityCode = m.MODALITY_CODE,
ModalityId = m.MODALITY_ID,
VendorId = companyId
}).OrderBy(x => x.Code).ToList<ProductTypeModality>();
Implement IComparable<T>
You can use Linq for basic sorts:
refSortNodeList.OrderBy(n => n.m_valRating);
If you need more complex sorting your will need to implement IComparable to use the built in sorting.
Try this:
refSortNodeList.Sort(new delgate(SortNode x, SortNode y)
{
return x.CompareTo(y);
}
);
It's easy using linq:
var newlist = refSortNodeList.sort( n => n.m_valRating );
List<SortNode> refSortNodeList = new List<SortNode> ();
Random refRandom = new Random ();
for (int i = 0; i < 100; ++i) {
refSortNodeList.Add (new SortNode (refRandom.Next (-10, 30)));
}
// Use this (Linq) if you're using .NET 3.5 or above.
var sortedList = refSortNodeList.OrderBy (node => node.m_valRating);
foreach (var varSortNode in sortedList) {
Console.WriteLine ("SortNode rating is {0}", varSortNode.m_valRating);
}
// Use this otherwise (e.g. .NET 2.0)
refSortNodeList.Sort (
delegate (SortNode n1, SortNode n2) {
return n1.m_valRating.CompareTo (n2.m_valRating);
}
);
foreach (var varSortNode in refSortNodeList) {
Console.WriteLine ("SortNode rating is {0}", varSortNode.m_valRating);
}