A coisa mais fácil a fazer é apenas P / Invocar a função interna do Windows e usá-la como a função de comparação em IComparer
:
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
private static extern int StrCmpLogicalW(string psz1, string psz2);
Michael Kaplan tem alguns exemplos de como essa função funciona aqui e as alterações feitas no Vista para fazê-lo funcionar de maneira mais intuitiva. O lado positivo dessa função é que ela terá o mesmo comportamento da versão do Windows em que é executada, no entanto, isso significa que ela difere entre as versões do Windows, portanto, é necessário considerar se isso é um problema para você.
Portanto, uma implementação completa seria algo como:
[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogicalW(string psz1, string psz2);
}
public sealed class NaturalStringComparer : IComparer<string>
{
public int Compare(string a, string b)
{
return SafeNativeMethods.StrCmpLogicalW(a, b);
}
}
public sealed class NaturalFileInfoNameComparer : IComparer<FileInfo>
{
public int Compare(FileInfo a, FileInfo b)
{
return SafeNativeMethods.StrCmpLogicalW(a.Name, b.Name);
}
}
Comparer<T>
vez de implementarIComparer<T>
, obterá uma implementaçãoIComparer
interna da interface (não genérica) que chama seu método genérico, para uso em APIs que usam isso. É basicamente gratuito também: basta excluir o "I" e mudarpublic int Compare(...)
parapublic override int Compare(...)
. O mesmo paraIEqualityComparer<T>
eEqualityComparer<T>
.Apenas pensei em adicionar isso (com a solução mais concisa que eu poderia encontrar):
O texto acima preenche todos os números da string no comprimento máximo de todos os números em todas as strings e usa a string resultante para classificar.
A conversão para (
int?
) é para permitir coleções de strings sem nenhum número (.Max()
em um enumerável vazio lança umInvalidOperationException
).fonte
.DefaultIfEmpty().Max()
vez de transmitir paraint?
. Também vale a pena fazer umsource.ToList()
para evitar re-enumerar o enumerável.Nenhuma das implementações existentes parecia ótima, então eu escrevi as minhas. Os resultados são quase idênticos à classificação usada pelas versões modernas do Windows Explorer (Windows 7/8). As únicas diferenças que eu vi são 1) embora o Windows costumava (por exemplo, XP) manipular números de qualquer tamanho, agora é limitado a 19 dígitos - o meu é ilimitado, 2) o Windows fornece resultados inconsistentes com certos conjuntos de dígitos Unicode - o meu funciona bom (embora não compare numericamente dígitos de pares substitutos; nem o Windows) e 3) o meu não pode distinguir tipos diferentes de pesos de classificação não primários se ocorrerem em seções diferentes (por exemplo, "e-1é" vs " é1e- "- as seções antes e depois do número têm diferenças de peso diacrítico e de pontuação).
A assinatura corresponde ao
Comparison<string>
delegado:Aqui está uma classe de wrapper para uso como
IComparer<string>
:Exemplo:
Aqui está um bom conjunto de nomes de arquivos que eu uso para testar:
fonte
Solução C # pura para linq orderby:
http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html
fonte
A resposta de Matthews Horsleys é o método mais rápido que não altera o comportamento, dependendo da versão do Windows em que o seu programa está sendo executado. No entanto, pode ser ainda mais rápido criando a regex uma vez e usando RegexOptions.Compiled. Também adicionei a opção de inserir um comparador de cadeias para que você possa ignorar maiúsculas e minúsculas, se necessário, e melhorar um pouco a legibilidade.
Usado por
São necessários 450ms para classificar 100.000 strings em comparação com 300ms para a comparação padrão de .net - muito rápido!
fonte
Minha solução:
Resultados:
fonte
Você precisa ter cuidado - lembro-me vagamente de ler que StrCmpLogicalW, ou algo parecido, não era estritamente transitivo, e observei que os métodos de classificação do .NET às vezes ficam presos em loops infinitos se a função de comparação quebrar essa regra.
Uma comparação transitiva sempre relatará que a <c se a <b e b <c. Existe uma função que faz uma comparação de ordem de classificação natural que nem sempre atende a esse critério, mas não me lembro se é StrCmpLogicalW ou algo mais.
fonte
CultureInfo
possui uma propriedadeCompareInfo
, e o objeto que ele retorna pode fornecerSortKey
objetos. Estes, por sua vez, podem ser comparados e garantem transitividade.Este é o meu código para classificar uma string com caracteres alfa e numéricos.
Primeiro, este método de extensão:
Em seguida, basta usá-lo em qualquer lugar do seu código como este:
Como isso funciona ? Substituindo por zeros:
Funciona com múltiplos números:
Espero que isso ajude.
fonte
Adicionando a resposta de Greg Beech (porque eu acabei de procurar por isso), se você quiser usar isso no Linq, poderá usar o
OrderBy
que leva umIComparer
. Por exemplo:fonte
Aqui está um exemplo relativamente simples que não usa P / Invoke e evita qualquer alocação durante a execução.
Ele não ignora os zeros iniciais, então
01
vem depois2
.Teste de unidade correspondente:
fonte
Na verdade, eu o implementei como um método de extensão
StringComparer
para que você possa fazer, por exemplo:StringComparer.CurrentCulture.WithNaturalSort()
ouStringComparer.OrdinalIgnoreCase.WithNaturalSort()
.A resultante
IComparer<string>
pode ser usado em todos os lugares comoOrderBy
,OrderByDescending
,ThenBy
,ThenByDescending
,SortedSet<string>
, etc. E você ainda pode facilmente emenda maiúsculas e minúsculas, cultura, etc.A implementação é bastante trivial e deve ter um bom desempenho, mesmo em grandes seqüências.
Também o publiquei como um pequeno pacote NuGet , para que você possa fazer:
O código, incluindo comentários da documentação XML e conjunto de testes, está disponível no repositório NaturalSort.Extension GitHub .
O código inteiro é este (se você ainda não pode usar o C # 7, basta instalar o pacote NuGet):
fonte
Aqui está uma maneira ingênua de LINQ sem regex de uma linha (emprestada do python):
fonte
Dump()
. Obrigado por apontar.Expandindo algumas das respostas anteriores e fazendo uso de métodos de extensão, vim com as seguintes advertências de possíveis enumerações enumeráveis múltiplas ou problemas de desempenho relacionados ao uso de vários objetos regex ou à chamada desnecessária de regex, que sendo dito, ele usa ToList (), que pode negar os benefícios em coleções maiores.
O seletor suporta digitação genérica para permitir que qualquer delegado seja atribuído; os elementos na coleção de origem são alterados pelo seletor e convertidos em seqüências de caracteres com ToString ().
fonte
Inspirada na solução de Michael Parker, aqui está uma
IComparer
implementação que você pode usar em qualquer um dos métodos de pedidos linq:fonte
Precisávamos de uma classificação natural para lidar com o texto com o seguinte padrão:
Por alguma razão, quando olhei pela primeira vez no SO, não encontrei este post e implementei o nosso. Comparado a algumas das soluções apresentadas aqui, embora com conceito semelhante, poderia ter o benefício de ser talvez mais simples e fácil de entender. No entanto, embora eu tenha tentado analisar gargalos de desempenho, ainda é uma implementação muito mais lenta que a padrão
OrderBy()
.Aqui está o método de extensão que eu implemento:
A idéia é dividir as seqüências originais em blocos de dígitos e não dígitos (
"\d+|\D+"
). Como essa é uma tarefa potencialmente cara, é feita apenas uma vez por entrada. Em seguida, usamos um comparador de objetos comparáveis (desculpe, não consigo encontrar uma maneira mais adequada de dizê-lo). Ele compara cada bloco ao seu bloco correspondente na outra string.Gostaria de receber feedback sobre como isso pode ser melhorado e quais são as principais falhas. Observe que a manutenção é importante para nós neste momento e atualmente não a estamos usando em conjuntos de dados extremamente grandes.
fonte
Uma versão mais fácil de ler / manter.
fonte
Deixe-me explicar meu problema e como consegui resolvê-lo.
Problema: - Classifique os arquivos com base em FileName dos objetos FileInfo que são recuperados de um Diretório.
Solução: - Selecionei os nomes dos arquivos no FileInfo e aparei a parte ".png" do nome do arquivo. Agora, basta fazer List.Sort (), que classifica os nomes dos arquivos na ordem de classificação Natural. Com base nos meus testes, descobri que ter .png atrapalha a ordem de classificação. Dê uma olhada no código abaixo
fonte