【问题标题】:Flatten nested loops to one list with LINQ使用 LINQ 将嵌套循环展平为一个列表
【发布时间】:2014-04-02 19:43:52
【问题描述】:

我现在正在用 TPL 类替换我的旧并行化帮助程序类。当操作代码中出现错误时,我的旧代码已被证明非常不可靠,而且它似乎不是为我现在正在做的事情而构建的。

第一个工作列表很容易翻译成Parallel.ForEach。但是这里有一个嵌套和索引的循环,我不能这么容易解决。

int streamIndex = 0;
foreach (var playlist in selectedPlaylists)
{
    var localPlaylist = playlist;
    foreach (var streamFile in playlist.StreamFiles)
    {
        var localStreamFile = streamFile;
        var localStreamIndex = streamIndex++;
        // Action that uses localPlaylist, localStreamFile and localStreamIndex
        ...
        // Save each job's result to its assigned place in the list
        lock (streamsList)
        {
            streamsList[localStreamIndex] = ...;
        }
    }
}

局部变量用于正确的闭包支持,因为 foreach 迭代变量是共享的。

我正在考虑类似的事情

selectedPlaylists.SelectMany(p => p.StreamFiles)

但是我失去了每个流文件来自哪里的关联,以及应该是确定性的索引,因为它用于对结果列表中的结果进行排序。有没有办法保持这些与 Linq 的关联,并在枚举列表时添加该计数器?可能是这样(虚构的伪代码):

selectedPlaylists
    .SelectMany(p => new
    {
        Playlist = p,
        StreamFile = ~~each one of p.StreamFiles~~,
        Index = ~~Counter()~~
    })

我可以保留那些旧的嵌套 foreach 循环并将所有作业收集到一个列表中,然后使用 Parallel.Invoke,但这似乎比它需要的更复杂。我想知道是否有一个我还不知道的简单 Linq 功能。

【问题讨论】:

  • 查看我对 Cancelationtoken 和并行度的编辑

标签: c# linq foreach


【解决方案1】:

你可以做这样的事情......

//
Dictionary<int, object> streamsList = new Dictionary<int, object>();

// First create a composition that holds the playlist and the streamfile
selectedPlaylists.SelectMany(playList => playList.StreamFiles.Select(streamFile => new { PlayList = playList, StreamFile = streamFile }))
                 // thenfor all of theese add the respective index
                 .Select((composition, i) => new { StreamFile = composition.StreamFile, PlayList = composition.PlayList, LocalStreamIndex = i })
                 .AsParallel()

                 .WithCancellation(yourTokenGoesHere)
                 .WithDegreeOfParallelism(theDegreeGoesHere)

                 .ForAll(indexedComposition =>
                 {
                     object result =somefunc(indexedComposition.LocalStreamIndex, indexedComposition.PlayList, indexedComposition.StreamFile);;
                     lock(streamsList) // dont call the function insde the lock or the as parallel is useless.
                         streamsList[indexedComposition.LocalStreamIndex] = result;

                 });

【讨论】:

  • 这看起来不错。索引工作正确。我不能使用ForAll 方法,因为它不允许我设置ParallelOptions(MaxDegreeOfParallelism 和CancellationToken),但Linq 查询也适用于Parallel.ForEach。很高兴知道它存在!
  • 你可以... .AsParallel().WithCancellation(yourTokenGoesHere).WithDegreeOfParallelism(50).ForAll()
【解决方案2】:

要展平StreamFiles 并保持与PlayList 的关联并为它们编制索引,您可以使用以下查询:

int index = 0;
var query = selectedPlaylists
              .SelectMany(p => p.StreamFiles
                               .Select(s =>
                                          new {
                                                PlayList = p,
                                                Index = index++,
                                                StreamFile = s
                                              }));

【讨论】:

  • 我相信您的索引仅来自“第一”级迭代,并没有考虑到第二“级”。我的意思是它对于同一个播放列表的每个流文件都是一样的。
  • 很高兴你指出了这一点。索引在我的第一次测试中有效,但是当我在寻找一组特定的列表时,索引实际上是错误的。
  • @LonelyPixel 是的,这就是问题所在。但是很容易解决。查看更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-13
  • 1970-01-01
  • 2018-01-06
  • 1970-01-01
相关资源
最近更新 更多