如何根据键值组将字典拆分为多个字典?

时间:2021-01-10 18:28:28

标签: c# linq dictionary split

我有一个字典 ReactDOM.render(<App />,document.getElementById('root')); ,其中包含一组 KeyValuePairs KVP。
出于此说明的目的,这些信息存储在字典中 Dictionary<string, string>
这些旨在使用 _test
用于 SQL INSERT 但是,这会将所有值插入到 SQL 中的一行中,其中预期的结果是多行。

command.Parameters.AddWithValue(value.key, value.Value) 中的示例源;

_test

我希望根据出现在键 AC 下的值分组将这些值拆分为多个字典,因此输出将是;
然后我可以使用 [0] {[Data, ONCE,UPON,A,TIME,UP,AN,ENORMOUS,GREEN,BEANSTALK,IN,THE,CLOUDS]} [1] {[Ident, 123456789]} [2] {[Time1, 2020-01-01T13:56:56.123]} [3] {[Time2, 2020-01-01T13:58:02.356]} [4] {[D, 57]} [5] {[D1, 508,967,123,456,234,456,232,124,167,198,985,786]} [6] {[AC, 1,1,1,1,2,2,2,4,3,3,1,1]} 对每个字典进行交互以执行每个插入。

dict-1
(包含基于 AC 为 1 的前 4 个值)

foreach

dict-2
(包含基于 AC 值为 2 的接下来 3 个值)

dictionary<string,string>

[0] {[Data, ONCE,UPON,A,TIME]}
[1] {[Ident, 123456789]}
[2] {[Time1, 2020-01-01T13:56:56.123]}
[3] {[Time2, 2020-01-01T13:58:02.356]}
[4] {[D, 57]}
[5] {[D1, 508,967,123,456]}
[6] {[AC, 1,1,1,1]}

dict-3
(包含基于 AC 4 的单个值)

dictionary<string,string>

[0] {[Data, UP,AN,ENORMOUS]}
[1] {[Ident, 123456789]}
[2] {[Time1, 2020-01-01T13:56:56.123]}
[3] {[Time2, 2020-01-01T13:58:02.356]}
[4] {[D, 57]}
[5] {[D1, 234,456,232]}
[6] {[AC, 2,2,2]}

dict-4

dictionary<string,string>

[0] {[Data, GREEN]}
[1] {[Ident, 123456789]}
[2] {[Time1, 2020-01-01T13:56:56.123]}
[3] {[Time2, 2020-01-01T13:58:02.356]}
[4] {[D, 57]}
[5] {[D1, 124]}
[6] {[AC, 4]}

dict-5

dictionary<string,string>

[0] {[Data, BEANSTALK,IN]}
[1] {[Ident, 123456789]}
[2] {[Time1, 2020-01-01T13:56:56.123]}
[3] {[Time2, 2020-01-01T13:58:02.356]}
[4] {[D, 57]}
[5] {[D1, 167,198]}
[6] {[AC, 3,3]}

他们是使用 Linq 方法或查询语法来做到这一点的好方法吗?

2 个答案:

答案 0 :(得分:0)

using System;
using System.Collections.Generic;
using System.Linq;

var _test = new Dictionary<string, string>
{
    { "Data", "ONCE,UPON,A,TIME,UP,AN,ENORMOUS,GREEN,BEANSTALK,IN,THE,CLOUDS"},
    { "Ident", "123456789"},
    { "Time1", "2020-01-01T13:56:56.123"},
    { "Time2", "2020-01-01T13:58:02.356"},
    { "D", "57"},
    { "D1", "508,967,123,456,234,456,232,124,167,198,985,786"},
    { "AC", "1,1,1,1,2,2,2,4,3,3,1,1"},
};

var testWithLists = _test
    .ToDictionary(
        x => x.Key,
        y => y.Value.Split(',').ToList()
    );

var prevAC = string.Empty;
var result = new List<Dictionary<string, string>>();

var resultPart = new Dictionary<string, List<string>>();

for (int i = 0; i < testWithLists["AC"].Count; i++)
{
    var curAC = testWithLists["AC"][i];

    if ((curAC != prevAC && i != 0) || i == (testWithLists["AC"].Count - 1))
        result.Add(resultPart.ToDictionary(x => x.Key, y => string.Join(',', y.Value)));

    if (curAC != prevAC)
    {
        resultPart = new Dictionary<string, List<string>>
        {
            { "Data", new List<string>() },
            { "Ident", testWithLists["Ident"] },
            { "Time1", testWithLists["Time1"] },
            { "Time2", testWithLists["Time2"] },
            { "D", testWithLists["D"] },
            { "D1", new List<string>() },
            { "AC", new List<string>() },
        };
    }

    resultPart["Data"].Add(testWithLists["Data"][i]);
    resultPart["D1"].Add(testWithLists["D1"][i]);
    resultPart["AC"].Add(testWithLists["AC"][i]);   

    prevAC = curAC;
}

没有检查数据、d1、ac 长度是否相等,但它的工作原理与您预期的一样。 result 变量是 5 个字典的列表。

附言新的 C# 9.0 record 类型在这里很有用。

P.S.P.S.抱歉,解决方案不理想:)

答案 1 :(得分:0)

使用一些我已经拥有的用于处理 IEnumerable 的扩展方法,包括用于运行的 group by 运算符、APL Scan 运算符以及此处看到的一些基本实用方法:

public static class IEnumerableExt {
    // returns IEnumerables of runs
    public static IEnumerable<IEnumerable<T>> Runs<T>(this IEnumerable<T> items) {
        var cmp = EqualityComparer<T>.Default;
        using (var itemsEnum = items.GetEnumerator()) {
            bool notAtEnd;

            IEnumerable<T> NextRun() {
                T curItem;
                do {
                    curItem = itemsEnum.Current;
                    yield return curItem;
                    notAtEnd = itemsEnum.MoveNext();
                } while (notAtEnd && cmp.Equals(itemsEnum.Current, curItem));
            }

            notAtEnd = itemsEnum.MoveNext();
            while (notAtEnd)
                yield return NextRun().ToList();
        }
    }

    // APL Scan operator (like Aggregate, but returns intermediate results)
    // T combineFn(T PrevResult, T CurItem)
    // First PrevResult = items.First()
    // First CurItem = items.Skip(1).First()
    // output is items.First(), combineFn(PrevResult, CurItem), ...
    public static IEnumerable<T> Scan<T>(this IEnumerable<T> items, Func<T, T, T> combineFn) {
        using (var itemsEnum = items.GetEnumerator()) {
            if (itemsEnum.MoveNext()) {
                T prevResult = itemsEnum.Current;

                for (; ; ) {
                    yield return prevResult;
                    if (!itemsEnum.MoveNext())
                        yield break;
                    prevResult = combineFn(prevResult, itemsEnum.Current);
                }
            }
        }
    }

    // prepend a sequence with a single element
    public static IEnumerable<T> FollowedBy<T>(this T first, IEnumerable<T> rest) => first.AsSingleton().Concat(rest);
    // Join strings together with a separator
    public static string Join(this IEnumerable<string> s, string sep) => String.Join(sep, s);

    // convert an element into a single element IEnumerable
    public static IEnumerable<T> AsSingleton<T>(this T item) => new[] { item };
}

您可以创建要拆分的每个组合值的起始位置序列,然后创建表示要为每个可拆分键值收集的值的范围序列。

我使用了一个变量来指定可拆分的键名,以防将来它们可能会发生变化,并且只是复制了所有不可拆分的键名/值对。

var splitKeyNames = new[] { "Data", "D1", "AC" };
var splitValues = splitKeyNames.ToDictionary(n => n, n => _test[n].Split(','));

var starts = splitValues["AC"].Runs()
                .Select(run => run.Count())
                .Scan((acc, runCt) => acc + runCt);
var ranges = 0.FollowedBy(starts).Zip(starts, (start, end) => start .. end);

var ans = ranges.Select(range => _test.Keys.Where(key => !splitKeyNames.Contains(key))
                                           .Select(key => new { key, value = _test[key] })
                                           .Concat(
                                                splitKeyNames.Select(key => new {
                                                                                key,
                                                                                value = splitValues[key][range].Join(",")
                                                                            }))
                                           .ToDictionary(kv => kv.key, kv => kv.value)
                );