En esta publicación, voy a adoptar un enfoque muy simple para algo que muchos desarrolladores de .NET buscan en Internet, ya que yo mismo busqué esta solución, pero es un poco complicado de encontrar, ya que la mayoría de las soluciones publicadas no funcionan.
Ejemplo de uso actual: Clasificación CSharp clasificación DirectoryInfo.GetFiles por nombre clasificación natural 1
Incluso si utiliza una cláusula ORDER BY para intentar ordenar, esto no funcionará porque SQL Server ordenará las cadenas mediante ordenación alfanumérica.
Cómo funciona el algoritmo de clasificación de cadenas alfanuméricas
Este tipo de algoritmo utiliza el código ASCII para ordenar los datos, comparando carácter por carácter hasta que la cadena termina o el código ASCII de una cadena es más pequeño que el de la otra. Por lo tanto, en una comparación entre el Archivo 100 y el Archivo 20, la comparación se realizará de la siguiente manera:
Cada carácter de la palabra "Archivo" se comparará entre las 2 cadenas. Como son iguales, el algoritmo continuará con el resto de la cadena.
El carácter “1” se comparará con el carácter “2”. Al ser más pequeño, la clasificación termina ahí, colocando “Archivo 100” antes de “Archivo 20”
Cómo funciona el algoritmo de clasificación de cadenas Natural Sort
Así como a mí me incomoda este orden, que es el estándar para la gran mayoría de lenguajes de programación, a mucha gente tampoco le gusta que “100” vaya antes de “2000” y para dar una visión más “humanizada” se creó el algoritmo Natural Sort, que separa los caracteres numéricos de las letras, y los ordena por separado. Los números se comparan numéricamente (donde 100 es mayor que 20) y las cadenas continúan usando código ASCII.
Para implementar el algoritmo Natural Sort, encontré código en el blog de James McCormack para crear una clase ICompare en C# y así ordenar mis archivos en mi función (y cualquier otra lista de archivos que necesite ordenar).
Ver el código fuente de la clase ICompare
public class NaturalSortComparer<T> : IComparer<string>, IDisposable
{
private readonly bool _isAscending;
public NaturalSortComparer(bool inAscendingOrder = true)
{
this._isAscending = inAscendingOrder;
}
public int Compare(string x, string y)
{
throw new NotImplementedException();
}
int IComparer<string>.Compare(string x, string y)
{
if (x == y)
return 0;
string[] x1, y1;
if (!table.TryGetValue(x, out x1))
{
x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
table.Add(x, x1);
}
if (!table.TryGetValue(y, out y1))
{
y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
table.Add(y, y1);
}
int returnVal;
for (var i = 0; i < x1.Length && i < y1.Length; i++)
{
if (x1[i] == y1[i]) continue;
returnVal = PartCompare(x1[i], y1[i]);
return _isAscending ? returnVal : -returnVal;
}
if (y1.Length > x1.Length)
{
returnVal = 1;
}
else if (x1.Length > y1.Length)
{
returnVal = -1;
}
else
{
returnVal = 0;
}
return _isAscending ? returnVal : -returnVal;
}
private static int PartCompare(string left, string right)
{
int x, y;
if (!int.TryParse(left, out x))
return string.Compare(left, right, StringComparison.InvariantCulture);
return !int.TryParse(right, out y) ? string.Compare(left, right, StringComparison.InvariantCulture) : x.CompareTo(y);
}
private Dictionary<string, string[]> table = new Dictionary<string, string[]>();
public void Dispose()
{
table.Clear();
table = null;
}
}
Ahora necesito cambiar mi función de listado de archivos (también incluí un indicador booleano para enumerar archivos dentro de subdirectorios o no) e incluir una función OrderBy a través de la biblioteca LINQ.
En este extracto:
foreach (var fileInfo in directories)
Lo cambié a este extracto:
foreach (var fileInfo in directories.OrderBy(fileInfo => fileInfo.Name, new NaturalSortComparer<string>()))
Y con esto nuestro problema quedó resuelto, donde tenemos el siguiente resultado: Clasificación CSharp DirectoryInfo.GetFiles por nombre clasificación natural resuelta
Ver el código fuente completo de esta función
using System.IO;
using System.Collections;
using System.Data.SqlTypes;
using System.Linq;
public partial class UserDefinedFunctions
{
private class FileProperties
{
public SqlInt32 NrLinha;
public SqlString Tipo;
public SqlString FileName;
public SqlString FileNameWithoutExtension;
public SqlString DirectoryName;
public SqlString Extension;
public SqlString FullName;
public SqlInt64 FileSize;
public SqlBoolean IsReadOnly;
public SqlDateTime CreationTime;
public SqlDateTime LastAccessTime;
public SqlDateTime LastWriteTime;
public FileProperties(SqlInt32 nrLinha, SqlString tipo, SqlString fileName, SqlString fileNameWithoutExtension, SqlString directoryName, SqlString extension, SqlString fullName, SqlInt64 fileSize, SqlBoolean isReadOnly, SqlDateTime creationTime, SqlDateTime lastAccessTime, SqlDateTime lastWriteTime)
{
NrLinha = nrLinha;
Tipo = tipo;
FileNameWithoutExtension = fileNameWithoutExtension;
FileName = fileName;
DirectoryName = directoryName;
Extension = extension;
FullName = fullName;
FileSize = fileSize;
IsReadOnly = isReadOnly;
CreationTime = creationTime;
LastAccessTime = lastAccessTime;
LastWriteTime = lastWriteTime;
}
}
[Microsoft.SqlServer.Server.SqlFunction(
FillRowMethodName = "listarArquivos",
TableDefinition = "Nr_Linha int, Fl_Tipo nvarchar(50), Nm_Arquivo nvarchar(500), Nm_Arquivo_Sem_Extensao nvarchar(500), Nm_Diretorio nvarchar(500), " +
"Nm_Extensao nvarchar(20), Nm_Completo nvarchar(500), Qt_Tamanho bigint, Fl_Somente_Leitura bit, Dt_Criacao datetime, " +
"Dt_Ultimo_Acesso datetime, Dt_Modificacao datetime"
)]
public static IEnumerable fncArquivo_Listar(string Ds_Diretorio, string Ds_Filtro, bool Fl_Recursivo)
{
var recursivo = (Fl_Recursivo) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var filePropertiesCollection = new ArrayList();
var dirInfo = new DirectoryInfo(Ds_Diretorio);
var files = dirInfo.GetFiles(Ds_Filtro, recursivo);
var directories = dirInfo.GetDirectories(Ds_Filtro);
var contador = 1;
foreach (var fileInfo in directories.OrderBy(fileInfo => fileInfo.Name, new NaturalSortComparer<string>()))
{
filePropertiesCollection.Add(new FileProperties(
contador,
"Diretorio",
fileInfo.Name,
fileInfo.Name,
fileInfo.Name,
"",
fileInfo.FullName + "\\",
0,
false,
fileInfo.CreationTime,
fileInfo.LastAccessTime,
fileInfo.LastWriteTime
));
contador++;
}
foreach (var fileInfo in files.OrderBy(fileInfo => fileInfo.DirectoryName, new NaturalSortComparer<string>()).ThenBy(fileInfo => fileInfo.Name, new NaturalSortComparer<string>()))
{
filePropertiesCollection.Add(new FileProperties(
contador,
"Arquivo",
fileInfo.Name,
(fileInfo.Extension.Length > 0) ? fileInfo.Name.Replace(fileInfo.Extension, "") : "",
fileInfo.DirectoryName,
fileInfo.Extension.ToLower(),
fileInfo.FullName,
fileInfo.Length,
fileInfo.IsReadOnly,
fileInfo.CreationTime,
fileInfo.LastAccessTime,
fileInfo.LastWriteTime
));
contador++;
}
return filePropertiesCollection;
}
protected static void listarArquivos(object objFileProperties, out SqlInt32 nrLinha, out SqlString tipo, out SqlString fileName, out SqlString fileNameWithoutExtension, out SqlString directoryName, out SqlString extension, out SqlString fullName, out SqlInt64 fileSize, out SqlBoolean isReadOnly, out SqlDateTime creationTime, out SqlDateTime lastAccessTime, out SqlDateTime lastWriteTime)
{
var fileProperties = (FileProperties) objFileProperties;
nrLinha = fileProperties.NrLinha;
tipo = fileProperties.Tipo;
fileName = fileProperties.FileName;
fileNameWithoutExtension = fileProperties.FileNameWithoutExtension;
directoryName = fileProperties.DirectoryName;
extension = fileProperties.Extension;
fullName = fileProperties.FullName;
fileSize = fileProperties.FileSize;
isReadOnly = fileProperties.IsReadOnly;
creationTime = fileProperties.CreationTime;
lastAccessTime = fileProperties.LastAccessTime;
lastWriteTime = fileProperties.LastWriteTime;
}
}
¡Muchas gracias por visitarnos y nos vemos en el próximo post!
Dirceu Resende
Arquitecto de Bases de Datos y BI · Microsoft MVP · MCSE, MCSA, MCT, MTA, MCP.
Comentários (0)
Carregando comentários…