2

I have a list of image name like this {"1.jpg", "10.jpg", "2.jpg"}.

I would like to sort like this {"1.jpg", "2.jpg", "10.jpg"}.

I created this comparer. That means if x or y == "DSC_10.jpg", so if list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...} don't sort and keep the list.

var comparer = new CompareImageName();
imageUrls.Sort(comparer);
return imageUrls;

public class CompareImageName : IComparer<string>
{
    public int Compare(string x, string y)
    {
        if (x == null || y == null) return 0;
        var l = x.Split('/');
        var l1 = y.Split('/');
        int a, b;
        var rs = int.TryParse(l[l.Length - 1].Split('.')[0], out a);
        var rs2 = int.TryParse(l1[l1.Length - 1].Split('.')[0], out b);

        if (!rs || !rs2) return 0;
        if (a == b || a == 0 && b == 0) return 0;

        return a > b ? 1 : -1;
    }
}

This sort correctly with name {"1.jpg", "10.jpg", "2.jpg"}, but incorrectly if list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...}.

I read in MSDN:

enter image description here

What wrong with my code?

  • var l = x.Split('/'); => why did you splite by '/' ? – Hieu Le May 23 '17 at 03:55
  • because I loaded list of image url from FTP server so x like "ftp://xxx.xxx.xxx.204/BETA/LLM/LLM0143/DSC_0526.jpg" or "ftp://xxx.xxx.xxx.204/BETA/LLM/LLM0143/1.jpg" – Nguyễn Xuân Hoàng May 23 '17 at 04:02
  • Seems that you need natural sorting for mixed string & numeric file names - check https://stackoverflow.com/q/11052095/ for similar solution. – Tetsuya Yamamoto May 23 '17 at 04:04
  • @NguyenXuanHoang, check my answer. I updated it to keep the order of elements if don't need to arrange – Hieu Le May 23 '17 at 04:47
  • Question is unclear. Do you mean if any of the strings in the sequence equals "DSC_10.jpg" that you don't want the list to be sorted at all? And what if any of the string in the sequence equals "DSC_11.jpg"? Or do you mean that if you compare a string from the sequence with a string equal to "DSC_10.jpg" that you declare them to be equal (= return 0).? – Harald Coppoolse May 23 '17 at 13:44

5 Answers5

2

Here is your solution check this example, following class will do the comparison

public class NumericCompare : IComparer<string>
{
   public int Compare(string x, string y)
   {
        int input1,input2;

        input1=int.Parse(x.Substring(x.IndexOf('_')+1).Split('.')[0]); 
        input2= int.Parse(y.Substring(y.IndexOf('_')+1).Split('.')[0]);
        return Comparer<int>.Default.Compare(input1,input2);
   }
}

You can make use of this class like the following:

var imageUrls = new List<string>() { "DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg" };

var comparer = new NumericCompare();
imageUrls.Sort(comparer);
Console.WriteLine(String.Join("\n",imageUrls));

Try this with simple OrderBy

   var SortedList = imageUrls.OrderBy(
                       x=>int.Parse(
                       x.Substring(x.IndexOf('_')+1).Split('.')[0])
                       ).ToList();
sujith karivelil
  • 28,671
  • 6
  • 55
  • 88
  • thanks for your answer. but I want to keep the list stay the same if the list like this {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...} – Nguyễn Xuân Hoàng May 23 '17 at 05:00
  • @NguyenXuanHoang: Then you have to choose `OrderBy` IComparer will sort the actual list. Check the updates in my answer, use `SortedList` variable that's your sorted items and `imageUrls` contains the actual list – sujith karivelil May 23 '17 at 05:03
2

I think you're better off doing a bit of Regex for this. Try this solution:

public class CompareImageName : IComparer<string>
{
    public int Compare(string x, string y)
    {
        if (x == null || y == null) return 0;

        var regex = new Regex(@"/(((?<prefix>\w*)_)|)((?<number>\d+))\.jpg$");

        var mx = regex.Match(x);
        var my = regex.Match(y);

        var r = mx.Groups["prefix"].Value.CompareTo(my.Groups["prefix"].Value);
        if (r == 0)
        {
            r = int.Parse(mx.Groups["number"].Value).CompareTo(int.Parse(my.Groups["number"].Value));
        }
        return r;
    }
}

Apart from the Regex string itself this is easier to follow the logic.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
0

Don't use imageUrls.Sort(comparer); on List because it doesn't accept 0 value as keeping the order of elements.

Reason:

The Sort performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal. Link: https://msdn.microsoft.com/en-gb/library/w56d4y5z.aspx

Solution: Let's try to use OrderBy with your compare

var imageUrls1 = new List<string>() { "1.jpg", "10.jpg", "2.jpg" };
var imageUrls2 = new List<string>() { "DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg" };

var comparer = new CompareImageName();

//Sort normally
imageUrls1 = imageUrls1.OrderBy(p=>p, comparer).ToList();

//Keep the order as your expectation
imageUrls2 = imageUrls2.OrderBy(p=>p, comparer).ToList();
Hieu Le
  • 1,042
  • 8
  • 18
0

Basically what you want to do is sort by the numeric part within the string. You are almost there. You just have to handle the part when you split a case like this DSC_2.jpg using a . then the first part is not all digits. So you need to get digits and then compare those. Here is the code. Please note I have made the assumption you will have backslash and if that is not the case then please handle it:

public int Compare(string x, string y)
{
    if (x == null || y == null) return 0;
    var nameX = x.Substring(x.LastIndexOf('/'));
    var nameY = y.Substring(y.LastIndexOf('/'));
    var nameXParts = nameX.Split('.');
    var nameYParts = nameY.Split('.');
    int a, b;
    var rs = int.TryParse(nameXParts[0], out a);
    var rs2 = int.TryParse(nameYParts[0], out b);

    var nameXDigits = string.Empty;
    if (!rs)
    {
        for (int i = 0; i < nameXParts[0].Length; i++)
        {
            if (Char.IsDigit(nameXParts[0][i]))
                nameXDigits += nameXParts[0][i];
        }
    }

    var nameYDigits = string.Empty;
    if (!rs2)
    {
        for (int i = 0; i < nameYParts[0].Length; i++)
        {
            if (Char.IsDigit(nameYParts[0][i]))
                nameYDigits += nameYParts[0][i];
        }
    }
    int.TryParse(nameXDigits, out a);
    int.TryParse(nameYDigits, out b);

    if (a == b || a == 0 && b == 0) return 0;

    return a > b ? 1 : -1;
}
CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
  • thanks for your answer. I'm able to make this. However, I want to keep the list stay the same if the list like this {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...}. – Nguyễn Xuân Hoàng May 23 '17 at 04:58
  • What do you mean? You can keep the list the same. The important bit of code is when the characters before the dot are not all numbers. That is all I handled. The rest is your code. – CodingYoshi May 23 '17 at 05:00
  • If the list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...}. don't sort and keep the list. I can check the name in the list then ignore sorting function. However, I want to know exactly the logic in Icompare. what wrong with my functiton? – Nguyễn Xuân Hoàng May 23 '17 at 05:03
  • Your function when you split `DSC_1.jpg` the first part is not a number, so you do not try to get the number and sort that part. – CodingYoshi May 23 '17 at 05:08
  • Yes, I've known this. So I can add more code in the function. However, I my case I would like to keep the list (don't order). – Nguyễn Xuân Hoàng May 23 '17 at 05:21
0

Maybe you can try doing this in a function instead of writing a comparator. I can't think of a good way to implement this logic as a comparator since there are different rules based on the contents (don't sort if the file name is not numeric).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace sortinglists
{
    public class MainProgram
    {
        public static void Main()
        {
            var imageUrlsNumbers = new List<string>();
            imageUrlsNumbers.Add("c:/a/b/1.jpg");
            imageUrlsNumbers.Add("c:/a/b/10.jpg");
            imageUrlsNumbers.Add("c:/a/b/2.jpg");

            CustomSort(ref imageUrlsNumbers);

            foreach (var imageUrl in imageUrlsNumbers)
            {
                Console.WriteLine(imageUrl);
            }

            var imageUrlsText = new List<string>();
            imageUrlsText.Add("c:/a/b/DSC_1.jpg");
            imageUrlsText.Add("c:/a/b/DSC_10.jpg");
            imageUrlsText.Add("c:/a/b/DSC_2.jpg");

            CustomSort(ref imageUrlsText);

            foreach (var imageUrl in imageUrlsText)
            {
                Console.WriteLine(imageUrl);
            }

        }

        public static void CustomSort(ref List<string> imageUrls)
        {
            if (imageUrls
                .Select(s => s.Substring(s.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
                .Select(t => t.Substring(0, t.IndexOf(".", StringComparison.OrdinalIgnoreCase)))
                .Where(u => new Regex("[A-Za-z_]").Match(u).Success)
                .Any())
            {
                imageUrls = imageUrls
                    .Select(x => x.Substring(x.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
                    .ToList();

            }
            else
            {
                imageUrls = imageUrls
                    .Select(v => v.Substring(v.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
                    .OrderBy(w => Convert.ToInt32(w.Substring(0, w.LastIndexOf(".", StringComparison.OrdinalIgnoreCase))))
                    .ToList();
            }
        }
    }
}

The output for imageUrlsNumbers after sorting is:

1.jpg
2.jpg
10.jpg

And the output for imageUrlsText after sorting is:

DSC_1.jpg
DSC_10.jpg
DSC_2.jpg
Poosh
  • 532
  • 2
  • 10
  • 25
  • your thought is true. we don't need to sort if the name is not numeric. So we can check the name of image before use comparer . It's really simple logic. But I'm confused about the comparison that MSDN says if it is string (can't convert to number). – Nguyễn Xuân Hoàng May 23 '17 at 08:43
  • When you say you're confused about the comparison when it's a string, are you referring to the Value/Meaning chart in your question from MSDN? If so, it doesn't matter what you're sorting. That chart just tells the int values you should return for your `IComparer`. See this example on MSDN: [link](https://msdn.microsoft.com/en-us/library/8ehhxeaf(v=vs.110).aspx). When comparing Box objects, they do a comparison of Height, Length, then Width, in that order. A Box with a bigger Volume, but smaller Height, it is considered smaller by their method. The chart just maps an int value to a meaning. – Poosh May 23 '17 at 15:45