LINQ GroupBy over - c#

This might have been dealt with, so I apologize in advance for that.
Anyway, this is my somewhat contrived example, i hope it gets my question across:
Say we have these classes
class WordExamples
{
public string word;
public List<Sentence> sentencesWithWord;
//constructor
public WordExamples(string word) {
this.word = word;
}
}
class Sentence
{
public List<string> words;
}
Then we set up two lists:
List<Sentence> sentences = GetSomeSentences();
List<WordExamples> wordExamples =
GetSomeWords().Select(w=>new WordExamples(w));
As you can see the list of WordExamples contains word examples that are incomplete in that they don't have the sentencesWithWord List instantiated.
So what I need is some neat Linq that will set this up. I.e. something like:
foreach wordExample get the subset of sentences that contain the word and assign it to sentencesWithWord. (Withouth nested for loops that is)
edit:
Adding public access modifier

It's not really clear what you're after, but I suspect you want:
foreach (var example in wordExamples)
{
Console.WriteLine("Word {0}", example.Key);
foreach (var sentence in example)
{
// I assume you've really got the full sentence here...
Console.WriteLine(" {0}", string.Join(" ", sentence.Words));
}
}
EDIT: If you really need the WordExamples class, you could have:
public class WordExamples
{
public string Word { get; private set; }
public List<Sentence> SentencesWithWord { get; private set; }
public WordExamples(string word, List<Sentences> sentences) {
Word = word;
// TODO: Consider cloning instead
SentencesWithWord = sentences;
}
}
This is basically just like an element of a Lookup, mind you...
Anyway, with that in place you could use:
var wordExamples = from sentence in sentences
from word in sentence.Words
group sentence by word into g
select new WordExample(g.Key, g.ToList());

As LINQ is a query language, not an assignment language, you should use a loop:
List<WordExamples> wordExamples = GetSomeWords().Select(w=>new WordExamples(w))
.ToList();
foreach(var wordExample in wordExamples)
{
wordExample.sentencesWithWord
= sentences.Where(x => x.words.Contains(wordExample.word)).ToList();
}

IEnumerable<WordExamples> wordExamples = GetSomeWords().Select(w=>
{
var examples = new WordExamples(w);
examples.sentencesWithWord = sentences.Where(s => s.words.Any(sw => sw == w)).ToList();
return examples;
}
);
Don't forget to set correct access modifiers.

It looks like you're re-inventing an ILookup.
ILookup<string, Sentence> examples = GetSentences()
.SelectMany(sentence => sentence.words, (sentence, word) => new {sentence, word} )
.ToLookup(x => x.word, x => x.sentence);

Related

How to format List items into different row of table for every new value of users

I have four lists for four columns of a table. I want to print data as shown in the screenshot of the image below.
However, whenever I run the code below details of first user come according to the screenshot but after I input another user's data and try to print it, it comes in same line like this:
Jack Li Tom smith 10 5
This is what I have tried:
public void SummaryTable()
{
Console.WriteLine("\tSummary of Membership Fee");
Console.WriteLine("{0} {1,10} {2,10} {3,10}","Name", "Weeks","Discount","Charges");
//Console.Write("Weeks\t");
//Console.Write("Discount\t");
// Console.WriteLine("Charge");
//Console.WriteLine();
foreach (string namesl in nameList)
{
Console.Write("{0}",namesl);
//Console.Write("\t");
}
foreach (int weekl in weekList)
{
Console.Write("{0,10}",weekl);
//Console.Write("\t");
}
foreach (string discountl in discountList)
{
Console.Write("{0,10}", discountl);
// Console.Write("\t");
}
foreach (double chargel in chargeList)
{
Console.WriteLine("{0,10}",chargel);
}
You could just print them like this:
Console.WriteLine("\tSummary of Membership Fee");
Console.WriteLine(new String('=', 45));
Console.WriteLine("{0,-20} {1,-5} {2,-10} {3,-10}", "Name", "Weeks", "Discount", "Charges");
Console.WriteLine(new String('-', 45));
for (int count = 0; count < nameList.Count; count++)
{
Console.WriteLine("{0,-20}{1,5}{2,10}{3,10}", nameList[count], weekList[count], discountList[count], chargeList[count]);
Console.WriteLine(new String('-', 45));
}
These are parallel lists
I believe what you have here are called parallel lists. The first item in the first list goes with the first item in the second, third, and fourth lists; the second item in the first list goes with the second item in the other lists; etc. Is that correct?
What is needed here is to merge the lists into one, which you can do several ways. Here is a simple and intuitive way to do it. I'll show two:
Crayon method
This method is very simple and intuitive, although not all that fancy.
First, define a class that can contain the data in a single object:
public class Member
{
public string Name { get; set; }
public int Week { get; set; }
public string Discount { get; set; }
public double Charge { get; set; }
}
Then write a bit of code to merge your four lists into a single list of Member objects:
public List<Member> GetMergedList()
{
var results = new List<Member>();
for (int i=0; i<nameList.Count; i++)
{
results.Add(
new Member
{
Name = nameList[i],
Week = weekList[i],
Discount = discountList[i],
Charge = chargeList[i]
}
);
}
return results;
}
Now you can just iterate over (or DataBind against) the single list:
public void Run()
{
var members = GetMergedList();
foreach (var m in members)
{
Console.WriteLine("{0}\t{1}\r{2}\t{3:0.00}", m.Name, m.Week, m.Discount, m.Charge);
}
}
Output:
John 1 Discount1 1.23
Paul 2 Discount2 4.56
Dave 3 Discount3 7.89
Using LINQ
You could also merge your lists using LINQ (e.g. using the Zip method) to produce an anonymous type that contains everything:
var membersViaLinq = nameList
.Zip(weekList, (n, w) => new { Name = n, Week = w })
.Zip(discountList, (x, d) => new { Name = x.Name, Week = x.Week, Discount = d})
.Zip(chargeList, (x, c) => new { Name = x.Name, Week = x.Week, Discount = x.Discount, Charge = c });
foreach (var x in membersViaLinq)
{
Console.WriteLine("{0}\t{1}\r{2}\t{3:0.00}", x.Name, x.Week, x.Discount, x.Charge);
}
This approach is a little more confusing to read, but the benefit is you don't have to define a class just to hold the data.
The best solution
The best solution is to retrieve the data in a single list to begin with, if possible, and completely eliminate the parallel lists entirely. To suggest a way to do that, I'd have to see your data retrieval code.
Full example code on DotNetFiddle
This is easily solvable using OOP.
Create a following class:
public class Person
{
public string firstName;
public string lastName;
public int weeks;
public bool discount;
public int charge;
}
Now instead of creating multiple lists you will create only one list:
List<Person> People = new List<Person>();
You can populate it with Person objects.
Example:
People.Add(person1);
People.Add(person2);
People.Add(person3);
People.Add(person4);
Here I'm assuming you are going pass in a list of persons as an argument to this function:
public void SummaryTable(List<Person> list)
{
foreach (Person p in list)
{
Console.Write("{0}", p.firstName);
Console.Write("{0}", p.lastName);
Console.Write("{0}", p.weeks);
Console.Write("{0}", p.discount);
Console.Write("{0}", p.charge);
}
}
Now you simply call the function:
SummaryTable(People);
Also make sure you import the required namespaces using System.Collections;.

Checking foreach loop list results C#

I have this bit of code in a class:
public class TicketSummary
{
//get all the development tickets
public List<IncidentSummary> AllDevelopmentTickets { get; set; }
public List<string> TicketNames()
{
List<string> v = new List<string>();
foreach (var developmentTicket in AllDevelopmentTickets)
{
var ticketIds = developmentTicket.id.ToString(CultureInfo.InvariantCulture);
v.Add(ticketIds);
}
return v;
}
}
}
And I am trying to see if my API connection (plus all the code) did it's job and pulls back the tickets and their info, more specifically the ids.
In my main program I have no clue how to check if it did the job. I tried something but it isn't quite right and doesn't return anything ( I know I need a Console.WriteLine)
static void Main(string[] args)
{
Console.ReadLine();
var tickets = new TicketSummary();
tickets.TicketNames();
while ( tickets != null )
{
Console.WriteLine(tickets);
}
}
Any suggestions, please?
Thank you!
You've dropped the returned result: tickets.TicketNames(); returns List<String> that you have to assign and then itterate:
var tickets = new TicketSummary();
var names = tickets.TicketNames(); // <- names, List<String> according to the code
// printing out all the names
foreach(var name in names)
Console.WriteLine(name);
Do you mean you just want to print all the tickets out?
foreach (var ticket in tickets.TicketNames())
{
Console.WriteLine(ticket);
}
You have several problems in your code, that should keep it from even compiling, but aside from that, It seem's what you're really after is rather transforming the data in AllDevelopmentTickets, rather than moving it somewhere. So you could probably do it with a Select call (from LINQ). So, in your main method:
var tickets = new TicketSummary();
// add some tickets to tickets.AllDevelopmentTickets here...
var ticketNames = tickets.AllDevelopmentTickets.Select(ticket => ticket.id.ToString();
// Yes, you should probably use an UI culture in the ToString call.
// I'm just trying to limit my line width =)
Now, ticketNames should be an IEnumerable<string> holding all the ticket ids. To, for example, print them out, you can iterate over them and write to console output:
foreach (var name in ticketNames) {
Console.WriteLine(name);
}
You need to assign / use the return value of the TicketNames() method. This seems a lot of work just to return the string version of the TicketId. This can be reduced
public List<string> TicketNames()
{
return AllDevelopmentTickets
.Select(t => t.id.ToString(CultureInfo.InvariantCulture))
.ToList();
}
var ticketSummary = new TicketSummary();
var ticketNames = ticketSummary.TicketNames();
foreach(var ticketName in ticketNames)
{
Console.WriteLine(ticketName);
}
or Even just:
foreach(var ticketName in AllDevelopmentTickets
.Select(t => t.id.ToString(CultureInfo.InvariantCulture)))
{
Console.WriteLine(ticketName);
}
You're ignoring the returned value.
static void Main(string[] args)
{
Console.ReadLine();
var tickets = new TicketSummary();
var res = tickets.TicketNames();
while ( for r in res )
{
Console.WriteLine(r);
}
}

How do I order this list of site URLs in C#?

I have a list of site URLs,
/node1
/node1/sub-node1
/node2
/node2/sub-node1
The list is given to me in a random order, I need to order it so the the top level is first, followed by sub-levels and so on (because I cannot create /node2/sub-node1 without /node2 existing). Is there a clean way to do this?
Right now I'm just making a recursive call, saying if I can't create sub-node1 because node2 exists, create node2. I'd like to have the order of the list determine the creation and get rid of my recursive call.
My first thought was ordering by length of the string... but then I thought of a list like this, that might include something like aliases for short names:
/longsitename/
/a
/a/b/c/
/a
/a/b/
/otherlongsitename/
... and I thought a better option was to order by the number of level-separator characters first:
IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
return urls.OrderBy(s => s.Count(c => c == '/')).ThenBy(s => s);
}
Then I thought about it some more and I saw this line in your question:
I cannot create /node2/sub-node1 without /node2 existing
Aha! The order of sections or within a section does not really matter, as long as children are always listed after parents. With that in mind, my original thought was okay and ordering by length of the string alone should be just fine:
IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
return urls.OrderBy(s => s.Length);
}
Which lead me at last to wondering why I cared about the length at all? If I just sort the strings, regardless of length, strings with the same beginning will always sort the shorter string first. Thus, at last:
IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
return urls.OrderBy(s => s);
}
I'll leave the first sample up because it may be useful if, at some point in the future, you need a more lexical or logical sort order.
Is there a clean way to do this?
Just sorting the list of URI's using a standard string sort should get you what you need. In general, "a" will order before "aa" in a string sort, so "/node1" should end up before "/node1/sub-node".
For example:
List<string> test = new List<string> { "/node1/sub-node1", "/node2/sub-node1", "/node1", "/node2" };
foreach(var uri in test.OrderBy(s => s))
Console.WriteLine(uri);
This will print:
/node1
/node1/sub-node1
/node2
/node2/sub-node1
Perhaps this works for you:
var nodes = new[] { "/node1", "/node1/sub-node1", "/node2", "/node2/sub-node1" };
var orderedNodes = nodes
.Select(n => new { Levels = Path.GetFullPath(n).Split('\\').Length, Node = n })
.OrderBy(p => p.Levels).ThenBy(p => p.Node);
Result:
foreach(var nodeInfo in orderedNodes)
{
Console.WriteLine("Path:{0} Depth:{1}", nodeInfo.Node, nodeInfo.Levels);
}
Path:/node1 Depth:2
Path:/node2 Depth:2
Path:/node1/sub-node1 Depth:3
Path:/node2/sub-node1 Depth:3
var values = new string[]{"/node1", "/node1/sub-node1" ,"/node2", "/node2/sub-node1"};
foreach(var val in values.OrderBy(e => e))
{
Console.WriteLine(val);
}
The best is to use natural sorting since your strings are mixed between strings and numbers. Because if you use other sorting methods or techniques and you have like this example:
List<string> test = new List<string> { "/node1/sub-node1" ,"/node13","/node10","/node2/sub-node1", "/node1", "/node2" };
the output will be:
/node1
/node1/sub-node1
/node10
/node13
/node2
/node2/sub-node1
which is not sorted.
You can look at this Implementation
If you mean you need all the first level nodes before all the second level nodes, sort by the number of slashes /:
string[] array = {"/node1","/node1/sub-node1", "/node2", "/node2/sub-node1"};
array = array.OrderBy(s => s.Count(c => c == '/')).ToArray();
foreach(string s in array)
System.Console.WriteLine(s);
Result:
/node1
/node2
/node1/sub-node1
/node2/sub-node1
If you just need parent nodes before child nodes, it doesn't get much simpler than
Array.Sort(array);
Result:
/node1
/node1/sub-node1
/node2
/node2/sub-node1
Recursion is actually exactly what you should use, since this is most easily represented by a tree structure.
public class PathNode {
public readonly string Name;
private readonly IDictionary<string, PathNode> _children;
public PathNode(string name) {
Name = name;
_children = new Dictionary<string, PathNode>(StringComparer.InvariantCultureIgnoreCase);
}
public PathNode AddChild(string name) {
PathNode child;
if (_children.TryGetValue(name, out child)) {
return child;
}
child = new PathNode(name);
_children.Add(name, child);
return child;
}
public void Traverse(Action<PathNode> action) {
action(this);
foreach (var pathNode in _children.OrderBy(kvp => kvp.Key)) {
pathNode.Value.Traverse(action);
}
}
}
Which you can then use like this:
var root = new PathNode(String.Empty);
var links = new[] { "/node1/sub-node1", "/node1", "/node2/sub-node-2", "/node2", "/node2/sub-node-1" };
foreach (var link in links) {
if (String.IsNullOrWhiteSpace(link)) {
continue;
}
var node = root;
var lastIndex = link.IndexOf("/", StringComparison.InvariantCultureIgnoreCase);
if (lastIndex < 0) {
node.AddChild(link);
continue;
}
while (lastIndex >= 0) {
lastIndex = link.IndexOf("/", lastIndex + 1, StringComparison.InvariantCultureIgnoreCase);
node = node.AddChild(lastIndex > 0
? link.Substring(0, lastIndex) // Still inside the link
: link // No more slashies
);
}
}
var orderedLinks = new List<string>();
root.Traverse(pn => orderedLinks.Add(pn.Name));
foreach (var orderedLink in orderedLinks.Where(l => !String.IsNullOrWhiteSpace(l))) {
Console.Out.WriteLine(orderedLink);
}
Which should print:
/node1
/node1/sub-node1
/node2
/node2/sub-node-1
/node2/sub-node-2

extracting the common prefixes from a list of strings

I have a list of strings, such as:
{ abc001, abc002, abc003, cdef001, cdef002, cdef004, ghi002, ghi001 }
I want to get all the common unique prefixes; for example, for the above list:
{ abc, cdef, ghi }
How do I do that?
var list = new List<String> {
"abc001", "abc002", "abc003", "cdef001",
"cdef002", "cdef004", "ghi002", "ghi001"
};
var prefixes = list.Select(x = >Regex.Match(x, #"^[^\d]+").Value).Distinct();
It may be a good idea to write a helper class to represent your data. For example:
public class PrefixedNumber
{
private static Regex parser = new Regex(#"^(\p{L}+)(\d+)$");
public PrefixedNumber(string source) // you may want a static Parse method.
{
Match parsed = parser.Match(source); // think about an error here when it doesn't match
Prefix = parsed.Groups[1].Value;
Index = parsed.Groups[2].Value;
}
public string Prefix { get; set; }
public string Index { get; set; }
}
You need to come up with a better name, of course, and better access modifiers.
Now the task is quite easy:
List<string> data = new List<string> { "abc001", "abc002", "abc003", "cdef001",
"cdef002", "cdef004", "ghi002", "ghi001" };
var groups = data.Select(str => new PrefixedNumber(str))
.GroupBy(prefixed => prefixed.Prefix);
The result is all data, parsed, and grouped by the prefix.
You can achieve that using Regular Expression to select the text part, and then use HashSet<string> to add that text part so no duplication added:
using System.Text.RegularExpressions;
//simulate your real list
List<string> myList = new List<string>(new string[] { "abc001", "abc002", "cdef001" });
string pattern = #"^(\D*)\d+$";
// \D* any non digit characters, and \d+ means followed by at least one digit,
// Note if you want also to capture string like "abc" alone without followed by numbers
// then the pattern will be "^(\D*)$"
Regex regex = new Regex(pattern);
HashSet<string> matchesStrings = new HashSet<string>();
foreach (string item in myList)
{
var match = regex.Match(item);
if (match.Groups.Count > 1)
{
matchesString.Add(match.Groups[1].Value);
}
}
result:
abc, cde
Assuming that your prefix is all alpha characters and terminited by the first non-alpha character, you could use the following LINQ expression
List<string> listOfStrings = new List<String>()
{ "abc001d", "abc002", "abc003", "cdef001", "cdef002", "cdef004", "ghi002", "ghi001" };
var prefixes = (from s in listOfStrings
select new string(s.TakeWhile(c => char.IsLetter(c)).ToArray())).Distinct();

C# foreach only get distinct values

I have the following code:
foreach (Logs log in _logsDutyStatusChange)
{
string driverid = log.did;
}
How would I only add distinct driver ID's to the driverid string?
Thanks
You should do:
foreach (string id in _logsDutyStatusChange.Select(x=>x.did).Distinct())
{
string driverid = id;
}
mhh ... maybe use IEnumerable< T >'s Distinct() function?
Your code isn't adding anything right now, it's just setting a string (declared in the scope of the loop) to each value. The end result would be only the last value, and it would be out of scope to the following code anyway. If you're trying to append them all to a string, separated by commas, e.g., try this:
string driverids = string.Join(",", _logsDutyStatusChange
.Select(item=>item.did)
.Distinct()
.ToArray());
This should work (without linq):
Hashset<string> alreadyEncountered = new Hashset<string>();
foreach (Logs log in _logsDutyStatusChange)
{
if(alreadyEncountered.Contains(log.did))
{
continue;
}
string driverid = log.did;
alreadyEncountered.Add(driverid);
}
First, create a comparer class and implement the IEqualityComparer<Log> interface:
public class LogComparer : IEqualityComparer<Log>
{
public bool Equals(Log one, Log two)
{
return one.did.Equals(two.did);
}
public int GetHashCode(Log log)
{
return log.did.GetHashCode();
}
}
And then use the overload of the Enumerable.Distinct() method that takes an instance of the equality comparer:
foreach(var log in _logsDutyStatusChange.Distinct(new LogComparer()))
{
// Work with each distinct log here
}
Instead of foreach'ing _logsDutyStatusChange, you could use LINQ to get a collection of distinct and loop through those, instead:
foreach (Logs log in _logsDutyStatusChange.Select(l => l.did).Distinct())
{
//Handling code here
}
Implementation will depend exactly on what you intend to do with the results.
You can use Distinct if your Logs class implements IEquatable
Otherwise, a quick 'hacky' fix can be to use something like
foreach (var group in _logsDutyStatusChange.GroupBy(l => new { log.did, .... more identifying fields })
{
string driverid = group.Key.did;
}
C# 4.0 tuples have 'automagic' comparing/equating as well
Here's one approach:
HashSet<string> SeenIDs = new HashSet<string>();
foreach (Logs log in _logsDutyStatusChange)
{
if (SeenIDs.Contains(log.did)) break;
SeenIDs.Add(log.did);
string driverid = log.did;
}
Distinct() doesn't quite work here, as it will only get you the distinct dids.

Categories