7

我知道如何使用 LINQ 对数据进行分组,也知道如何将其拆分为单独的项目,但我不知道如何仅部分取消分组。

我有一组看起来像这样的数据:

var data = new Dictionary<Header, Detail>()
{
    { new Header(), new Detail { Parts = new List<string> { "Part1", "Part1", "Part2" } } }
};

为了正确处理这个问题,我需要重复部分的每个实例都是字典中的单独条目(尽管它是否仍然是字典并不重要 -IEnumerable<KeyValuePair<Header, Detail>>完全可以接受)。但是,我不想Parts完全拆分列表 - 列表中有不同的部分很好。

具体来说,我希望最终数据如下所示:

{
  { new Header(), new Detail { Parts = new List<string> { "Part1", "Part2" } } },
  { new Header(), new Detail { Parts = new List<string> { "Part1" } } },
}

对于更复杂的示例:

var data = new Dictionary<Header, Detail>()
{
    { new Header(1), new Detail { Parts = new List<string> { "Part1", "Part1", "Part2" } } },

    { new Header(2), new Detail { Parts = new List<string> { "Part1", "Part2" } } },

    { new Header(3), new Detail { Parts = new List<string> { "Part1", "Part2", "Part2", "Part2", "Part3", "Part3"} } }
};

var desiredOutput = new List<KeyValuePair<Header, Detail>>()
{
    { new Header(1), new Detail { Parts = new List<string> { "Part1", "Part2" } } },
    { new Header(1), new Detail { Parts = new List<string> { "Part1" } } },

    { new Header(2), new Detail { Parts = new List<string> { "Part1", "Part2" } } },

    { new Header(3), new Detail { Parts = new List<string> { "Part1", "Part2", "Part 3" } } },
    { new Header(3), new Detail { Parts = new List<string> { "Part2", "Part3" } } },
    { new Header(3), new Detail { Parts = new List<string> { "Part2" } } }
};

有什么建议吗?

4

4 回答 4

2

不,实际上没有一个现有的 LINQ 函数可以完成所有这些工作。

本质上,如果您想Parts按每个字符串进行分组,并将每个组视为一行,那么您想要的是每个“列”。我使用辅助函数GetNthValues(旨在模拟 LINQ 风格的函数)来做到这一点。一旦你有了它,这几乎只是对每个部分进行分组、调用函数并将结果放回字典的问题。

public static Dictionary<Header, Detail> Ungroup(Dictionary<Header, Detail> input)
{
    var output = new Dictionary<Header, Detail>();

    foreach (var key in input.Keys)
    {
        var lookup = input[key].Parts.ToLookup(part => part);

        bool done = false;

        for (int i = 0; !done; i++)
        {
            var parts = lookup.GetNthValues(i).ToList();
            if (parts.Any())
            {
                output.Add(new Header(key.Value), new Detail { Parts = parts });
            }
            else
            {
                done = true;
            }
        }
    }

    return output;
}

public static IEnumerable<TElement> GetNthValues<TKey, TElement>(
    this ILookup<TKey, TElement> source, int n)
{
    foreach (var group in source)
    {
        if (group.Count() > n)
        {
            yield return group.ElementAt(n);
        }
    }
}
于 2012-11-07T15:10:52.733 回答
2

Linq 在这里对你帮助不大,但这里有一个扩展方法,它可以解决问题:

public static IEnumerable<KeyValuePair<Header, Detail>> UngroupParts(
    this IEnumerable<KeyValuePair<Header, Detail>> data)
{
    foreach (var kvp in data)
    {
        Header header = kvp.Key;
        List<string> parts = kvp.Value.Parts.ToList();
        do
        {
            List<string> distinctParts = parts.Distinct().ToList();
            Detail detail = new Detail() { Parts = distinctParts };
            yield return new KeyValuePair<Header, Detail>(header, detail);

            foreach (var part in distinctParts)
                parts.Remove(part);
        }
        while (parts.Any());
    }
}

用法:

var desiredOutput = data.UngroupParts();
于 2012-11-07T15:29:09.093 回答
1

SortedSet从零件中的元素创建一个Detail。这,转换为 a List,是你的第一个组,SortedSet事实上,它只包含每个元素的一个实例Detail

从原始Detail部分(或它的副本)中删除它。重复直到细节的大小为零。

编辑:

尝试使用类似于单个 Linq 语句的东西。为了简单起见,让我使用列表

var total = new List<List<string>>() { 
    new List<string>(), 
    new List<string>(), 
    new List<string>(), 
    new List<string>(), 
    new List<string>(), 
    new List<string>() 
};

//the statement

var q = k.Aggregate(total, (listOlists, singleStrin) => {
    listOlists.Where(l => !l.Contains(singleStrin)).First().Add(singleStrin);
    return listOlists;
});

基本上,我创建了一个累加器函数,仅当列表不包含该元素时才将元素添加到字符串列表中。列表本身包含在累加器列表中。你需要初始化累加器列表,否则 Linq 语句会变得更难看。

于 2012-11-07T14:47:00.303 回答
0

这会将字符串列表分解为多个没有重复的字符串列表。

List<string> oldParts = new List<string> { "Part1", "Part2", "Part2", "Part2", "Part3", "Part3" };
List<List<string>> allLists = new List<List<string>>();

foreach (string currentPart in oldParts)
{
    foreach (List<string> currentList in allLists)
    {
         // if currentList doesn't have the part, then 
         //    add part to the currentList, and process next part
         if (!currentList.Contains(currentPart))
         {
             currentList.Add(currentPart);
             goto NextPart;
         }
    }
    // if we get here, the part is already contained on in the lists
    // so add a new list to allLists
    // and add the part to the new list
    allLists.Add(new List<string> { currentPart });

    NextPart: ;
}     
于 2012-11-07T14:57:46.960 回答