部分取消组合重复值列表

时间:2012-11-07 14:35:59

标签: c# linq

我知道如何使用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 个答案:

答案 0 :(得分: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);
        }
    }
}

答案 1 :(得分: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();

答案 2 :(得分:1)

SortedSet部分的元素中创建Detail。转换为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语句会变得更加丑陋。

答案 3 :(得分: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: ;
}