TransWikia.com

Ordenar una lista alfabéticamente igual que Windows?

Stack Overflow en español Asked by Leodev on December 4, 2021

Les comento mi problema.

Estoy cargando una lista de tipo FileInfo con los nombres de los archivos de una carpeta, estos archivos los he ordenado alfabéticamente y noté que mi programa los carga todos, pero en un orden "alterno", porqué SI los ordena, pero no de la misma manera que lo hace Windows.

He renombrado y dibujado números en los archivos para que sea más sencillo entender el problema, porque a mi me costó un buen rato notar el error.

Windows muestra los archivos así:
introducir la descripción de la imagen aquí

Pero mi programa los muestra así:

introducir la descripción de la imagen aquí

Los nombres de los archivos son:

000_n.jpg
4_n.jpg
22_n.jpg
333_n.jpg
999_n.jpg

Si se fijan (y acá aparece el problema) la imagen que tiene dibujado un "2", se llama 4_n.jpg, cuyo nombre es más corto que las imagenes siguientes, por tanto Windows la coloca primero, pero al cargarlos a mi programa, el ordenado alfabético lo que hace es decir "el 4 está después del 2 y del 3" así que coloca la imagen en el penúltimo lugar, sin importar que el nombre sea más corto.

Técnicamente, ambas formas son correctas, pero Windows lo hace de una manera y el compilador lo hace de otra.

El código que usé para cargar las imágenes era:

List<FileInfo> files = new DirectoryInfo(theDirectory.FullName)
.GetFiles("*.*", SearchOption.TopDirectoryOnly)
.Where(s => s.Extension.MatchesWith(".jpg", ".jpeg", ".gif"))
.ToList<FileInfo>();

Y también traté de este modo:

IEnumerable<string> archivos = System.IO.Directory.EnumerateFiles(folder).OrderBy(filename => filename);

Pero aún así carga las imágenes en el orden "equivocado".

Lo que necesito es que al cargar las imágenes, éstas queden en el mismo orden que las ordena Windows.

Agradecería me indiquen qué estoy haciendo mal o si hay algún método que me permita conseguir lo que quiero, ya que entiendo la lógica de ambas operaciones, como lo hace Windows y como lo hace LinQ pero no logro solucionarlo.

EDIT:

Descubrí que si relleno los nombres con ceros mi programa cargará las imágenes en el orden correcto, así:

000_n.jpg
004_n.jpg
022_n.jpg
333_n.jpg
999_n.jpg

algo es algo, sirve para entender la lógica.

Gracias.

3 Answers

Finalmente logré solucionar mi problema:

NOTA: LA RESPUESTA CORRECTA ES LA PABLO GUTIERREZ.

Gracias a Pablo Gutiérrez que me dio la mejor forma de ordenar nombres con inicio numérico, ahora pude construir una versión mejorada de mi primera respuesta. Hice un ExtensionMethod para reutilizarlo:

public static List<FileInfo> SortAsWindows(this List<FileInfo> list)
{
    Regex regex = new Regex("^[0-9]*");
    SortedList<int, string> numberList = new SortedList<int, string>(list.Count);
    List<string> charlist = new List<string>();
    for (int i = 0; i < list.Count; i++)
    {
        var match = regex.Match(list[i].Name);
        if (match.Success)
        {
            int t;
            if (int.TryParse(match.Value, out t))
            {
                numberList.Add(t, list[i].FullName);
            }
            else
            {
                charlist.Add(list[i].FullName);
            }
        }
    }
    
    charlist = charlist.OrderBy(c => c).ToList<string>();
    
    List<FileInfo> sort = new List<FileInfo>();
    for (int i = 0; i < numberList.Count; i++)
    {
        sort.Add(new FileInfo(numberList.Values[i]));
    }
    
    for (int i = 0; i < charlist.Count; i++)
    {
        sort.Add(new FileInfo(charlist[i]));
    }
    
    
    return sort;
}

Y para usarlo sería así:

static void Main(string[] args)
{
    Console.Clear();
    string folder = @"C:UsersanteuDesktopNueva carpeta (3)Nueva carpeta";
    
    List<FileInfo> x = new DirectoryInfo(folder)
    .GetFiles("*.*", SearchOption.TopDirectoryOnly)
    .Where(s => s.Extension.MatchesWith(".jpg", ".jpeg", ".gif"))
    .ToList<FileInfo>()
    .SortAsWindows();

}

Básicamente es ordenar los nombres con inicio numérico usando la lógica de Pablo Gutiérrez y luego añadirle los nombres con inicio con letras que eso ya lo hace de forma nativa Linq.

Un saludo y gracias.

Answered by Leodev on December 4, 2021

En windows, desde XP en adelante, cuando los nombres de carpetas y archivos contienen números, el contenido numérico es ordenado como números y no como texto. 4 es menor que 22 y es por eso que muestra los archivos en ese orden. Mas info.

Por otra parte, cuando ordenas strings en C#, el orden es como texto sin importar si tiene números o no.

Solución

Utilizando como base esta respuesta de Josh a una pregunta similar en el sitio en ingles, escribí este código que usa la función StrCmpLogicalW.

Vale destacar que la función StrCmpLogicalW es parte del sistema operativo, es la misma que utiliza Windows Explorer y en consecuencia resultado siempre será igual.

Clase que implementa la interface IComparer

public class WindowsFileNameComparer : IComparer<FileInfo>
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    static extern int StrCmpLogicalW(String x, String y);
    public int Compare(FileInfo x, FileInfo y)
    {
        return StrCmpLogicalW(x.Name, y.Name);
    }
}

Uso de la nueva clase

Esta clase se pasa como argumento al método Sort de la lista como se indica a continuación:

...

//Obtiene la lista de FileInfo
List<FileInfo> files = new DirectoryInfo(folder)
    .GetFiles("*.*", SearchOption.TopDirectoryOnly)
    .Where(s => s.Extension.MatchesWith(".jpg", ".jpeg", ".gif"))
    .ToList<FileInfo>();

//Luego se ordena
files.Sort(new WindowsFileNameComparer());

/*
   en este punto, files contiene los nombres de archivos
   en el mismo orden que lo muestra el Explorador de Windows.
*/

...

Answered by Pablo Gutiérrez on December 4, 2021

Puedes aplicar length a tu campo para ser ordenado u otra forma es obteniendo el valor numerico con split y sobre este aplicar la ordenacion.

Ejemplo: opcion 1

 System.IO.Directory.EnumerateFiles(folder).OrderBy(f =>f.filename.Length).OrderBy(f=>f.filename);

opcion 2 utilizando split

System.IO.Directory.EnumerateFiles(folder).OrderBy(f => Int32.Parse(f.filename.Split('_')[0]));//tambien f.filename.Split('_').First()

Answered by jcHernande2 on December 4, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP