C# Groupby Linq and foreach - c#

I need a more efficient way of producing multiple files from my data group.
Im using a List<MyObject> type and my object has some public properties in which I need to group the data by.
I have heard of Linq and it sounds like something I could use. However Im not sure how to go about it.
I need to produce a text file for each STATE, so grouping all the MyObjects (people) by state, then running a foreach look on them to build the TEXT file.
void Main()
{
List<MyObject> lst = new List<MyObject>();
lst.Add(new MyObject{ name = "bill", state = "nsw", url = "microsoft.com"});
lst.Add(new MyObject{ name = "ted", state = "vic", url = "apple.com"});
lst.Add(new MyObject{ name = "jesse", state = "nsw", url = "google.com"});
lst.Add(new MyObject{ name = "james", state = "qld", url = "toshiba.com"});
string builder = "";
foreach (MyObject item in myObjects) {
builder += item.name + "\r\n";
builder += item.url + "\r\n" + "\r\n\r\n";
}
and out to the `StreamWriter` will be the filenames by state.
In total for the above data I need 3 files;
-nsw.txt
-vic.txt
-qld.txt

Something like this, perhaps?
var groups = lst.GroupBy(x => x.state);
foreach (var group in groups)
{
using (var f = new StreamWriter(group.Key + ".txt"))
{
foreach (var item in group)
{
f.WriteLine(item.name);
f.WriteLine(item.url);
}
}
}

You def. could use LINQ here.
lst.GroupBy(r=> r.state).ToList().ForEach(r=> {
//state= r.Key
//
foreach (var v in r)
{
}
});
The thing about linq. If you want to know how to do something in it. Think "how would I do this in SQL". The keywords are for the most part the same.

You can actually produce entire content with LINQ:
var entryFormat = "{1}{0}{2}{0}{0}{0}";
var groupsToPrint = lst
.GroupBy(p => p.state)
.Select(g => new
{
State = g.Key,
// produce file content on-the-fly from group entries
Content = string.Join("", g.Select(v => string.Format(entryFormat,
Environment.NewLine, v.name, v.url)))
});
var fileNameFormat = "{0}.txt";
foreach (var entry in groupsToPrint)
{
var fileName = string.Format(fileNameFormat, entry.State);
File.WriteAllText(fileName, entry.Content);
}

Something like...
string builderNsw = "";
foreach (MyObject item in lst.Where(o=>o.state == 'nsw')) {
builderNsw += item.name + "\r\n";
builderNsw += item.url + "\r\n" + "\r\n\r\n";
}
...but there are probably many ways to achieve this.

Same as Above - Iterating through groups by group, can get group name also
int itemCounter = 1;
IEnumerable<DataRow> sequence = Datatables.AsEnumerable();
var GroupedData = from d in sequence group d by d["panelName"]; // GroupedData is now of type IEnumerable<IGrouping<int, Document>>
foreach (var GroupList in GroupedData) // GroupList = "document group", of type IGrouping<int, Document>
{
bool chk = false;
foreach (var Item in GroupList)
{
if (chk == false) // means when header is not inserted
{
var groupName = "Panel Name : " + Item["panelName"].ToString();
chk = true;
}
var count = itemCounter.ToString();
var itemRef = Item["reference"].ToString();
itemCounter++;
}
}

Related

Delete duplicate elements from list by Identifier string (C#)

I have method to add elements to list
Here is code
public static List<InputDevice> GetAudioInputDevices()
{
var inputs = new List<InputDevice>();
var enumerator = new MMDeviceEnumerator();
var devicesAudio = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All);
foreach (var device in devicesAudio)
{
inputs.Add(new InputDevice()
{
Name = device.FriendlyName,
Status = device.State.ToString(),
DeviceId = device.ID,
Identifier = device.FriendlyName.Replace(" ", "").ToUpper()
});
}
return inputs;
}
But sometimes I can have duplicates in Identifier
How I can return list without duplicates on return?
There's a few ways to accomplish this, you could just skip the Adding of the item if it's already in the list:
foreach (var device in devicesAudio)
{
string identifier = device.FriendlyName.Replace(" ", "").ToUpper();
if (inputs.Any(input => input.Identifier == identifier))
continue;
inputs.Add(new InputDevice()
{
Name = device.FriendlyName,
Status = device.State.ToString(),
DeviceId = device.ID,
Identifier = identifier
});
}
Or you could group the list by the identifier after the foreach, something like this:
inputs = inputs.GroupBy(i => i.Identifier)
.Select(i => new InputDevice()
{
Identifier = i.Key,
Status = i.First().Status,
DeviceId = i.First().DeviceId,
Name = i.First().Name
}).ToList();
It really depends on what you need to do with the duplicated ones.
Hope it helps!
To make it faster you can use HashSet (complexity of Contains for HashSet is o(1)) and ask on each loop whether there already is a specific identifier in inputs List.
public static List<InputDevice> GetAudioInputDevices()
{
var inputs = new List<InputDevice>();
var enumerator = new MMDeviceEnumerator();
var devicesAudio = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All);
var usedIdentifiers = new HashSet<string>();
foreach (var device in devicesAudio)
{
var identifier = device.FriendlyName.Replace(" ", "").ToUpper();
if (usedIdentifiers.Contains(identifier))
continue;
inputs.Add(new InputDevice()
{
Name = device.FriendlyName,
Status = device.State.ToString(),
DeviceId = device.ID,
Identifier = identifier
});
usedIdentifiers.Add(identifier);
}
return inputs;
}
The best way, I thing is this
public static List<InputDevice> GetAudioInputDevices()
{
var inputs = new List<InputDevice>();
var enumerator = new MMDeviceEnumerator();
var devicesAudio = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All);
inputs = devicesAudio.GroupBy(d => d.FriendlyName.Replace(" ", "").ToUpper()).Select(g => g.First())
.Select(d => new InputDevice()
{
Name = d.FriendlyName,
Status = d.State.ToString(),
DeviceId = d.ID,
Identifier = d.FriendlyName.Replace(" ", "").ToUpper()
}).ToList();
return inputs;
}
Check in website information about HashSet.

How can I edit the Keywords field in SharePoint using c#?

I can edit other fields like Title and even my own created fields, but I cant figure out how to add in Keywords to a document I upload. This is what I have so far. The error I get says that the Keywords column does not exist. The Keywords fields type is Managed Metadata. Here is a image of where I want to add keywords into Here is a image of where I want to add keywords into
Update: Here is the updated code of what worked
static void AddMetaData(ClientContext ctx, string fName, string kWords )
{
List list = ctx.Web.Lists.GetByTitle("Documents");
ListItemCollection items = list.GetItems(CamlQuery.CreateAllItemsQuery());
ctx.Load(items); // loading all the fields
ctx.ExecuteQuery();
TaxonomySession session = TaxonomySession.GetTaxonomySession(ctx);
var store = session.GetDefaultKeywordsTermStore();
var terms = store.KeywordsTermSet.GetAllTerms();
ctx.Load(store, i => i.DefaultLanguage);
ctx.ExecuteQuery();
var collection = new System.Collections.ObjectModel.Collection<Term>();
var keywords = kWords.Split(';');
foreach (var key in keywords)
{
var filtered = ctx.LoadQuery(terms.Where(t => t.Name == key));
ctx.ExecuteQuery();
var term = filtered.SingleOrDefault();
if (term != null)
collection.Add(term);
}
foreach (var item in items)
{
var taxonomyField = ctx.CastTo<TaxonomyField>(list.Fields.GetByInternalNameOrTitle("Keywords"));
ctx.Load(taxonomyField);
ctx.ExecuteQuery();
taxonomyField.SetFieldValueByCollection(item, collection, store.DefaultLanguage);
item.Update();
ctx.ExecuteQuery();
}
}
It's a taxonomy field so first you have to get your keywords from the managed metadata store:
TaxonomySession session = TaxonomySession.GetTaxonomySession(context);
var store = session.GetDefaultKeywordsTermStore();
var terms = store.KeywordsTermSet.GetAllTerms();
context.Load(store, i => i.DefaultLanguage);
context.ExecuteQuery();
var collection = new System.Collections.ObjectModel.Collection<Term>();
var keywords = new string[] { "Key1", "Key2" };
foreach (var key in keywords)
{
var filtered = context.LoadQuery(terms.Where(t => t.Name == key));
context.ExecuteQuery();
var term = filtered.SingleOrDefault();
if (term != null)
collection.Add(term);
}
Then set field using method contained in TaxonomyField class:
var taxonomyField = context.CastTo<TaxonomyField>(list.Fields.GetByInternalNameOrTitle("Keywords"));
context.Load(taxonomyField);
context.ExecuteQuery();
taxonomyField.SetFieldValueByCollection(item, collection, store.DefaultLanguage);
item.Update();
context.ExecuteQuery();

How to get ienumerated element from sister collection

I often use two related collections and want to access the corresponding elements in a foreach loop.
But, there is no ".Index" property, is there a direct way of doing this, WITHOUT incrementing a counter?
public void PrepareData()
{
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
foreach(var value in values.Skip(1))
{
string message = "Data: "+headers[value.Index]+' '+value
}
}
You could use Enumerable.Zip and an anonymous type:
string[] names={"Rod", "Jane", "Freddy"}
int[] ages={28,32,26;};
var pairs=names.Zip(ages, (name,age) => new{Name=name, Age=age});
foreach(var pair in pairs)
{
string name=pair.Name;
string age=pair.Age;
}
Yet another variant, use Select overloading with index like
public void PrepareData()
{
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',').Select((el,index)=>new {value=el, index=index});
foreach(var value in values.Skip(1))
{
string message = "Data: "+headers[value.index]+' '+value.value
}
}
depends on data in headers and values, better variant can be
public void PrepareData()
{
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
foreach(var item in values.Skip(1).Select((el,index)=>new {value=el, index=index}))
{
string message = "Data: "+headers[item.index]+' '+item.value
}
}
I'm thinking you want a .Zip.
var pairs = headers.Zip(values,
(header, value) => new Tuple<string, string>(header, value));
Do it with IndexOf
var lines = ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
foreach(var value in values.Skip(1))
{
string message = "Data: " + headers[Array.IndexOf(values, value)] + ' ' + value;
}
or maybe with iterators :
var lines = System.IO.File.ReadAllLines(#"\\tsclient\T\Bbtra\wapData.txt");
var headers = lines[0].Split(',');
var values = lines.Last().Split(',');
var e1 = headers.GetEnumerator();
var e2 = values.GetEnumerator();
while(e1.MoveNext() && e2.MoveNext())
{
string message = "Data: " + e1.Current.ToString() + ' ' + e2.Current.ToString();
}

How can I access the result of dynamic linq with dynamic key/value pairs of IEnumerable?

I have a Dynamic Linq query, through which am trying to access grouped data, grouped on two columns, and 1 columns on which aggregation function is used.
var gFieldList = new List<string>() { "Supplier", "Country" };
var sFieldList = new List<string>() { "Sales"};
var gField = string.Join(", ", gFieldList.Select(x => "it[\"" + x + "\"] as " + x));
var sField = string.Join(", ", sFieldList.Select(y => "Sum(Convert.ToDouble(it[\""+y+"\"])) as "+y));
var dQueryResult = dataTable
.AsEnumerable()
.AsQueryable()
.GroupBy("new("+gField+")", "it")
.Select("new("+sField+",it.Key as Key, it as Data)");
To access the data from the dQueryResult am using the following code.
var groupedData = (from dynamic dat in dQueryResult select dat).ToList();
foreach (var group in groupedData)
{
var key = group.Key;
var sum = group.Sales;
MessageBox.Show("Supplier : " + key.Supplier + "Country : " + key.Country + " SALES : "+Sale.ToString());
}
The problem is
var gFieldList = new List<string>() { "Supplier", "Country" };
var sFieldList = new List<string>() { "Sales"};
keeps on changing. They both need to be dynamic, which will not allow me to access the values dynamically from the above mentioned code since for now it's been coded as key.Supplier, key.Country and group.Sales .
How can I make the iteration of the result of the dynamic query as dynamic?
I tried using
var groupedData = (from dynamic dat in dQueryResult select dat).ToList();
foreach (var group in groupedData)
{
var key = group.Key;
var sum = group.Sales;
foreach (var v in gFieldList)
{
MessageBox.Show(key.v);
}
}
But it's throwing an exception stating 'DynamicClass1' does not contain a definition for 'v'
you can use reflection like this
foreach (var v in gFieldList)
{
MessageBox.Show(key.GetType().GetProperty(v).GetValue(key,null));
...

How to split a string in C#?

I am trying to parse the following string and get the result.
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6"
I am trying to get the following result after the split.
string SiteA = "Pages:1,Documents:6"
string SiteB = "Pages:4"
Here is my code but it doesn't seem to be working. How can I get all related "SiteA" and "SiteB"?
List<string> listItem = new List<string>();
string[] keyPairs = test.Split(',');
string[] item;
foreach (string keyPair in keyPairs)
{
item = keyPair.Split(':');
listItem.Add(string.Format("{0}:{1}", item[0].Trim(), item[1].Trim()));
}
I would use a Lookup for this:
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6";
var listItemsBySite = test.Split(',')
.Select(x => x.Split(':'))
.ToLookup(x => x[0],
x => string.Format("{0}:{1}",
x[1].Trim(),
x[2].Trim()));
You can then use it like this:
foreach (string item in listItemsBySite["SiteA"])
{
Console.WriteLine(item);
}
Here's my solution... pretty elegant in LINQ, you can use anonymous objects, Tuples, KeyValuePair, or your own custom class. I'm just using an anonymous type.
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6";
var results = test
.Split(',')
.Select(item => item.Split(':'))
.ToLookup(s => s[0], s => new { Key = s[1], Value = s[2] });
// This code just for display purposes
foreach (var site in results)
{
Console.WriteLine("Site: " + site.Key);
foreach (var value in site)
{
Console.WriteLine("\tKey: " + value.Key + " Value: " + value.Value);
}
}
Here is my code:
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6";
string[] data = test.Split(',');
Dictionary<string, string> dic = new Dictionary<string, string>();
for(int i = 0; i < data.Length; i++) {
int index = data[i].IndexOf(':');
string key = data[i].Substring(0, index);
string value = data[i].Substring(index + 1);
if(!dic.ContainsKey(key))
dic.Add(key, value);
else
dic[key] = string.Format("{0}, {1}", new object[] { dic[key], value });
}
Here is how I would do it:
SortedList<string, StringBuilder> listOfLists = new SortedList<string, StringBuilder>();
string[] keyPairs = test.Split(',');
foreach (string keyPair in keyPairs)
{
string[] item = keyPair.Split(':');
if (item.Length >= 3)
{
string nextValue = string.Format("{0}:{1}", item[1].Trim(), item[2].Trim());
if (listOfLists.ContainsKey(item[0]))
listOfLists[item[0]].Append(nextValue);
else
{
StringBuilder sb = new StringBuilder();
sb.Append(nextValue);
listOfLists.Add(item[0], sb);
}
}
}
foreach (KeyValuePair<string, StringBuilder> nextCollated in listOfLists)
System.Console.WriteLine(nextCollated.Key + ":" + nextCollated.Value.ToString());
This is what I would do (tested).
(However, does assume that all items will be correctly formatted).
And of course, it's not really optimized.
static void Main(string[] args)
{
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6";
Dictionary<String, List<String>> strings = new Dictionary<string, List<string>>();
String[] items = test.Split(',');
foreach (String item in items)
{
List<String> itemParts = item.Split(':').ToList();
String firstPart = itemParts[0];
itemParts.RemoveAt(0);
String secondPart = String.Join(":", itemParts);
if (!strings.ContainsKey(firstPart))
strings[firstPart] = new List<string>();
strings[firstPart].Add(secondPart);
}
// This is how you would consume it
foreach (String key in strings.Keys)
{
List<String> keyItems = strings[key];
Console.Write(key + ": ");
foreach (String item in keyItems)
Console.Write(item + " ");
Console.WriteLine();
}
}
Here's a solution using LINQ:
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6";
var dict = test
.Split(',')
.GroupBy(s => s.Split(':')[0])
.ToDictionary(g => g.Key,
g => string.Join(",",
g.Select(i => string.Join(":", i.Split(':').Skip(1)))
.ToArray()));
using System;
using System.Linq;
using System.Text.RegularExpressions;
class MyClass
{
static void Main(string[] args)
{
string test = "SiteA:Pages:1,SiteB:Pages:4,SiteA:Documents:6";
var sites = test.Split(',')
.Select(p => p.Split(':'))
.Select(s => new { Site = s[0], Key = s[1], Value = s[2] })
.GroupBy(s => s.Site)
.ToDictionary(g => g.Key, g => g.ToDictionary(e => e.Key, e => e.Value));
foreach (var site in sites)
foreach (var key in site.Value.Keys)
Console.WriteLine("Site {0}, Key {1}, Value {2}", site.Key, key, site.Value[key]);
// in your preferred format:
var SiteA = string.Join(",", sites["SiteA"].Select(p => string.Format("{0}:{1}", p.Key, p.Value)));
var SiteB = string.Join(",", sites["SiteB"].Select(p => string.Format("{0}:{1}", p.Key, p.Value)));
Console.WriteLine(SiteA);
Console.WriteLine(SiteB);
}
}

Categories