如何将这个递归调用重写为循环

本文关键字:调用 重写 循环 递归 | 更新日期: 2023-09-27 18:25:25

为了简化代码以便更好地理解,我试图将此递归调用转换为while循环:

function getMusic(IStorageFolder folder) {
    int cnt = 0;
    var folders = await folder.GetFoldersAsync();
    if (folders != null)
        foreach (var fol in folders)
            await getMusic(fol);
    var files = await folder.GetFilesAsync();
    foreach (var file in files)
    {
        MusicProperties musicProperties = await file.Properties.GetMusicPropertiesAsync();
        source.Add(new Music("artist", "title", "album");
        cnt++;
    }
}

在代码中,sourceMusic类型的ObservableCollection(包含三个参数,如source.Add...行所示)。

然而,我一直没有成功。这是我的尝试,它似乎没有填充source列表。

private async Task getMusic(IStorageFolder folder)
{
    Stack<StorageFolder> fold = new Stack<StorageFolder>();
    int count = 0; int firstTen = 0;
    var folders = await folder.GetFoldersAsync();
    foreach (var indvFolder in folders)
        fold.Push(indvFolder);
    while (count < fold.Count)
    {
        var fol = fold.Pop();
        if (firstTen > 9)
            break;
        var files = await fol.GetFilesAsync();
        foreach (var file in files)
        {
            MusicProperties musicProperties = await file.Properties.GetMusicPropertiesAsync();
            source.Add(new Music("artist", "title", "album"));
            count++;
        }
        firstTen++;
    }
}

如何将这个递归调用重写为循环

fold.Pop()更改fold.Countfold.Count不包括已处理的项目,因此count < fold.Count没有意义。

相反,使用while (fold.Count > 0)

此外,这些东西应该在循环中,因为原始代码访问了子文件夹的子文件夹:

foreach (var indvFolder in fol.GetFoldersAsync())
    fold.Push(indvFolder);

设置只是

fold.Push(folder);

您试图在一个函数中完成太多的工作,这就是使代码复杂化的原因。从概念上讲,您在这里要做几个不同的事情,获取树中的所有文件夹,从所有文件夹中获取所有文件,以及从所有文件中获取所有属性。解散那些行动。

遍历文件系统以获取所有文件是一项您几乎已经完成的操作。事实上,由于像这样的树将一直以相同的方式遍历,您甚至可以将异步树遍历概括为一种与结构无关的算法:

public static async Task<IEnumerable<T>> TraverseAsync<T>(
    this IEnumerable<T> source
    , Func<T, Task<IEnumerable<T>>> childrenSelector)
{
    var queue = new Queue<T>(source);
    List<T> results = new List<T>();
    while (queue.Any())
    {
        var next = queue.Dequeue();
        results.Add(next);
        foreach (var child in await childrenSelector(next))
            queue.Enqueue(child);
    }
    return results;
}

使用此功能,您现在可以获取起始文件夹,遍历图形以获取所有文件夹,将这些文件夹映射到所有文件,然后将这些文件映射到它们的所有属性:

private async Task<IEnumerable<Music>> getMusic(IStorageFolder rootFolder)
{
    var folders = await new[] { rootFolder }
        .TraverseAsync(folder => folder.GetFoldersAsync());
    var files = (await Task.WhenAll(
        folders.Select(folder => folder.GetFilesAsync())))
        .SelectMany(folder => folder);
    var properties = (await Task.WhenAll(
        files.Select(file => file.GetMusicPropertiesAsync())))
        .SelectMany(property => property);
    return properties.Select(prop => new Music());
}

由于需要保持所有内容的异步性,这就变得有点混乱了。如果我们首先制作SelectMany:的异步版本

public static async Task<IEnumerable<TResult>> SelectManyAsync<TSource, TResult>(
    this Task<IEnumerable<TSource>> source,
    Func<TSource, Task<IEnumerable<TResult>>> resultSelector)
{
    var sourceSequence = await source;
    var sequences = await Task.WhenAll(sourceSequence.Select(resultSelector));
    return sequences.SelectMany(x => x);
}

它简化了代码:

private async Task<IEnumerable<Music>> getMusic(IStorageFolder rootFolder)
{
    var properties = await new[] { rootFolder }
        .TraverseAsync(folder => folder.GetFoldersAsync())
        .SelectManyAsync(folder => folder.GetFilesAsync())
        .SelectManyAsync(file => file.GetMusicPropertiesAsync());
    return properties.Select(prop => new Music());
}

这里需要注意的是,我们还确保该方法实际返回任务本身中具有的结果,而不是将列表作为副作用进行更改。这确保了Task类将负责所有必要的跨线程同步问题,并确保在异步操作实际完成之前无法访问结果集合(这可能是问题所在)。