Как извлечь общий путь к файлу из списка путей к файлам в C # - PullRequest
10 голосов
/ 20 декабря 2011

Каков наилучший способ извлечь общий путь к файлу из списка строк пути к файлу в c #?

Например: У меня есть список 5 путей к файлам в переменной списка, как показано ниже

C: \ а \ PQR \ TMP \ Образец \ b.txt
C: \ а \ PQR \ TMP \ new2 \ c1.txt
C: \ а \ PQR \ TMP \ b2.txt
C: \ а \ PQR \ TMP \ b3.txt
c: \ abc \ pqr \ tmp \ tmp2 \ b2.txt

вывод должен быть c: \ abc \ pqr \ tmp

Ответы [ 9 ]

16 голосов
/ 20 декабря 2011

Поскольку все лучше всего решить с помощью LINQ *:
* не все лучше всего решить с помощью 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());
    }
}

Объяснение:

Первая строка получает список длин возможных совпадений для оценки.Сначала мы хотим получить самую длинную возможность (поэтому я переворачиваю перечисление, которое было бы 0, 1, 2, 3; превращая его в 3, 2, 1, 0).

Затем я получаю строку для сопоставления, котораяэто просто подстрока первой записи заданной длины.

Затем я фильтрую результаты, чтобы убедиться, что мы включаем только возможные совпадения, с которых начинаются все файлы.

Наконец, я возвращаю первоерезультат, который будет самой длинной подстрокой, и вызовите path.getdirectoryname, чтобы убедиться, что у чего-то есть несколько идентичных букв в именах файлов, которые не включены.

3 голосов
/ 20 декабря 2011

Я не думаю, что есть какая-то хитрость, чтобы пройти через метод грубой силы, чтобы сделать это сравнение, но вы можете несколько оптимизировать свое решение:

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
1 голос
/ 20 декабря 2011

Использовать первый путь в качестве начального числа итератора:

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

    }
}
1 голос
/ 20 декабря 2011

Вот быстрая реализация, которая просто перебирает список строк и затем сравнивает символы в начале обрезки строк с исходной строкой:

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);
1 голос
/ 20 декабря 2011

Я бы использовал цикл, и в каждой строке я бы разбил его на s.Split('\').

Затем он проходит по первому элементу и следующему элементу, сохраняя их на ходу.

Когда я нахожу другой, я могу вернуть результат последней итерации.

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
}
0 голосов
/ 28 августа 2018

Сбой верхнего ответа для идентичных путей, таких как, например:

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

Это лучше: Найти общий префикс строк

однако

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

должно быть

.TakeWhile(s =>
      {
          var reference = s.First();
          return s.All(d => string.Equals(reference, d, StringComparison.OrdinalIgnoreCase));
      })
0 голосов
/ 19 марта 2018

Выбранное решение не работает должным образом, если один из путей является именно именем каталога, который должен быть возвращен.Я думаю, что должно быть:

от len в Enumerable.Range (0, matchNames.Min (s => s.Length) + 1 ). Reverse ()

0 голосов
/ 20 декабря 2011

Ведите подсчет каждого сегмента и продолжайте, пока все пути начинаются с подсчета.

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 ) );
}
0 голосов
/ 20 декабря 2011

Вы можете разбить пути на сегменты (то есть разделить их на обратную косую черту), затем построить сегмент за раз и сравнить результаты, пока не найдете конец совпадения.Я сомневаюсь, что это лучший способ, но он сработает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...