0

我的问题“现代语言如何处理局部变量和递归?” 来自编写文件搜索方法。基本上,

public ArrayList getMusicFiles(string directory){
    ArrayList songpaths = new ArrayList();

    string[] localFiles = System.IO.Directory.GetFiles(directory);
    for(int i=0; i<localFiles.Length-1; i++)
        if(isMusicFile(localFiles[i]))
            songpaths.add(localFiles[i]);

    string[] localFolders = System.IO.Directory.GetDirectories(directory);
    for(int i=0; i<localFolder.length-1; i++)
        getMusicFiles(localFolder[i]);
}

所以,问题是这将在每次递归时重新声明“歌曲路径”。在 VB 中,您可以将歌曲路径声明为静态,我认为这可以解决这个问题。有没有一种很好的 C# 方法可以不覆盖 ArrayList?

4

5 回答 5

8

请注意,我是在您打算使用directory参数代替rootDir类级变量的假设下进行操作的。


您在这里有两个选择。

hacky,但更有效的方法

在这种情况下,您传递列表对象。我将使用List<string>而不是ArrayList.

public List<string> getMusicFiles(string directory) {
  var list = new List<string>();
  getMusicFiles(list, directory);
  return list;
}

private void getMusicFilesInternal(List<string> songpaths, string directory)
{
  string[] localFiles= System.IO.Directory.GetFiles(directory);
  for(int i=0; i<localFiles.Length-1; i++) {
    if(isMusicFile(localFiles[i])) {
      songpaths.add(localFiles[i]);
    }
  }
  string[] localFolders= System.IO.Directory.GetDirectories(directory);
  for(int i=0; i<localFolder.Length-1; i++) {
    getMusicFiles(songpaths, localFolder[i]);
  }
}

效率较低但实用的方式

每次递归时返回列表并聚合结果:

public IList<string> getMusicFiles(string directory)
{
  List<string> songpaths = new List<string>();
  string[] localFiles= System.IO.Directory.GetFiles(directory);
  for(int i=0; i<localFiles.Length-1; i++) {
    if(isMusicFile(localFiles[i])) {
      songpaths.add(localFiles[i]);
    }
  }
  string[] localFolders= System.IO.Directory.GetDirectories(directory);
  for(int i=0; i<localFolder.Length-1; i++) {
    songpaths.AddRange(getMusicFiles(localFolder[i]));
  }
  return songpaths;
}

您也可以使用延迟执行来实现这一点,这仍然不如第一个示例高效,但在使用结果时为您提供了更大的灵活性:

public IEnumerable<string> getMusicFiles(string directory)
{
  string[] localFiles= System.IO.Directory.GetFiles(directory);
  for(int i=0; i<localFiles.Length-1; i++) {
    if(isMusicFile(localFiles[i])) {
      yield return localFiles[i];
    }
  }
  string[] localFolders= System.IO.Directory.GetDirectories(directory);
  for(int i=0; i<localFolder.Length-1; i++) {
    foreach (var j in getMusicFiles(localFolder[i])) {
      yield return j;
    }
  }
}

这将返回一个 enumerable,每次枚举它时都会执行搜索操作,类似于 Linq 查询的工作方式。您可以调用ToList()结果来执行查询并将结果存储在一个列表中,您可以多次枚举该列表而无需再次执行查询。

如果我清理所有代码,以下是我可能会选择使用的变体。您的原始代码有几个问题(您从数组的Length属性中减去一个,即使这会导致您跳过最后一个元素,并且还有一些其他拼写错误)。

public IEnumerable<string> getMusicFiles(string directory)
{
  foreach (var file in System.IO.Directory.GetFiles(directory)) {
    if (isMusicFile(file)) {
      yield return file;
    }
  }

  foreach (var dir in System.IO.Directory.GetDirectories(directory)) {
    foreach (var musicFile in getMusicFiles(dir)) {
      yield return musicFile;
    }
  }
}

如果您担心 的性能foreach,请不要。首先,您应该首先针对可读性进行编码,然后才是性能,只有在发现瓶颈时才进行优化。其次,当您foreach在数组类型上使用时,编译器会将其转换为Length基于等效的迭代,而不是通过IEnumerator<T>.

于 2013-05-07T22:33:31.850 回答
2

您可以选择使用累加器策略,并将工作强加给需要将数组传递给它的辅助函数:

public List<string> GetMusicFiles(string directory)
{
    List<string> songPaths = new List<string>();

    GetMusicFilesHelper(directory, songPaths);

    return songPaths;
}

private void GetMusicFilesHelper(string directory, List<string> paths)
{
    string[] localFiles = Directory.GetFiles(directory);
    for(int i = 0; i < localFiles.Length; i++) 
    {
        if(isMusicFile(localFiles[i])) paths.Add(localFiles[i]);
    }

    string[] localFolders = Directory.GetDirectories(directory);
    for(int i = 0; i < localFolder.length; i++)
    {
        GetMusicFilesHelper(localFolder[i], paths);
    }
}

另一种选择是一起跳过递归并让Directory.GetFiles您为您完成工作:

public List<string> GetMusicFiles(string directory)
{
    List<string> songPaths = new List<string>();

    // TODO: pick a better search pattern
    string[] paths = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories);
    foreach (string path in paths)
    {
        if (IsMusicFile(path))
        {
            songPaths.Add(path);
        }
    }
}

如果您使用的是 .Net 4.0+,这将成为小菜一碟Directory.EnumerateFiles

public IEnumerable<string> GetMusicFiles(string directory)
{
    return Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
                    .Where(ff => IsMusicFile(ff));
}
于 2013-05-07T22:34:46.983 回答
1

代码有点难读,但无论如何......我建议编写一个实际用于递归的方法的私有版本,并将 ArrayList 作为参数传递。

于 2013-05-07T22:31:17.010 回答
0

您可以通过传入当前列表来做到这一点 - 这样您就可以在递归中附加列表。像这样的东西...

public ArrayList getMusicFiles(string directory, ArrayList data, string rootDir) {
    string[] localFiles = System.IO.Directory.GetFiles(rootDir);
    for (int i = 0; i < localFiles.Length - 1; i++) 
        if (isMusicFile(localFiles[i])) 
            songpaths.add(localFiles[i]);

    string[] localFolders = System.IO.Directory.GetDirectories(rootDir);
    for (int i = 0; i < localFolder.length - 1; i++) 
        data.AddRange(getMusicFiles(localFolder[i]));
    return data;
}
于 2013-05-07T22:34:33.040 回答
-1
public ArrayList getMusicFiles(string directory, ArrayList songpaths){
  string[] localFiles= System.Io.Directory.GetFiles(rootDir);

  for(int i=0; i<localFiles.Length-1; i++) if(isMusicFile(localFiles[i]))
      songpaths.add(localFiles[i]);

  string[] localFolders= System.IO.Directory.GetDirectories(rootDir);
for(int i=0; i<localFolder.length-1; i++) getMusicFiles(localFolder[i]);

new ArrayList()然后在第一次调用该方法时传入 a 。

于 2013-05-07T22:30:50.953 回答