Neste post vou fazer uma abordagem bem simples sobre algo que muitos desenvolvedores .NET buscam na internet, como eu mesmo busquei essa solução, mas que é um pouco complicado de encontrar, pois a maioria das soluções postadas não funciona.
Exemplo do uso atual: CSharp sort sorting DirectoryInfo.GetFiles by name natural sort 1
Mesmo que você utilize uma cláusula ORDER BY para tentar ordenar, isso não vai funcionar, porque o SQL Server irá fazer a ordenação de strings utilização a ordenação alfanumérica.
Como funciona o algoritmo de ordenação de strings alfanuméricas
Esse tipo de algoritmo utiliza o código ASCII para ordenar os dados, comparando caractere por caractere até terminar a string ou o código ASCII de uma string for menor que o da outra. Desta forma, numa comparação entre Arquivo 100 e Arquivo 20, a comparação será feita da seguinte forma:
Cada caractere da palavra “Arquivo” será comparada entre as 2 strings. Como elas são iguais, o algoritmo irá prosseguir para o restante da string
O caractere “1” será comparado com o caractere “2”. Como ele é menor, a ordenação termina aí, colocando “Arquivo 100” antes de “Arquivo 20”
Como funciona o algoritmo de ordenação de strings Natural Sort
Assim como eu me incomodo com essa ordenação, que é a padrão pela grande maioria das linguagens de programação, muita gente também não gosta do “100” vir antes do “2000” e por para dar uma visão mais “humanizada”, foi-se criado o algoritmo de Natural Sort, que separa caracteres numéricos de letras, e os ordena separadamente. Os números são comparados de forma numérica (onde 100 é maior que 20), e as strings ele continua utilizando o código ASCII.
Para implementar o algoritmo Natural Sort, encontrei um código no blog do James McCormack para criar uma classe ICompare no C# e assim, ordenar os meus arquivos na minha função (e qualquer outra lista de arquivos que você precisar ordenar).
Visualizar Código-fonte da classe 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;
}
}
Agora eu preciso alterar a minha função de listar arquivos (também incluí uma flag booleana para listar arquivos dentro de subdiretórios ou não) e incluir uma função de OrderBy através da biblioteca LINQ.
Neste trecho:
foreach (var fileInfo in directories)
Eu alterei para esse trecho:
foreach (var fileInfo in directories.OrderBy(fileInfo => fileInfo.Name, new NaturalSortComparer<string>()))
E com isso, o nosso problema foi resolvido, onde temos o seguinte resultado: CSharp sort sorting DirectoryInfo.GetFiles by name natural sort solved
Visualizar Código-fonte completo dessa função
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;
}
}
Muito obrigado pela visita e até o próximo post!
Dirceu Resende
Arquiteto de Banco de Dados e BI · Microsoft MVP · MCSE, MCSA, MCT, MTA, MCP.
Comentários (0)
Carregando comentários…