Mahender
Mahender

Reputation: 5664

how to extract common file path from list of file paths in c#

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

Answers (9)

Mongus Pong
Mongus Pong

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

Dr.Gee
Dr.Gee

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

Ota Milink
Ota Milink

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

George Duckett
George Duckett

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());
    }
}

Explanation:

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

Eugen Rieck
Eugen Rieck

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

John Koerner
John Koerner

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

corsiKa
corsiKa

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

Matt Mills
Matt Mills

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

Samuel Slade
Samuel Slade

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

Related Questions