Using group by in C# - c#

Could anyone explain what this sample of code is doing? I can't quite grasp how the words string is being grouped. Is it taking the first letter of each word and grouping them somehow?
// Create a data source.
string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots" };
// Create the query.
var wordGroups1 =
from w in words
group w by w[0] into fruitGroup
where fruitGroup.Count() >= 2
select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };

The LINQ query groups all the words by their first character. It then removes all groups which contain only one element (=keeps all groups with two or more elements). At the end the groups are filled into new anonymous objects containing the first letter and number of words found starting with that letter.
The LINQ Documentation and samples should get you started reading and writing code like that.

// Create a data source.
string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots" };
// Create the query.
var wordGroups1 =
from w in words //w is every single string in words
group w by w[0] into fruitGroup //group based on first character of w
where fruitGroup.Count() >= 2 //select those groups which have 2 or more members
//having the result so far, it makes what is needed with select
select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };
Another example. In the array show the frequency of string's length:
var wordGroups1 =
from w in words
group w by w.Length into myGroup
select new { StringLength = myGroup.Key, Freq = myGroup.Count() };
//result: 1 6-length string
// 1 11-length string
// 2 7-length string
// 1 8-length string

Related

C# String split based on the delimiter pattern [,]

I am having trouble splitting the below text. Is there any easier way to split this.
the input will be either like
"1[,]Group A[,]2[,]Group B[,]3[,]Group C[,]4[,]Group D"
OR
"a[,]Group A[,]b[,]Group B[,]c[,]Group C[,]d[,]Group D"
OR
"a)[,]Group A[,]b)[,]Group B[,]c)[,]Group C[,]d)[,]Group D"
Or Sometimes it will be like below text. How do I identify the absence of above pattern as well
"1 Group A[,]2 Group B[,]3 Group C[,]4 Group D"
Expected output
Group A
Group B
Group C
Group D
Instead of splitting your string, you can try just picking the parts you want out of the string:
var r = new Regex("Group [A-Z]");
var m = r.Matches(inputstring);
var result = m.Select(t => t.Value).ToList();
That will match any "Group" followed by a single uppercase letter.
I cooked up this method real quick with a pseudo pattern check:
static void pattern(string input)
{
string[] splits = input.Split(new[] { "[,]" }, StringSplitOptions.None);
if (splits.Length < 2)
return;
//pseudo pattern check
char[] patternStart = splits[0].ToCharArray();
for(int i = 2; i < splits.Length; i+=2)
{
patternStart[0]++;
if (!patternStart.SequenceEqual(splits[i]))
{
Console.WriteLine("pattern fail");
return;
}
}
foreach (string entry in splits.Where((s, i) => i % 2 == 1))
Console.WriteLine(entry);
}
The pattern check if based on the idea that it is always the first character in the patter that is increasing and it will always be something progressing by 1 in the ASCII table (e.g. a,b,c or A,B,C or 1,2,3)
Running this with the provided patterns:
pattern("1[,]Group A[,]2[,]Group B[,]3[,]Group C[,]4[,]Group D");
Console.WriteLine();
pattern("a[,]Group A[,]b[,]Group B[,]c[,]Group C[,]d[,]Group D");
Console.WriteLine();
pattern("a)[,]Group A[,]b)[,]Group B[,]c)[,]Group C[,]d)[,]Group D");
Console.WriteLine();
pattern("1 Group A[,]2 Group B[,]3 Group C[,]4 Group D");
Console.WriteLine();
yields
Group A
Group B
Group C
Group D
Group A
Group B
Group C
Group D
Group A
Group B
Group C
Group D
pattern fail
Assuming your group names must be longer than two characters, you can simply use:
var groups = input.Replace("[,]","\0").Split( '\0' ).Where( x => x.Length > 2 );
var output = string.Join( " ", groups );
Or
var groups = input.Split( "[,]" ).Where( x => x.Length > 2 );
var output = string.Join( " ", groups );
If your group names might be 2 or fewer characters, your requirements are not complete, as there is ambiguity. For example, with this input:
a)[,]a)[,]b)[,]b)
The output could be either
a) b)
Or
a) a) b) b)
...so you will need to come up with a rule to distinguish the text you wish to keep from the text you wish to discard.

Check if chars of a string contains in another string with LINQ

I'm making a Scrabble game in the command line with C#. The player must input some words like list below:
Word
Points
some
6
first
8
potsie
8
day
7
could
8
postie
8
from
9
have
10
back
12
this
7
The letters the player got are this:
sopitez
This value is a string. I'll check if the letters contains in the words. For this I've tried this code:
String highst = (from word
in words
where word.Contains(letters)
orderby points descending
select word).First();
But it doesn't work how I'll it. This code wouldn't select any word. I know the reason why because sopitez doesn't contain in any word.
My question now is there a way to check the chars in the string letters contain into the words whitout looping over the chars.
Note: Each letter must be used at most once in the solution.
If I calculate the result it must be potsie or postie. (I must write the logic for that)
P.S.: I'm playing this game: www.codingame.com/ide/puzzle/scrabble
This will not be performant at all but at least it will do the trick. Notice that I've used a dictionary just for the sake of simplicity (also I don't see why you would have repeated words like "potsie", I've never played scrabble). You can as well use a list of Tuples if you follow this code
EDIT: I changed this according to the OP's new comments
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var letters = new HashSet<char>("sopitez");
var wordsMap = new Dictionary<string, int>()
{
{"some", 6}, {"first", 8}, {"potsie", 8}, {"postie", 8}, {"day", 7},
{"could", 8}, {"from", 9}, {"have", 10}, {"back", 12},
{"this", 7}
};
var highest = wordsMap
.Select(kvp => {
var word = kvp.Key;
var points = kvp.Value;
var matchCount = kvp.Key.Sum(c => letters.Contains(c) ? 1 : 0);
return new {
Word = word,
Points = points,
MatchCount = matchCount,
FullMatch = matchCount == word.Length,
EstimatedScore = points * matchCount /(double) word.Length // This can vary... it's just my guess for an "Estiamted score"
};
})
.OrderByDescending(x => x.FullMatch)
.ThenByDescending(x => x.EstimatedScore);
foreach (var anon in highest)
{
Console.WriteLine("{0}", anon);
}
}
}
The problem here is that Contains checks to see if one string contains another; it is not checking to see if it contains all of those characters. You need to replace each string in your dictionary with a HashSet<char> and perform set comparisons like IsSubset or IsSuperset to determine if the letters are matching.
Here is what you're doing:
string a= "Hello";
string b= "elHlo";
bool doesContain = b.Contains(a); //This returns false
Here is what you need to do:
var setA = new HashSet<char>(a);
var setB = new HashSet<char>(b);
bool isSubset = a.IsSubsetOf(b); //This returns true
Update
Actually, this is wrong, because sets remove duplicate elements. But essentially you are misusing Contains. You'll need some more complicated sequence comparison that can allow duplicate letters.
Update2
You need this for word/letters comparison:
//Compares counts of each letter in word and tiles
bool WordCanBeMadeFromLetters(string word, string tileLetters) {
var tileLetterCounts = GetLetterCounts(tileLetters);
var wordLetterCounts = GetLetterCounts(word);
return wordLetterCounts.All(letter =>
tileLetterCounts.ContainsKey(letter.Key)
&& tileLetterCounts[letter.Key] >= letter.Value);
}
//Gets dictionary of letter/# of letter in word
Dictionary<char, int> GetLetterCounts(string word){
return word
.GroupBy(c => c)
.ToDictionary(
grp => grp.Key,
grp => grp.Count());
}
So your original example can look like this:
String highst = (from word
in words
where WordCanBeMadeFromLetters(word, letters)
orderby points descending
select word).First();
Since letters can repeat, I think you need something like this (of course that's not very efficient, but pure LINQ):
var letters = "sopitezwss";
var words = new Dictionary<string, int>() {
{"some", 6}, {"first", 8}, {"potsie", 8}, {"day", 7},
{"could", 8}, {"from", 9}, {"have", 10}, {"back", 12},
{"this", 7}, {"postie", 8}, {"swiss", 15}
};
var highest = (from word
in words
where word.Key.GroupBy(c => c).All(c => letters.Count(l => l == c.Key) >= c.Count())
orderby word.Value descending
select word);

Regex that match different format sentences in c#

Format of file
POS ID PosScore NegScore SynsetTerms Gloss
a 00001740 0.125 0 able#1" able to swim"; "she was able to program her computer";
a 00002098 0 0.75 unable#1 "unable to get to town without a car";
a 00002312 0 0 dorsal#2 abaxial#1 "the abaxial surface of a leaf is the underside or side facing away from the stem"
a 00002843 0 0 basiscopic#1 facing or on the side toward the base
a 00002956 0 0.23 abducting#1 abducent#1 especially of muscles; drawing away from the midline of the body or from an adjacent part
a 00003131 0 0 adductive#1 adducting#1 adducent#1 especially of muscles;
In this file, I want to extract (ID,PosScore,NegScore and SynsetTerms) field. The (ID,PosScore,NegScore) field data extraction is easy and I use the following code for the data of these fields.
Regex expression = new Regex(#"(\t(\d+)|(\w+)\t)");
var results = expression.Matches(input);
foreach (Match match in results)
{
Console.WriteLine(match);
}
Console.ReadLine();
and it give the correct result but the Filed SynsetTerms create a problem because some lines have two or more words so how organize word and get against it PosScore And NegScore.
For example, in fifth line there are two words abducting#1 and abducent#1 but both have same score.
So what will be regex for such line that get Word and its score, like:
Word PosScore NegScore
abducting#1 0 0.23
abducent#1 0 0.23
The non-regex, string-splitting version might be easier:
var data =
lines.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
.Skip(1)
.Select(line => line.Split('\t'))
.SelectMany(parts => parts[4].Split().Select(word => new
{
ID = parts[1],
Word = word,
PosScore = decimal.Parse(parts[2]),
NegScore = decimal.Parse(parts[3])
}));
You can use this regex
^(?<pos>\w+)\s+(?<id>\d+)\s+(?<pscore>\d+(?:\.\d+)?)\s+(?<nscore>\d+(?:\.\d+)?)\s+(?<terms>(?:.*?#[^\s]*)+)\s+(?<gloss>.*)$
You can create a list like this
var lst=Regex.Matches(input,regex)
.Cast<Match>()
.Select(x=>
new
{
pos=x.Groups["pos"].Value,
terms=Regex.Split(x.Groups["terms"].Value,#"\s+"),
gloss=x.Groups["gloss"].Value
}
);
and now you can iterate over it
foreach(var temp in lst)
{
temp.pos;
//you can now iterate over terms
foreach(var t in temp.terms)
{
}
}

C# Regular Expression: How to extract a collection

I have collection in text file:
(Collection
(Item "Name1" 1 2 3)
(Item "Simple name2" 1 2 3)
(Item "Just name 3" 4 5 6))
Collection also could be empty:
(Collection)
The number of items is undefined. It could be one item or one hundred. By previous extraction I already have inner text between Collection element:
(Item "Name1" 1 2 3)(Item "Simple name2" 1 2 3)(Item "Just name 3" 4 5 6)
In the case of empty collection it will be empty string.
How could I parse this collection using .Net Regular Expression?
I tried this:
string pattern = #"(\(Item\s""(?<Name>.*)""\s(?<Type>.*)\s(?<Length>.*)\s(?<Number>.*))*";
But the code above doesn't produce any real results.
UPDATE:
I tried to use regex differently:
foreach (Match match in Regex.Matches(document, pattern, RegexOptions.Singleline))
{
for (int i = 0; i < match.Groups["Name"].Captures.Count; i++)
{
Console.WriteLine(match.Groups["Name"].Captures[i].Value);
}
}
or
while (m.Success)
{
m.Groups["Name"].Value.Dump();
m.NextMatch();
}
Try
\(Item (?<part1>\".*?\")\s(?<part2>\d+)\s(?<part3>\d+)\s(?<part4>\d+)\)
this will create a collection of matches:
Regex regex = new Regex(
"\\(Item (?<part1>\\\".*?\\\")\\s(?<part2>\\d+)\\s(?<part3>\\d"+
"+)\\s(?<part4>\\d+)\\)",
RegexOptions.Multiline | RegexOptions.Compiled
);
//Capture all Matches in the InputText
MatchCollection ms = regex.Matches(InputText);
//Get the names of all the named and numbered capture groups
string[] GroupNames = regex.GetGroupNames();
// Get the numbers of all the named and numbered capture groups
int[] GroupNumbers = regex.GetGroupNumbers();
I think you might need to make your captures non-greedy...
(?<Name>.*?)
instead of
(?<Name>.*)
I think you should read file and than make use of Sting.Split function to split the collection and start to read it
String s = "(Collection
(Item "Name1" 1 2 3)
(Item "Simple name2" 1 2 3)
(Item "Just name 3" 4 5 6))";
string colection[] = s.Split('(');
if(colection.Length>1)
{
for(i=1;i<colection.Length;i++)
{
//process string one by one and add ( if you need it
//from the last item remove )
}
}
this will resolve issue easily there is no need of put extra burden of regulat expression.

What's the difference between "groups" and "captures" in .NET regular expressions?

I'm a little fuzzy on what the difference between a "group" and a "capture" are when it comes to .NET's regular expression language. Consider the following C# code:
MatchCollection matches = Regex.Matches("{Q}", #"^\{([A-Z])\}$");
I expect this to result in a single capture for the letter 'Q', but if I print the properties of the returned MatchCollection, I see:
matches.Count: 1
matches[0].Value: {Q}
matches[0].Captures.Count: 1
matches[0].Captures[0].Value: {Q}
matches[0].Groups.Count: 2
matches[0].Groups[0].Value: {Q}
matches[0].Groups[0].Captures.Count: 1
matches[0].Groups[0].Captures[0].Value: {Q}
matches[0].Groups[1].Value: Q
matches[0].Groups[1].Captures.Count: 1
matches[0].Groups[1].Captures[0].Value: Q
What exactly is going on here? I understand that there's also a capture for the entire match, but how do the groups come in? And why doesn't matches[0].Captures include the capture for the letter 'Q'?
You won't be the first who's fuzzy about it. Here's what the famous Jeffrey Friedl has to say about it (pages 437+):
Depending on your view, it either adds
an interesting new dimension to the
match results, or adds confusion and
bloat.
And further on:
The main difference between a Group
object and a Capture object is that
each Group object contains a
collection of Captures representing
all the intermediary matches by the
group during the match, as well as the
final text matched by the group.
And a few pages later, this is his conclusion:
After getting past the .NET
documentation and actually
understanding what these objects add,
I've got mixed feelings about them. On
one hand, it's an interesting
innovation [..] on the other hand, it
seems to add an efficiency burden [..]
of a functionality that won't be used
in the majority of cases
In other words: they are very similar, but occasionally and as it happens, you'll find a use for them. Before you grow another grey beard, you may even get fond of the Captures...
Since neither the above, nor what's said in the other post really seems to answer your question, consider the following. Think of Captures as a kind of history tracker. When the regex makes his match, it goes through the string from left to right (ignoring backtracking for a moment) and when it encounters a matching capturing parentheses, it will store that in $x (x being any digit), let's say $1.
Normal regex engines, when the capturing parentheses are to be repeated, will throw away the current $1 and will replace it with the new value. Not .NET, which will keep this history and places it in Captures[0].
If we change your regex to look as follows:
MatchCollection matches = Regex.Matches("{Q}{R}{S}", #"(\{[A-Z]\})+");
you will notice that the first Group will have one Captures (the first group always being the whole match, i.e., equal to $0) and the second group will hold {S}, i.e. only the last matching group. However, and here's the catch, if you want to find the other two catches, they're in Captures, which contains all intermediary captures for {Q} {R} and {S}.
If you ever wondered how you could get from the multiple-capture, which only shows last match to the individual captures that are clearly there in the string, you must use Captures.
A final word on your final question: the total match always has one total Capture, don't mix that with the individual Groups. Captures are only interesting inside groups.
This can be explained with a simple example (and pictures).
Matching 3:10pm with the regular expression ((\d)+):((\d)+)(am|pm), and using Mono interactive csharp:
csharp> Regex.Match("3:10pm", #"((\d)+):((\d)+)(am|pm)").
> Groups.Cast<Group>().
> Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g);
{ "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" }
So where's the 1?
Since there are multiple digits that match on the fourth group, we only "get at" the last match if we reference the group (with an implicit ToString(), that is). In order to expose the intermediate matches, we need to go deeper and reference the Captures property on the group in question:
csharp> Regex.Match("3:10pm", #"((\d)+):((\d)+)(am|pm)").
> Groups.Cast<Group>().
> Skip(4).First().Captures.Cast<Capture>().
> Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c);
{ "[0] 1", "[1] 0" }
Courtesy of this article.
A Group is what we have associated with groups in regular expressions
"(a[zx](b?))"
Applied to "axb" returns an array of 3 groups:
group 0: axb, the entire match.
group 1: axb, the first group matched.
group 2: b, the second group matched.
except that these are only 'captured' groups. Non capturing groups (using the '(?: ' syntax are not represented here.
"(a[zx](?:b?))"
Applied to "axb" returns an array of 2 groups:
group 0: axb, the entire match.
group 1: axb, the first group matched.
A Capture is also what we have associated with 'captured groups'. But when the group is applied with a quantifier multiple times, only the last match is kept as the group's match. The captures array stores all of these matches.
"(a[zx]\s+)+"
Applied to "ax az ax" returns an array of 2 captures of the second group.
group 1, capture 0 "ax "
group 1, capture 1 "az "
As for your last question -- I would have thought before looking into this that Captures would be an array of the captures ordered by the group they belong to. Rather it is just an alias to the groups[0].Captures. Pretty useless..
From the MSDN documentation:
The real utility of the Captures property occurs when a quantifier is applied to a capturing group so that the group captures multiple substrings in a single regular expression. In this case, the Group object contains information about the last captured substring, whereas the Captures property contains information about all the substrings captured by the group. In the following example, the regular expression \b(\w+\s*)+. matches an entire sentence that ends in a period. The group (\w+\s*)+ captures the individual words in the collection. Because the Group collection contains information only about the last captured substring, it captures the last word in the sentence, "sentence". However, each word captured by the group is available from the collection returned by the Captures property.
Imagine you have the following text input dogcatcatcat and a pattern like dog(cat(catcat))
In this case, you have 3 groups, the first one (major group) corresponds to the match.
Match == dogcatcatcat and Group0 == dogcatcatcat
Group1 == catcatcat
Group2 == catcat
So what it's all about?
Let's consider a little example written in C# (.NET) using Regex class.
int matchIndex = 0;
int groupIndex = 0;
int captureIndex = 0;
foreach (Match match in Regex.Matches(
"dogcatabcdefghidogcatkjlmnopqr", // input
#"(dog(cat(...)(...)(...)))") // pattern
)
{
Console.Out.WriteLine($"match{matchIndex++} = {match}");
foreach (Group #group in match.Groups)
{
Console.Out.WriteLine($"\tgroup{groupIndex++} = {#group}");
foreach (Capture capture in #group.Captures)
{
Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}");
}
captureIndex = 0;
}
groupIndex = 0;
Console.Out.WriteLine();
}
Output:
match0 = dogcatabcdefghi
group0 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group1 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group2 = catabcdefghi
capture0 = catabcdefghi
group3 = abc
capture0 = abc
group4 = def
capture0 = def
group5 = ghi
capture0 = ghi
match1 = dogcatkjlmnopqr
group0 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group1 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group2 = catkjlmnopqr
capture0 = catkjlmnopqr
group3 = kjl
capture0 = kjl
group4 = mno
capture0 = mno
group5 = pqr
capture0 = pqr
Let's analyze just the first match (match0).
As you can see there are three minor groups: group3, group4 and group5
group3 = kjl
capture0 = kjl
group4 = mno
capture0 = mno
group5 = pqr
capture0 = pqr
Those groups (3-5) were created because of the 'subpattern' (...)(...)(...) of the main pattern (dog(cat(...)(...)(...)))
Value of group3 corresponds to it's capture (capture0). (As in the case of group4 and group5). That's because there are no group repetition like (...){3}.
Ok, let's consider another example where there is a group repetition.
If we modify the regular expression pattern to be matched (for code shown above)
from (dog(cat(...)(...)(...))) to (dog(cat(...){3})),
you'll notice that there is the following group repetition: (...){3}.
Now the Output has changed:
match0 = dogcatabcdefghi
group0 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group1 = dogcatabcdefghi
capture0 = dogcatabcdefghi
group2 = catabcdefghi
capture0 = catabcdefghi
group3 = ghi
capture0 = abc
capture1 = def
capture2 = ghi
match1 = dogcatkjlmnopqr
group0 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group1 = dogcatkjlmnopqr
capture0 = dogcatkjlmnopqr
group2 = catkjlmnopqr
capture0 = catkjlmnopqr
group3 = pqr
capture0 = kjl
capture1 = mno
capture2 = pqr
Again, let's analyze just the first match (match0).
There are no more minor groups group4 and group5 because of (...){3} repetition ({n} wherein n>=2)
they've been merged into one single group group3.
In this case, the group3 value corresponds to it's capture2 (the last capture, in other words).
Thus if you need all the 3 inner captures (capture0, capture1, capture2) you'll have to cycle through the group's Captures collection.
Сonclusion is: pay attention to the way you design your pattern's groups.
You should think upfront what behavior causes group's specification, like (...)(...), (...){2} or (.{3}){2} etc.
Hopefully it will help shed some light on the differences between Captures, Groups and Matches as well.

Categories

Resources