Поиск по HierarchicalData с рекурсией - PullRequest
1 голос
/ 05 июля 2011

Я строю древовидную структуру со списком ScanItem. Класс ScanItem на самом деле:

public class ScanItem
    {
        public string FullPath { get; set; }
        public string Name
        {
            get
            {
                return Path.GetFileName(FullPath);
            }

        }
        public DateTime ModifiedDate { get; set; }
        public DateTime CreatedDate { get; set; }
        public FileAttributes Attributes { get; set; }
        public bool IsDirectory { get; set; }


        public string Extension
        {
            get
            {
                if (IsDirectory)
                    return "Folder";
                else
                    return Path.GetExtension(Name);
            }
        }

        public UInt64 Size { get; set; }            
    }

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

    public class ScanFile : ScanItem
    {

    }
    public class ScanDir : ScanItem 
    {
        public List<ScanItem> Items { get; set; }
        public ScanDir()
        {
            Items = new List<ScanItem>();
        }                
    }

Обратите внимание, что класс ScanFile аналогичен классу ScanItem, а класс ScanDir имеет дополнительное свойство, называемое Items, и будет содержать собственный список элементов.

Так что, если я перебираю эту директорию (C: \ Temp): enter image description here

Мой список будет содержать:

enter image description here

обратите внимание, что если я разверну один объект ScanDir, я получу другой список:

enter image description here

для того, чтобы заполнить следующее древовидное представление:

enter image description here

Таким образом, я смог заполнить этот список с помощью рекурсии, выполнив поиск файлов и каталогов по определенному пути.

Я просто хотел объяснить мою ситуацию, потому что в Интернете есть несколько мест, которые позволяют вам фильтровать древовидную структуру, и это то, что я действительно хочу сделать. Но было бы хорошо, если бы я мог перебирать каждый элемент в Списке, а затем удалять его, если некоторые критерии не выполнены:

На самом деле я пытался использовать следующий рекурсивный метод для фильтрации своих результатов.

public List<ScanItem> search(List<ScanItem> items)
    {
        var filter = new List<ScanItem>();

        foreach (var item in items)
        {
            if (!item.FullPath.Contains("stringIwantToLookFor")) continue;
            filter.Add(item);
            if (item.IsDirectory)
            {
                search(((ScanDir)item).Items);                    
            }                
        }

        return filter;
    }

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

EDIT:

Другими словами, если я хочу, чтобы все элементы, содержащие «X.txt», в моем списке, я просто хотел увидеть: enter image description here

Ответы [ 4 ]

2 голосов
/ 06 июля 2011

Я бы сделал это так: создайте public abstract ScanItem Seach(string s) на вашем ScanItem. Затем вы можете вызвать его с помощью строки, которую хотите найти.

Реальная реализация будет выглядеть так:

ScanFile

public override ScanItem Seach(string s)
{
    if (Name.Contains(s))
        return this;

    return null;
}

ScanDir:

public override ScanItem Seach(string s)
{
    var results = Items.Select(i => i.Seach(s)).Where(i => i != null).ToList();
    if (results.Any())
    {
        var result = (ScanDir)MemberwiseClone();
        result.Items = results;
        return result;
    }

    return null;
}

Реализация в ScanFile проста: если файл совпадает, вернуть его, иначе вернуть null. В ScanDir рекурсивно вызывайте Search для всех дочерних элементов. Если какой-либо из них вернул не-null, создайте копию текущего объекта и установите Items копии только для тех, которые совпадают. Если ничего не найдено, верните null.

Обратите внимание, что поиск будет выполняться только по именам файлов, а не по каталогам. Но если вы хотите сделать это, такая модификация будет простой.

1 голос
/ 05 июля 2011

Вы должны относиться к каталогам немного по-другому, потому что теперь, если корневой каталог не соответствует критериям, процедура немедленно завершится.

Попробуйте: немного измените ScanItem:

public class ScanItem {
  ...
  public virtual bool IsDirectory { get; }
  ...
}

добавьте это в свой файл ScanFile:

public class ScanFile : ScanItem {
  public override bool IsDirectory {
    get { return false; }
  }
}

и это к вашему scanDir:

public class ScanDir : ScanItem {
  public List<ScanItem> Items { get; set; }
  public ScanDir() {
    Items = new List<ScanItem>();
  }

  public ScanDir CopyWithoutChildren() {
    return new ScanDir() {
      FullPath = this.FullPath,
      ModifiedDate = this.ModifiedDate,
      CreatedDate = this.CreatedDate,
      Attributes = this.Attributes,
      Size = this.Size
    };
  }

  public override bool IsDirectory {
    get { return true; }
  }
}

Теперь выполните фильтрацию файлов, исключив пустые каталоги:

public List<ScanItem> search(List<ScanItem> items) {
  var filter = new List<ScanItem>();

  foreach(var item in items) {
    if(item.IsDirectory) {
      List<ScanItem> potential = search(((ScanDir)item).Items);
      if(potential.Count > 0) {
        ScanDir dir = ((ScanDir)item).CopyWithoutChildren();
        dir.Items.AddRange(potential);
        filter.Add(dir);
      }
    } else {
      if(!item.FullPath.Contains("stringIwantToLookFor")) continue;
      filter.Add(item);
    }
  }

  return filter;
}

Я не проверял это, но я думаю, это должно делать то, что вы хотите.

0 голосов
/ 06 июля 2011

поэтому Если я ищу файлы, содержащие foo, этот метод заполняет файлы, содержащие foo, в списке 'newList'. Я должен был бы установить этот список равным новому списку, прежде чем вызывать этот метод. Очевидно, мне не хватает базовой реализации, такой как изменение foo для параметра и т. Д. Мне также не хватает удалить пустые каталоги, над которыми я работаю.

    private List<ScanDir> history = new List<ScanDir>();
    private ScanDir LastDir;
    private List<ScanItem> newList = new List<ScanItem>();
    public void Search(List<ScanItem> allItems) //adds files that contain foo
    {
        bool updateLastDir = false;

        foreach(ScanItem s in allItems)
        {
            if (updateLastDir)
            {
                history = (from a in history
                           select a).Distinct().ToList();

                LastDir = null;
                for (int i = history.Count - 1; i >= 0; i--)
                {
                    if (history[i].FullPath == Directory.GetParent(s.FullPath).ToString())
                    {
                        LastDir = history[i];
                        break;
                    }                        
                }

                updateLastDir = false;                                        
            }
            if (s.IsDirectory)
            {
                var temp = new ScanDir { FullPath = s.FullPath, IsDirectory = true, comparePath = s.comparePath, Attributes = s.Attributes };

                if (LastDir == null)
                {
                    newList.Add(temp);                        
                }
                else
                {
                    LastDir.Items.Add(temp);

                }

                LastDir = temp;
                history.Add(LastDir);

                Search(((ScanDir)s).Items);

                history.RemoveAt(history.Count - 1);

                updateLastDir = true;


            }
            else
            {
                if (s.Name.Contains("Foo")) // then add it
                {
                    if (LastDir == null)                        
                        newList.Add(s);                        
                    else                        
                        LastDir.Items.Add(s);                       
                }
            }
        }

    }
0 голосов
/ 05 июля 2011

Я понял, что мой комментарий к вашему посту может быть недостаточно описательным, поэтому я написал некоторый псевдокод на C #, чтобы продемонстрировать, к чему я клоню.

Вот пример использования шаблона Visitor дляосуществлять поиск полиморфным, слабо связанным способом:

interface FilesystemVistor
{
  void Visit (FilesystemItem item);
}
interface FilesystemItem
{
  void Accept(FilesystemVistor visitor);
  string Name;
}

class Directory : FilesystemItem
{
  private FilesystemItem[] _children;
  public void Accept(FilesystemVistor visitor) {
    visitor.Visit(this);
    foreach(FilesystemItem item in _children)
    {
      visitor.Visit(item);
    }
  }
}
class File : FilesystemItem
{
  public void Accept(FilesystemVistor visitor) {
    visitor.Visit(this);
  }
}

class FilesystemSearcher : FilesystemVistor
{
  private List<string> _results;
  public void Visit(FilesystemItem item) {
    if (item.Name == "Foo") { _results.Add(item.Name); }
  }
}

Этот дизайн, основанный на шаблоне посетителя, позволит вам реализовать любой вид поиска, не имея алгоритма поиска, который должен «знать» что-либо оструктура файловой системы и файловая система не нуждаются в дополнительном свойстве, таком как «IsDirectory», чтобы раскрыть детали своей реализации.

...