Reputation: 5664
What is the best way extract the common file path from the list of file path strings in c#?
Eg: I have a list 5 file paths in List variable, like below
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
output should be c:\abc\pqr\tmp
Upvotes: 11
Views: 6028
Reputation: 11477
Keep a tally of each segment and keep going while all the paths start with the tally.
Implementation with captured variable and TakeWhile
:
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<string>();
var common = paths[0].Split('\\').TakeWhile ( segment =>
{
test.Add ( segment );
return paths.All ( path => path.StartsWith ( String.Join ("\\", test ) + "\\") ) ;
} );
Console.WriteLine ( String.Join ("\\", common ) ); // c:\abc\pqr\tmp
}
Implementation without captured variable and Reduce
:
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 common = paths[0].Split('\\').Aggregate("", (test, segment) => {
var prefix = test + segment + "\\";
return paths.All( path => path.StartsWith ( prefix ) ) ? prefix : test;
});
Console.WriteLine( common ); // c:\abc\pqr\tmp\
}
Upvotes: 1
Reputation: 69
The top answer fails for identical paths like e.g.:
string str1 = @"c:\dir\dir1\dir2\dir3";
string str2 = @"c:\dir\dir1\dir2\dir3";
This is better: Find common prefix of strings
however
.TakeWhile(s => s.All(d => d == s.First()))
should be
.TakeWhile(s =>
{
var reference = s.First();
return s.All(d => string.Equals(reference, d, StringComparison.OrdinalIgnoreCase));
})
Upvotes: 0
Reputation: 38
The selected solution does not work properly if one of the paths is exactly the directory name that should be returned. I think there should be:
from len in Enumerable.Range(0, matchingNames.Min(s => s.Length) + 1).Reverse()
Upvotes: 0
Reputation: 32428
Because everything is best solved with LINQ*:
*not everything is best solved with LINQ.
using System.Collections.Generic;
using System.IO;
using System.Linq;
class Program
{
static void Main(string[] args)
{
List<string> Files = new List<string>()
{
@"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());
}
}
The first line gets a list of lengths of possible matches to evaluate. We want the longest possibility first (so i reverse the enumeration which would be 0, 1, 2, 3; turning it into 3, 2, 1, 0).
I then get the string to match, which is simply a substring of the first entry of the given length.
I then filter the results, to ensure we only include possible matches that all files start with.
Finally, i return the first result, which will be the longest substring and call path.getdirectoryname to ensure if something has a few identical letters in the filenames it isn't included.
Upvotes: 18
Reputation: 65274
Use the first path as the iterator seed:
using System;
using System.Collections.Generic;
using System.IO;
namespace stackoverflow1
{
class MainClass
{
public static void Main (string[] args)
{
List<String> paths=new List<String>();
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<String> 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<list.Count;i++)
{
if (!list[i].StartsWith(s))
{
ok=false;
int p=s.LastIndexOf(Path.DirectorySeparatorChar);
if (p<0) return "";
s=s.Substring(0,p);
break;
}
}
if (ok) break;
}
return s;
}
}
}
}
Upvotes: 1
Reputation: 38077
Here is a quick implementation that just loops through the list of strings and then compares the characters at the beginning of the strings trimming from the original string:
List<string> list1 = new List<string>();
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<nextDir.Length &&
baseDir[index] == nextDir[index])
{
index++;
}
baseDir = baseDir.Substring(0, index);
}
}
MessageBox.Show(baseDir);
Upvotes: 1
Reputation: 82559
I would use a loop and on each string I'd split it with s.Split('\')
.
Then it iterate over the first element, and next element, saving them as I go.
When I find one that is different, I can return the last iteration's result.
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
}
Upvotes: 1
Reputation: 8792
I don't think there's a "trick"to get past using the brute force method to do this comparison, but you can optimize your solution somewhat:
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
Upvotes: 3
Reputation: 8613
You could break the paths down into segments (i.e. split by the backslash), then build back a segment at a time and compare the results until you find the end of the match. I doubt that is the best way, but it would work.
Upvotes: 0