come estrarre il percorso file comune dall’elenco dei percorsi dei file in c #

Qual è il modo migliore per estrarre il percorso file comune dall’elenco delle stringhe del percorso file in c #?

Ad esempio: ho un elenco di 5 percorsi di file nella variabile List, come di seguito

c: \ abc \ pqr \ tmp \ campione \ b.txt
c: \ abc \ pqr \ tmp \ new2 \ c1.txt
c: \ abc \ pqr \ tmp \ b2.txt
c: \ abc \ pqr \ tmp \ b3.txt
c: \ abc \ pqr \ tmp \ tmp2 \ b2.txt

l’output dovrebbe essere c: \ abc \ pqr \ tmp

Perché tutto è meglio risolto con LINQ *:
* Non tutto è meglio risolto con LINQ.

 using System.Collections.Generic; using System.IO; using System.Linq; class Program { static void Main(string[] args) { List Files = new List() { @"c:\abc\pqr\tmp\sample\b.txt", @"c:\abc\pqr\tmp\new2\c1.txt", @"c:\abc\pqr\tmp\b2.txt", @"c:\abc\pqr\tmp\b3.txt", @"c:\a.txt" }; var MatchingChars = from len in Enumerable.Range(0, Files.Min(s => s.Length)).Reverse() let possibleMatch = Files.First().Substring(0, len) where Files.All(f => f.StartsWith(possibleMatch)) select possibleMatch; var LongestDir = Path.GetDirectoryName(MatchingChars.First()); } } 

Spiegazione:

La prima riga ottiene un elenco di lunghezze di possibili corrispondenze da valutare. Vogliamo prima la possibilità più lunga (quindi invertire l’enumerazione che sarebbe 0, 1, 2, 3, trasformandola in 3, 2, 1, 0).

Quindi ottengo la stringa corrispondente, che è semplicemente una sottostringa della prima voce della lunghezza specificata.

Quindi filtro i risultati, per assicurarci di includere solo le possibili corrispondenze con cui iniziano tutti i file.

Infine, restituisco il primo risultato, che sarà la sottostringa più lunga e chiameremo path.getdirectoryname per assicurarmi che qualcosa abbia alcune lettere identiche nei nomi dei file che non è incluso.

Non penso che ci sia un “trucco” per superare il metodo della forza bruta per fare questo confronto, ma puoi ottimizzare la tua soluzione in qualche modo:

 Prepare: Find the shortest string Repeat: See if all of the other strings contain it If so, you're done If not, remove one or more characters 

Vorrei usare un ciclo e su ogni stringa l’avrei diviso con s.Split('\') .

Quindi itera sul primo elemento e sul prossimo elemento, salvandoli mentre procedo.

Quando ne trovo uno diverso, posso restituire il risultato dell’ultima iterazione.

 string commonPath(string[] paths) { // this is a Java notation, I hope it's right in C# as well? Let me know! string[][] tokens = new string[paths.length][]; for(int i = 0; i < paths.Length; i++) { tokens[i] = paths.Split('\\'); } string path = ""; for(int i = 0; i < tokens[0].Length; i++) { string current = tokens[0][i]; for(int j = 1; j < tokens.Length; j++) { if(j >= tokens[i].Length) return path; if(current != tokens[i][j]) return path; } path = path + current + '\\'; } return path; // shouldn't reach here, but possible on corner cases } 

Ecco un’implementazione rapida che scorre semplicemente l’elenco di stringhe e quindi confronta i caratteri all’inizio del taglio delle stringhe dalla stringa originale:

 List list1 = new List(); list1.Add(@"c:\abc\pqr\tmp\sample\b.txt"); list1.Add(@"c:\abc\pqr\tmp\new2\c1.txt"); list1.Add(@"c:\abc\pqr\tmp\b2.txt"); list1.Add(@"c:\abc\pqr\tmp\b3.txt"); list1.Add(@"c:\abc\pqr\tmp\tmp2\b2.txt"); string baseDir = ""; foreach (var item in list1) { if (baseDir == "") baseDir = System.IO.Path.GetDirectoryName(item); else { int index = 0; string nextDir = System.IO.Path.GetDirectoryName(item); while (index< baseDir.Length && index 

Usa il primo percorso come seme del iteratore:

 using System; using System.Collections.Generic; using System.IO; namespace stackoverflow1 { class MainClass { public static void Main (string[] args) { List paths=new List(); paths.Add(@"c:\abc\pqr\tmp\sample\b.txt"); paths.Add(@"c:\abc\pqr\tmp\new2\c1.txt"); paths.Add(@"c:\abc\pqr\tmp\b2.txt"); paths.Add(@"c:\abc\pqr\tmp\b3.txt"); paths.Add(@"c:\abc\pqr\tmp\tmp2\b2.txt"); Console.WriteLine("Found: "+ShortestCommonPath(paths)); } private static String ShortestCommonPath(IList list) { switch (list.Count) { case 0: return null; case 1: return list[0]; default: String s=list[0]; while (s.Length>0) { bool ok=true; for (int i=1;i 

È ansible suddividere i percorsi in segmenti (ovvero dividere il backslash), quindi ribuild un segmento alla volta e confrontare i risultati fino a trovare la fine della corrispondenza. Dubito che sia il modo migliore, ma funzionerebbe.

Tieni traccia di ogni segmento e continua mentre tutti i percorsi iniziano con il conteggio.

 void Main() { string[] paths = new[] { @"c:\abc\pqr\tmp\sample\b.txt", @"c:\abc\pqr\tmp\new2\c1.txt", @"c:\abc\pqr\tmp\b2.txt", @"c:\abc\pqr\tmp\b3.txt", @"c:\abc\pqr\tmp\tmp2\b2.txt"}; var test = new List(); var common = paths[0].Split('\\').TakeWhile ( segment => { test.Add ( segment ); return paths.All ( path => path.StartsWith ( String.Join ("\\", test ) + "\\") ) ; } ); Console.WriteLine ( String.Join ("\\", common ) ); } 

La soluzione selezionata non funziona correttamente se uno dei percorsi è esattamente il nome della directory che deve essere restituito. Penso che ci dovrebbe essere:

da len in Enumerable.Range (0, matchingNames.Min (s => s.Length) + 1 ). Reverse ()

La risposta superiore fallisce per percorsi identici come ad es .:

 string str1 = @"c:\dir\dir1\dir2\dir3"; string str2 = @"c:\dir\dir1\dir2\dir3"; 

Questo è meglio: trova il prefisso comune delle stringhe

però

 .TakeWhile(s => s.All(d => d == s.First())) 

dovrebbe essere

 .TakeWhile(s => { var reference = s.First(); return s.All(d => string.Equals(reference, d, StringComparison.OrdinalIgnoreCase)); })