我有一个struct字典,其中一个成员是一个包含适用于每个字典项的不同元素的列表。
我想针对每个项目加入这些元素,以便过滤它们和/或按元素对它们进行分组。
在SQL中我熟悉加入表/查询以获得所需的多行,但我是C#/ Linq的新手。由于“列”可以是已经与正确的字典项关联的对象/列表,我想知道如何使用它们来执行连接?
以下是结构示例:
name elements
item1 list: elementA
item2 list: elementA, elementB
我想要一个提供此输出的查询(count = 3)
name elements
item1 elementA
item2 elementA
item2 elementB
最终,将它们分组为:
element count
ElementA 2
ElementB 1
这是我的代码开始计算字典项目。
public struct MyStruct
{
public string name;
public List<string> elements;
}
private void button1_Click(object sender, EventArgs e)
{
MyStruct myStruct = new MyStruct();
Dictionary<String, MyStruct> dict = new Dictionary<string, MyStruct>();
// Populate 2 items
myStruct.name = "item1";
myStruct.elements = new List<string>();
myStruct.elements.Add("elementA");
dict.Add(myStruct.name, myStruct);
myStruct.name = "item2";
myStruct.elements = new List<string>();
myStruct.elements.Add("elementA");
myStruct.elements.Add("elementB");
dict.Add(myStruct.name, myStruct);
var q = from t in dict
select t;
MessageBox.Show(q.Count().ToString()); // Returns 2
}
编辑:我真的不需要输出字典。我用它来存储我的数据,因为它运行良好并防止重复(我确实有我存储的唯一item.name作为键)。但是,出于过滤/分组的目的,我猜它可能是没有问题的列表或数组。我可以随时做.ToDictionary其中key = item.Name。
答案 0 :(得分:3)
var q = from t in dict
from v in t.Value.elements
select new { name = t.Key, element = v };
这里的方法是Enumerable.SelectMany。使用扩展方法语法:
var q = dict.SelectMany(t => t.Value.elements.Select(v => new { name = t.Key, element = v }));
修改强>
请注意,您也可以使用上面的t.Value.name
代替t.Key
,因为这些值相等。
那么,这里发生了什么?
查询 - 理解语法可能最容易理解;你可以写一个等效的迭代器块来看看发生了什么。但是,我们不能简单地使用匿名类型,因此我们将声明要返回的类型:
class NameElement
{
public string name { get; set; }
public string element { get; set; }
}
IEnumerable<NameElement> GetResults(Dictionary<string, MyStruct> dict)
{
foreach (KeyValuePair<string, MyStruct> t in dict)
foreach (string v in t.Value.elements)
yield return new NameElement { name = t.Key, element = v };
}
扩展方法语法怎么样(或者真正在这里发生了什么?)
(这部分受到Eric Lippert在https://stackoverflow.com/a/2704795/385844的帖子的启发;我有一个更为复杂的解释,然后我读到了,并想出了这个:)
假设我们要避免声明NameElement类型。我们可以通过传入一个函数来使用匿名类型。我们改变了这个电话:
var q = GetResults(dict);
到此:
var q = GetResults(dict, (string1, string2) => new { name = string1, element = string2 });
lambda表达式(string1, string2) => new { name = string1, element = string2 }
表示一个函数,它接受2个字符串 - 由参数列表(string1, string2)
定义 - 并返回用这些字符串初始化的匿名类型的实例 - 由表达式定义new { name = string1, element = string2 }
。
相应的实现是:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<string, string, T> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (string e in pair.Value.elements)
yield return resultSelector.Invoke(t.Key, v);
}
类型推断允许我们调用此函数,而无需按名称指定T
。这很方便,因为(据我们所知,作为C#程序员),我们使用的类型没有名称:它是匿名的。
请注意,变量t
现在为pair
,以避免与类型参数T
混淆,而v
现在为e
,用于“元素”。我们还将第一个参数的类型更改为其基类型IEnumerable<KeyValuePair<string, MyStruct>>
之一。它更啰嗦,但它使方法更有用,最终会有所帮助。由于类型不再是字典类型,我们还将参数名称从dict
更改为pairs
。
我们可以进一步概括这一点。第二个foreach
具有将键值对投影到类型T的序列的效果。整个效果可以封装在单个函数中;委托类型为Func<KeyValuePair<string, MyStruct>, T>
。第一步是重构方法,因此我们有一个语句将元素pair
转换为序列,使用Select
方法调用resultSelector
委托:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<string, string, T> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (T result in pair.Value.elements.Select(e => resultSelector.Invoke(pair.Key, e))
yield return result;
}
现在我们可以轻松更改签名:
IEnumerable<T> GetResults<T>(
IEnumerable<KeyValuePair<string, MyStruct>> pairs,
Func<KeyValuePair<string, MyStruct>, IEnumerable<T>> resultSelector)
{
foreach (KeyValuePair<string, MyStruct> pair in pairs)
foreach (T result in resultSelector.Invoke(pair))
yield return result;
}
呼叫网站现在看起来像这样;注意lambda表达式现在如何包含我们在更改其签名时从方法体中删除的逻辑:
var q = GetResults(dict, pair => pair.Value.elements.Select(e => new { name = pair.Key, element = e }));
为了使该方法更有用(并且其实现更简洁),让我们用类型参数KeyValuePair<string, MyStruct>
替换类型TSource
。我们会同时更改其他名称:
T -> TResult
pairs -> sourceSequence
pair -> sourceElement
而且,只是为了踢,我们将它作为一种扩展方法:
static IEnumerable<TResult> GetResults<TSource, TResult>(
this IEnumerable<TSource> sourceSequence,
Func<TSource, IEnumerable<TResult>> resultSelector)
{
foreach (TSource sourceElement in sourceSequence)
foreach (T result in resultSelector.Invoke(pair))
yield return result;
}
你有它:SelectMany!好吧,函数仍然有错误的名称,实际的实现包括验证源序列和选择器函数是非空的,但这是核心逻辑。
从MSDN:SelectMany
“将序列的每个元素投影到IEnumerable,并将生成的序列展平为一个序列。”
答案 1 :(得分:1)
这会将数组展平为单个数组,然后计算唯一值。
var groups = dictionary
.SelectMany(o => o.Value)
.GroupBy(o => o);
foreach (var g in groups)
Console.WriteLine(g.Key + ": " + g.Count());
使用以下字典:
Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>();
dictionary.Add("One", new string[] { "A" });
dictionary.Add("Two", new string[] {"A", "B" });
dictionary.Add("Three", new string[] { "A", "B" });
我得到了这个输出:
A: 3
B: 2
答案 2 :(得分:1)
/* Will return
name elements
item1 elementA
item2 elementA
item2 elementB
*/
var res = dict
.Values
.SelectMany(m => m.elements.Select(e => new {m.name, element= e}))
.ToArray();
/* Will return
element count
ElementA 2
ElementB 1
*/
var res2 = res
.GroupBy(r => r.element)
.Select(g => new {element = g.Key, count = g.Count()})
.ToArray();
答案 3 :(得分:0)
如果你使用另一个词典怎么办。
Dictionary<String, string> dict2 = new Dictionary<string, string>();
dict.foreach(item => item.elements.foreach(elem => dict2.Add(elem,item.name)));
然后您可以查询新词典以获取计数,它将元素作为键,因此对于每个元素,它具有包含它的项目。因此,您可以找到有多少项具有您想要的元素
答案 4 :(得分:0)
您可能希望从更简单的结构集合开始,但是从您的字典开始:
var q = from t in dict.Values
from el in t.Elements
group el by el into eNameGroup
select new { Name = eNameGroup.Key, Count = eNameGroup.Count() };
返回:
姓名数量
ElementA 2
ElementB 1
答案 5 :(得分:0)
如果你所追求的是分组/转动,可以通过利用LINQ的分组和完全避免字典来更加声明地完成:
void Main()
{
var items = new MyStruct[] {
new MyStruct { name = "item1", elements = new List<string> { "elementA" }},
new MyStruct { name = "item2", elements = new List<string> { "elementA", "elementB" }}};
var groupedByElement =
from item in items
from element in item.elements
group item by element;
groupedByElement.Dump(); // items grouped by element value, (pivoted)
var elementsWithCount =
from gj in groupedByElement
select new { element = gj.Key, count = gj.Count() };
elementsWithCount.Dump();
// element, count
// elementA, 2
// elementB, 1
}
public struct MyStruct
{
public string name;
public List<string> elements;
}