多维列表仅删除该级别的重复项

multidimensional list remove duplicates only for that level

我正在尝试从一个名为 Serato 的程序构建 crates 的树状视图,他们在程序中构建 crates 的方式是通过文件夹中的文件,但是问题是您可以有多个内部和外部硬盘驱动器对于具有相同名称的文件夹,子文件夹也可以与之前的 parent 文件夹同名。

例如,一个文件夹可以是这样的;

C:/Music%%House.crate
C:/Music%%House%%Chicago.crate
C:/Music%%House%%Garage.crate
C:/Music%%Hip-Hop%%Classic.crate
C:/Music%%Hip-Hop%%Remixes.crate
C:/Music%%Hip-Hop%%New.crate
C:/Music%%House%%Hip-Hop%%House Remixes.crate
C:/Videos%%House.crate
C:/Videos%%House%%Chicago.crate
C:/House%%Unsorted.crate
Hip-Hop%%Unsorted.crate
C:/Hip-Hop%%Wedding.crate

如您所见,嵌套在里面的是与 children 同名的板条箱。 现在我可以在另一个驱动器上有另一个文件夹,相同但可能有另一个添加的文件夹,如;

D:/Music%%House.crate
D:/Music%%House%%Chicago.crate
D:/Music%%House%%Garage.crate
D:/Music%%Hip-Hop%%Classic.crate
D:/Music%%Hip-Hop%%Remixes.crate
D:/Music%%Hip-Hop%%New.crate
D:/Music%%Hip-Hop%%Transitions.crate
D:/Music%%House%%Hip-Hop%%House Remixes.crate
D:/Videos%%House.crate
D:/Videos%%House%%Chicago.crate
D:/House%%Unsorted.crate
D:/Hip-Hop%%Unsorted.crate
D:/Hip-Hop%%Wedding.crate

我需要做的是在 WPF 树视图中让它看起来像这样;

Hip-Hop
--Unsorted
--Wedding
House
--Unsorted
Music
--House
----Chichago
----Garage
----Hip-Hop
------House Remixes
--Hip-Hop
----Classic
----Remixes
----New
Videos
--House
----Chicago

路径字符串如下所示:

C:/Music%%House%%Hip-Hop%%House Remixes.crate

这是我的问题 我不知道从哪里开始填充自定义列表以添加这些避免在 children 的某些级别中重复名称,但保留并添加卷到自定义 list.Hopefully我解释的很好,我是初学者

更新了 Bionic Code 的答案,仍然出现错误。

public class FileSystemItem : IEquatable<FileSystemItem>
{
  public FileSystemItem(string name, FileSystemItem parent)
  {
    this.Name = name;
    this.Parent = parent;
    this.Children = new List<FileSystemItem>();
    this.Volumes = new List<DriveInfo>();
  }

  public string Name { get; }
  public FileSystemItem Parent { get; }
  public IList<FileSystemItem> Children { get; }
  public IList<DriveInfo> Volumes { get; }

  public bool Equals(FileSystemItem? other) => (this.Name, this.Parent).Equals((other.Name, other.Parent));
}





if (patsh.Count > 0)
{


    var treeLevelIndex = new List<Dictionary<string, FileSystemItem>>();

    foreach (string path in paths)
    {
       FileSystemItem parentFileSystemItem = null;
                    int level = 0;

       string pathRoot = Path.GetPathRoot(path);
       DriveInfo drive = new DriveInfo(pathRoot);
       string pathWithoutRoot = path.Substring(drive.Name.Length);

       foreach (string directoryName in pathWithoutRoot.Split(new char[] { '%', '%' }, StringSplitOptions.RemoveEmptyEntries))
       {
            
          if (treeLevelIndex.Count == level)
          {
             treeLevelIndex.Add(new Dictionary<string, FileSystemItem>());
          }

          var levelIndex = treeLevelIndex[level];

          // Check if path segment already exists (merge)
          if (levelIndex.TryGetValue(directoryName, out FileSystemItem indexedParent))
          {
             // Don't add duplicates
             if (!indexedParent.Volumes.Any(volume => volume.Name.Equals(drive.Name, StringComparison.Ordinal)))
             {
                 indexedParent.Volumes.Add(drive);
             }

             parentFileSystemItem = indexedParent;
             level++;
             continue;
          }

          /* Create index entry for the current level */

          var childFileSystemItem = new FileSystemItem(Path.GetFileNameWithoutExtension(directoryName), parentFileSystemItem);
          childFileSystemItem.Volumes.Add(drive);

          // Tree leaf. Don't add duplicates. 
          // Does not need to be indexed as it will not hold child nodes.
          if (Path.HasExtension(directoryName))
          {
             if (!parentFileSystemItem?.Children.Contains(childFileSystemItem) ?? false)
             {
                parentFileSystemItem.Children.Add(childFileSystemItem);
             }
          }
          else // An unindexed tree node.
          {
              parentFileSystemItem?.Children.Add(childFileSystemItem);
                            
           // THIS IS WHERE THE EXPECTION UNHANDLED breaks at (below)                                
              levelIndex.Add(childFileSystemItem.Name, childFileSystemItem);
              parentFileSystemItem = childFileSystemItem;
          }
          level++;
      }
  }

  List<FileSystemItem> treeViewSource = treeLevelIndex.First().Values.ToList();
                
               

  TvCrates.ItemsSource = treeViewSource;

}

如上备注所示,该区域出现错误;

else // An unindexed tree node.
{
    parentFileSystemItem?.Children.Add(childFileSystemItem);
                                         
    levelIndex.Add(childFileSystemItem.Name, childFileSystemItem);
    parentFileSystemItem = childFileSystemItem;
}
level++;

来自

levelIndex.Add(childFileSystemItem.Name, childFileSystemItem);

出现错误:

System.ArgumentException: 'An item with the same key has already been added.'

这是路径的实际列表:(更改路径文件) https://file.io/pI6WqAIgjS4l

您只需从根(而不是叶)开始逐层构建树。您必须从驱动器名称开始。然后使用字典作为查找 table 以有效地识别和合并重复项。

要识别重复项,必须让数据模型实现 IEquatable。或者实现自定义 IEqualityComparer:

public class FileSystemItem : IEquatable<FileSystemItem>
{
  public FileSystemItem(string name, FileSystemItem parent)
  {
    this.Name = name;
    this.Parent = parent;
    this.Children = new List<FileSystemItem>();
    this.Volumes = new List<DriveInfo>();
  }

  public string Name { get; }
  public FileSystemItem Parent { get; }
  public IList<FileSystemItem> Children { get; }
  public IList<DriveInfo> Volumes { get; }

  public bool Equals(FileSystemItem? other) 
    => (this.Name, this.Parent).Equals((other.Name, other.Parent));
}

建树的算法也很简单。这是一个免费示例:

// Your input 
var paths = new List<string>();

// The list of lookup tables which is also the flattened tree structure. 
// Each tree level is equivalent to a path segment (directory).
// Each level has its own lookup table to allow the same name on each level.       
var treeLevelIndex = new List<Dictionary<string, FileSystemItem>>();
      
foreach (string path in paths)
{
  if (!Path.HasExtension(path))
  {
    continue;  
  }

  FileSystemItem parentFileSystemItem = null;
  int level = 0;

  string pathRoot = Path.GetPathRoot(path);
  DriveInfo drive = new DriveInfo(pathRoot);
  int crateStartIndex = Math.Max(path.LastIndexOf(Path.DirectorySeparatorChar), path.LastIndexOf(Path.AltDirectorySeparatorChar));
  string pathWithoutRoot = crateStartIndex > 0 
    ? path.Substring(crateStartIndex) 
    : path;

  char[] separators =  
  { 
    '%', 
    Path.DirectorySeparatorChar, // '\'
    Path.AltDirectorySeparatorChar // '/'
  };
  foreach (string directoryName in pathWithoutRoot.Split(separators, StringSplitOptions.RemoveEmptyEntries))
  {
    if (treeLevelIndex.Count == level)
    {
      treeLevelIndex.Add(new Dictionary<string, FileSystemItem>());
    }

    var levelIndex = treeLevelIndex[level];

    // Check if path segment already exists (merge)
    if (levelIndex.TryGetValue(directoryName, out FileSystemItem indexedParent))
    {
      // Don't add duplicates
      if (!indexedParent.Volumes.Any(volume => volume.Name.Equals(drive.Name, StringComparison.Ordinal)))
      {
        indexedParent.Volumes.Add(drive);
      }

      parentFileSystemItem = indexedParent;
      level++;
      continue;
    }

    /* Create index entry for the current level */

    var pathWithoutExtension = Path.GetFileNameWithoutExtension(directoryName);
    var childFileSystemItem = new FileSystemItem(pathWithoutExtension, parentFileSystemItem);
    childFileSystemItem.Volumes.Add(drive);

    // Tree leaf. Don't add duplicates. 
    // Does not need to be indexed as it will not hold child nodes,
    // except the node with an extension is a root node.
    if (Path.HasExtension(directoryName))
    {
      if (!parentFileSystemItem?.Children.Contains(childFileSystemItem) ?? false)
      {
        parentFileSystemItem.Children.Add(childFileSystemItem);
      }
      if (level == 0)
      {
        levelIndex.Add(directoryName, childFileSystemItem);
      }
    }
    else // An unindexed tree node.
    {
      parentFileSystemItem?.Children.Add(childFileSystemItem);
      levelIndex.Add(directoryName, childFileSystemItem);
      parentFileSystemItem = childFileSystemItem;
    }
    level++;
  }
}

List<FileSystemItem> treeViewSource = treeLevelIndex.First().Values.ToList();

MainWindow.xaml

<TreeView>
  <TreeView.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type local:FileSystemItem}"
                              ItemsSource="{Binding Children}">
      <TextBlcok Text="{Binding Name}" />
    </HierarchicalDataTemplate>
  </TreeView.ItemTemplate>
</TreeView>