I am parsing a text file using
(?<DateTime>.+?\t.+?)\t(?<Data>.+?)(\t(?<Data2>.+?))?\r\n
Originally it was just
(?<DateTime>.+?\t.+?)\t(?<Data>.+?)\r\n
But then I discovered one file that had an extra column that needed to be accounted for in the 10 files this API should parse, so I had to edit it to come up with the first regex.
Here is an example of the data I am parsing
2020-05-26 08:30:06 18.6
2020-05-26 08:44:38 18.0
2020-05-26 08:52:04 17.5
2020-05-26 09:17:44 18.0
2020-05-26 10:25:35 17.5
2020-05-26 10:47:08 18.0
2020-05-26 11:06:08 18.5
And here is the data with the rogue column
2019-08-21 10:32:21 0 00000
2019-08-21 19:21:37 0 00000
2019-08-21 23:24:10 0 00000
2019-08-22 00:47:39 0 00000
Note that while these are all zeros right now, other values are possible
Now everything here is still "working" but after I made my edit to the regex now one of the files that has ~8000 records is taking a long time to process. I wrote some console outputs in the parse method and found that it seems to halt around row ~7700 for almost 10 minutes before it suddenly exits with 500. Here is my parse method(I don't think this is important but I'm throwing this in anyway)
DataRow row;
index = 0;
Console.WriteLine("Beginning parse loop");
foreach (Match match in reg.Matches(data)) {
row = table.NewRow();
foreach (List<string> column in columns) {
string value = getRegexGroupValue(match, column);
if (column[1] == "System.DateTime") {
if (value != "") {
row[column[0]] = Convert.ToDateTime(value);
}
} else if (column[1] == "System.Int32") {
row[column[0]] = Convert.ToInt32(value);
} else {
row[column[0]] = value;
}
}
table.Rows.Add(row);
Console.WriteLine(String.Format("Ending loop {0}", index++));
}
What's going on here?
When I use reg.Matches(data).Count in the debug console, it says some error and doesn't show me the row count, but when I use Notepad++ to check the regex I can get the row total just fine
EDIT: I got the file processing again using (?<DateTime>.+?\t.+?)\t(?<Data>.+?)[(\t)(\r\n)] but this is not the best solution as I am no longer capturing the extra column in that file, not sure if we'll ever use that but I'd rather have it than not
You are using .+? too much. Use negated character classes and use anchors:
(?m)^(?<DateTime>[^\t\r\n]+\t[^\t\r\n]+)\t(?<Data>[^\t\r\n]+)(?:\t(?<Data2>[^\t\r\n]+))?\r?$
See proof.
Explanation
EXPLANATION
--------------------------------------------------------------------------------
(?m) set flags for this block (with ^ and $
matching start and end of line) (case-
sensitive) (with . not matching \n)
(matching whitespace and # normally)
--------------------------------------------------------------------------------
^ the beginning of a "line"
--------------------------------------------------------------------------------
(?<DateTime> group and capture to \k<DateTime>:
--------------------------------------------------------------------------------
[^\t\r\n]+ any character except: '\t' (tab), '\r'
(carriage return), '\n' (newline) (1 or
more times (matching the most amount
possible))
--------------------------------------------------------------------------------
\t '\t' (tab)
--------------------------------------------------------------------------------
[^\t\r\n]+ any character except: '\t' (tab), '\r'
(carriage return), '\n' (newline) (1 or
more times (matching the most amount
possible))
--------------------------------------------------------------------------------
) end of \k<DateTime>
--------------------------------------------------------------------------------
\t '\t' (tab)
--------------------------------------------------------------------------------
(?<Data> group and capture to \k<Data>:
--------------------------------------------------------------------------------
[^\t\r\n]+ any character except: '\t' (tab), '\r'
(carriage return), '\n' (newline) (1 or
more times (matching the most amount
possible))
--------------------------------------------------------------------------------
) end of \k<Data>
--------------------------------------------------------------------------------
(?: group, but do not capture (optional
(matching the most amount possible)):
--------------------------------------------------------------------------------
\t '\t' (tab)
--------------------------------------------------------------------------------
(?<Data2> group and capture to \k<Data2>:
--------------------------------------------------------------------------------
[^\t\r\n]+ any character except: '\t' (tab), '\r'
(carriage return), '\n' (newline) (1
or more times (matching the most
amount possible))
--------------------------------------------------------------------------------
) end of \k<Data2>
--------------------------------------------------------------------------------
)? end of grouping
--------------------------------------------------------------------------------
\r? '\r' (carriage return) (optional (matching
the most amount possible))
--------------------------------------------------------------------------------
$ before an optional \n, and the end of a
"line"
Related
Working with a pipe-delimited file. Currently, I use Notepad++ find and replace REGEX pattern ^(?:[^|]*\|){5}\K[^|]* that replaces all lines with an empty string between the 5th and 6th |. I'm trying to programmatically do this process, but .NET does not support \K. I've tried a few instances of the backward lookup, but I cannot seem to grasp it.
string[] lines = File.ReadAllLines(path);
foreach (string line in lines)
{
string line2 = null;
string finalLine = line;
string[] col = line.Split('|');
if (col[5] != null)
{
line2 = Regex.Replace(line, #"^(?:[^|]*\|){5}\K[^|]*", "");
\K is a "workaround" for regex grammars/engines that don't support anchoring against look-behind assertions.
.NET's regex grammar has look-behind assertions (using the syntax (?<=subexpression)), so use them:
Regex.Replace(line, #"(?<=^(?:[^|]*\|){5})[^|]*", "")
In the context of .NET, this pattern now describes:
(?<= # begin (positive) look-behind assertion
^ # match start of string
(?: # begin non-capturing group
[^|]*\| # match (optional) field value + delimiter
){5} # end of group, repeat 5 times
) # end of look-behind assertion
[^|]* # match any non-delimiters (will only occur where the lookbehind is satisfied)
No need using lookbehinds, use capturing groups and backreferences:
line2 = Regex.Replace(line, #"^((?:[^|]*\|){5})[^|]*", "$1");
See proof.
EXPLANATION
--------------------------------------------------------------------------------
^ the beginning of the string
--------------------------------------------------------------------------------
( group and capture to \1:
--------------------------------------------------------------------------------
(?: group, but do not capture (5 times):
--------------------------------------------------------------------------------
[^|]* any character except: '|' (0 or more
times (matching the most amount
possible))
--------------------------------------------------------------------------------
\| '|'
--------------------------------------------------------------------------------
){5} end of grouping
--------------------------------------------------------------------------------
) end of \1
--------------------------------------------------------------------------------
[^|]* any character except: '|' (0 or more times
(matching the most amount possible))
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I am parsing some manifest files and need to sanitize them before I can load them as XML. So, these files are invalid XML files.
Consider the following snippet:
<assemblyIdentity name=""Microsoft.Windows.Shell.DevicePairingFolder"" processorArchitecture=""amd64"" version=""5.1.0.0"" type="win32" />
There are several instances of double quotes, "", that I want to replace with single occurrences, ".
Essentially, the example would be transformed to
<assemblyIdentity name="Microsoft.Windows.Shell.DevicePairingFolder" processorArchitecture="amd64" version="5.1.0.0" type="win32" />
I presume a regex would be the best approach here, however, it is not my strong point.
The following should be noted:
The manifest is a multiline string (essentially just an XML document)
Something like processorArchitecture="" is valid in the document hence why a simple string.Replace call is not appropriate.
Two ways:
String replace
var newString = s.Replace("\"\"", "\"");
Regex.
string checkStringForDoubleQuotes = #"""";
string newString = Regex.Replace(s, checkStringForDoubleQuotes , #""");
After update:
Your regex is this https://regex101.com/r/xZUtUf/1/
""(?=\w)|(?<=\w)""
string s = "test=\"\" test2=\"\"assdasad\"\"";
string checkStringForDoubleQuotes = "\"\"(?=\\w)|(?<=\\w)\"\"";
string newString = Regex.Replace(s, checkStringForDoubleQuotes , "\"");
Console.WriteLine(newString);
// test="" test2="assdasad"
https://dotnetfiddle.net/FmWXUa
Use
(\w+=)""(.*?)""(?=\s+\w+=|$)
Replace with $1"$2". See proof.
Explanation
--------------------------------------------------------------------------------
( group and capture to \1:
--------------------------------------------------------------------------------
\w+ word characters (a-z, A-Z, 0-9, _) (1 or
more times (matching the most amount
possible))
--------------------------------------------------------------------------------
= '='
--------------------------------------------------------------------------------
) end of \1
--------------------------------------------------------------------------------
"" '""'
--------------------------------------------------------------------------------
( group and capture to \2:
--------------------------------------------------------------------------------
.*? any character except \n (0 or more times
(matching the least amount possible))
--------------------------------------------------------------------------------
) end of \2
--------------------------------------------------------------------------------
"" '""'
--------------------------------------------------------------------------------
(?= look ahead to see if there is:
--------------------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1
or more times (matching the most amount
possible))
--------------------------------------------------------------------------------
\w+ word characters (a-z, A-Z, 0-9, _) (1 or
more times (matching the most amount
possible))
--------------------------------------------------------------------------------
= '='
--------------------------------------------------------------------------------
| OR
--------------------------------------------------------------------------------
$ before an optional \n, and the end of
the string
--------------------------------------------------------------------------------
) end of look-ahead
C# example:
using System;
using System.Text.RegularExpressions;
public class Example
{
public static void Main()
{
string pattern = #"(\w+=)""""(.*?)""""(?=\s+\w+=|$)";
string substitution = #"$1""$2""";
string input = #"<assemblyIdentity name=""""Microsoft.Windows.Shell.DevicePairingFolder"""" processorArchitecture=""""amd64"""" version=""""5.1.0.0"""" type=""win32"" />";
Regex regex = new Regex(pattern);
string result = regex.Replace(input, substitution);
Console.Write(result);
}
}
Use the hex escape for quotes as \x22 to make it easier to work with. This will replace each individual consecutive "" to ".
Regex.Replace(data, #"(\x22\x22)", "\x22")
First post so I am sorry if I do not provide enough information, however, I will try my best. Also, it is probably wise to note I have bare minimal knowledge of this, I know basically what this code does and that is it.
In short I am a member or a simulation game community which mainly consists of cruising around running from/being the police. the game logs all chat messages as they are sent straight to a .txt file and this code searches said chatlog for one of the parameters listed in the code below and if it matches one it will play the corresponding "beep".
The first two work however the third one i have some struggles with and was wondering if anyone here could help me understand. One of the features these "cops" have is a speed gun, which shows results in-game like so:
user clocked # 105kph/ 65mph• ignored *
user clocked # 138kph/ 86mph• **
* this is below the speed limit
** this is above the speed limit
however in the chatlog they appear like this:
user ^Lclocked # 138kph/ 86mph•
user ^Lclocked # 63kph/ 39mph•^L ignored
i have tried using varied wildcards(assuming they work) and writing the code in both fashions like so:
Regex expspeeding = new Regex(#"mph• ", RegexOptions.IgnoreCase);
Regex expspeeding = new Regex(#"#mph•^L ignored ", RegexOptions.IgnoreCase);
but it simply does not work.
Below is the main bulk of what I have, please if anyone can even suggest a direction to go in that would be greatly appreciated.
Any questions you have I will try my best to answer.
Thanks for your time.
{
public partial class Form1 : Form
{
LogFileTracer tracer;
// The search patterns
Regex expdc = new Regex(#"Disconnected", RegexOptions.IgnoreCase);
Regex expidle = new Regex(#"You will be kicked soon", RegexOptions.IgnoreCase);
Regex expnospeeding = new Regex(#"?• ignored", RegexOptions.IgnoreCase);
public Form1()
{
InitializeComponent();
tracer = new LogFileTracer();
tracer.onTextReceived += Tracer_onTextReceived;
}
// Event if line was read from logfile
private void Tracer_onTextReceived(string text)
{
try
{
if (InvokeRequired)
{
// Thanks Microsoft for that threading bullcrap
this.Invoke(new LogFileTracer.TextReceivedDelegate(Tracer_onTextReceived), text);
return;
}
}
catch
{
// Nobody cares
}
if (text == null || text.Length == 0) return;
// Check whether any of the patterns match
if (expdc.IsMatch(text))
{
// play sound pattern
txtEventLog.AppendText(text + "\r\n");
Console.Beep(1000, 100);
Console.Beep(750, 100);
Console.Beep(500, 100);
}
else if (expidle.IsMatch(text))
{
// play sound pattern
txtEventLog.AppendText(text + "\r\n");
Console.Beep(2000, 50);
Console.Beep(2050, 50);
Console.Beep(2000, 50);
}
else if (expnospeeding.IsMatch(text))
{
// play sound pattern
txtEventLog.AppendText(text + "\r\n");
Console.Beep(1000, 50);
Console.Beep(2050, 50);
Console.Beep(2000, 50);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
LblStatus.Text = "Logger Status: " + (tracer.isAlive ? "running" : "dead");
}
private void Form1_Load(object sender, EventArgs e)
{
// Try to start the reader with the textbox
if (!string.IsNullOrEmpty(TxtFilename.Text)) tracer.start(TxtFilename.Text);
}
private void CmdStart_Click(object sender, EventArgs e)
{
// Start or restart the tracer
tracer.start(TxtFilename.Text);
}
}
}
You could do a regex check for mph•$. This matches all the lines that end with mph•, but not those that include the ignored-keyword.
Description
If you're trying to parse for the following text
user ^Lclocked # 138kph/ 86mph•
user ^Lclocked # 63kph/ 39mph•^L ignored
Then I'd use a regex like this:
^user\s+\^Lclocked\s+#\s+([0-9]+)kph/\s*([0-9]+)mph[^\n\r]*
That regex will capture the data and put them into the capture groups like this:
Group 0 gets the full string
Group 1 gets the kph value
Group 2 gets the mph value
Note: I'm using flags for multiline and case-insensitive.
Sample Matches
[0][0] = user ^Lclocked # 138kph/ 86mph•
[0][1] = 138
[0][2] = 86
[1][0] = user ^Lclocked # 63kph/ 39mph•^L ignored
[1][1] = 63
[1][2] = 39
Explained
NODE EXPLANATION
----------------------------------------------------------------------
^ the beginning of a "line"
----------------------------------------------------------------------
user 'user'
----------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
\^ '^'
----------------------------------------------------------------------
Lclocked 'Lclocked'
----------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
# '#'
----------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
( group and capture to \1:
----------------------------------------------------------------------
[0-9]+ any character of: '0' to '9' (1 or more
times (matching the most amount
possible))
----------------------------------------------------------------------
) end of \1
----------------------------------------------------------------------
kph/ 'kph/'
----------------------------------------------------------------------
\s* whitespace (\n, \r, \t, \f, and " ") (0 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
( group and capture to \2:
----------------------------------------------------------------------
[0-9]+ any character of: '0' to '9' (1 or more
times (matching the most amount
possible))
----------------------------------------------------------------------
) end of \2
----------------------------------------------------------------------
mph 'mph'
----------------------------------------------------------------------
[^\n\r]* any character except: '\n' (newline), '\r'
(carriage return) (0 or more times
(matching the most amount possible))
A part of my application requires to find out the occurrence of "1p/sec" or "22p/sec" or "22p/ sec" or ( [00-99]p/sec also [00-99]p/ sec) in a string.
so far I am able to get only the first occurrence(i.e if its a single digit, like the one in the above string). I should be able to get 'n' number of occurrence
Someone pl provide guidance
string input = "US Canada calling # 1p/ sec (Base Tariff - 11p/sec). Validity : 30 Days.";
// Here we call Regex.Match.
Match match = Regex.Match(input, #"(\d)[p/sec]",
RegexOptions.IgnorePatternWhitespace);
// input.IndexOf("p/sec");
// Here we check the Match instance.
if (match.Success)
{
// Finally, we get the Group value and display it.
string key = match.Groups[1].Value;
Console.WriteLine(key);
}
You need to quantify your \d in the regex, for example by adding a + quantifier, which will then cause \d+ to match at least one but possibly more digits. To restrict to a specific number of digits, you can use the {n,m} quantifier, e.g. \d{1,2} which will then match either one or two digits.
Note also that [p/sec] as you use it in the regex is a character class, matching a single character from the set { c, e, p, s, / }, which is probably not what you want because you'd want to match the p/sec literally.
A more robust option would probably be the following
(\d+)\s*p\s*/\s*sec
which a) matches p/sec literally and also allows for whitespace between the number and the unit as well as around the /.
Use
Match match = Regex.Match(input, #"(\d{1,2})p/sec" ...
instead.
\d mathces a single digit. If you append {1,2} to that you instead match one - two digits. \d* would match zero or more and \d+ would match one or more. \d{1,10} would match 1-10 digits.
If you need to know if it was surrounded by brackets or not you could do
Match match = Regex.Match(input, #"(([\d{1,2}])|(\d{1,2}))p/sec"
...
bool hasBrackets = match.Groups[1].Value[0] == '[';
How about this regex:
^\[?\d\d?\]?/\s*sec$
Explanation:
The regular expression:
^\[?\d\d?\]?/\s*sec$
matches as follows:
NODE EXPLANATION
----------------------------------------------------------------------
^ the beginning of the string
----------------------------------------------------------------------
\[? '[' (optional (matching the most amount
possible))
----------------------------------------------------------------------
\d digits (0-9)
----------------------------------------------------------------------
\d? digits (0-9) (optional (matching the most
amount possible))
----------------------------------------------------------------------
\]? ']' (optional (matching the most amount
possible))
----------------------------------------------------------------------
/ '/'
----------------------------------------------------------------------
\s* whitespace (\n, \r, \t, \f, and " ") (0 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
sec 'sec'
----------------------------------------------------------------------
$ before an optional \n, and the end of the
string
----------------------------------------------------------------------
If i get you right, you want to find all occurrances, right? Use a matchcollection
string input = "US Canada calling # 1p/sec (Base Tariff - 11p/sec). Validity : 30 Days.";
// Here we call Regex.Match.
Regex regex = new Regex(#"(\d){1,2}[p/sec]", RegexOptions.IgnorePatternWhitespace);
MatchCollection matchCollection = regex.Matches(input);
// input.IndexOf("p/sec");
// Here we check the Match instance.
foreach (Match match in matchCollection)
{
Console.WriteLine(match.Groups[0].Value);
}
You can do this:
string input = "US Canada calling # 1p/ sec (Base Tariff - 11p/sec). 91p/ sec , 123p/ sec Validity : 30 Days.";
MatchCollection matches = Regex.Matches(input, #"\b(\d{1,2})p/\s?sec");
foreach (Match m in matches) {
string key = m.Groups[1].Value;
Console.WriteLine(key);
}
Output:
1
11
91
\b is a word boundary, to anchor the match to the start of a "word" (notice that it will not match the "123p/ sec" in the test string!)
\d{1,2} Will match one or two digits. See Quantifiers
p/\s?sec matches a literal "p/sec" with an optional whitespace before "sec"
Regex Expression:
((?<price>(\d+))p/sec)|(\[(?<price>(\d+[-]\d+))\]p/sec)
In C# you need to run a for loop to check multiple captures:
if(match.Success)
{
for(int i = 0; i< match.Groups["price"].Captures.Count;i++)
{
string key = match.Groups["price"].Value;
Console.WriteLine(key);
}
}
I have a dwg file with a filename of SMITH 3H FINAL 03-26-2012.dwg and I'm trying to find the right Regular Expression for validation purposes because I will have 100's of files weekly I need to verify the format of the filename is correct. I know very little about Regular Expressions and I have some code I found below but it is not passing as valid. If I'm reading the first line correctly, then is it expecting a comma in the filename and that's why it's not passing as valid?
string filenamePattern = String.Concat("^",
"([a-z',-.]+\\s+)+", // HARRIS, SMITH
"(\\d{1,2}-\\d{1,2}){1}\\s+", // 09-06
"([a-z]+\\s)*", //
"((\\#?\\s*(\\d(\\s*|,))*\\d*-\\d+-?H?D?\\d*?),*\\s+(&\\s)*)+", // #5,6-11H & #4,7,8-11H2, etc
"([a-z()-]+\\s)*", // CLIP-OUT (FINAL)
"(\\d{1,2}-\\d{1,2}(-\\d{2}|-\\d{4})){1}", // 05-11-2009
"\\.dwg", // .dwg
"$");
RegexOptions options = (RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.IgnoreCase);
Regex reg = new Regex(filenamePattern, options);
if (reg.IsMatch(filename))
{
valid = true;
}
According to your comments on other answer, have a try with:
^[a-z]+(?:[ -][a-z]+)*\s+\d+H\s+[a-z]+\s+\d{2}-\d{2}-\d{4}\.dwg$
explanation:
The regular expression:
(?-imsx:^[a-z]+(?:[ -][a-z]+)*\s+\d+H\s+[a-z]+\s+\d{2}-\d{2}-\d{4}\.dwg$)
matches as follows:
NODE EXPLANATION
----------------------------------------------------------------------
(?-imsx: group, but do not capture (case-sensitive)
(with ^ and $ matching normally) (with . not
matching \n) (matching whitespace and #
normally):
----------------------------------------------------------------------
^ the beginning of the string
----------------------------------------------------------------------
[a-z]+ any character of: 'a' to 'z' (1 or more
times (matching the most amount possible))
----------------------------------------------------------------------
(?: group, but do not capture (0 or more times
(matching the most amount possible)):
----------------------------------------------------------------------
[ -] any character of: ' ', '-'
----------------------------------------------------------------------
[a-z]+ any character of: 'a' to 'z' (1 or more
times (matching the most amount
possible))
----------------------------------------------------------------------
)* end of grouping
----------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
\d+ digits (0-9) (1 or more times (matching
the most amount possible))
----------------------------------------------------------------------
H 'H'
----------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
[a-z]+ any character of: 'a' to 'z' (1 or more
times (matching the most amount possible))
----------------------------------------------------------------------
\s+ whitespace (\n, \r, \t, \f, and " ") (1 or
more times (matching the most amount
possible))
----------------------------------------------------------------------
\d{2} digits (0-9) (2 times)
----------------------------------------------------------------------
- '-'
----------------------------------------------------------------------
\d{2} digits (0-9) (2 times)
----------------------------------------------------------------------
- '-'
----------------------------------------------------------------------
\d{4} digits (0-9) (4 times)
----------------------------------------------------------------------
\. '.'
----------------------------------------------------------------------
dwg 'dwg'
----------------------------------------------------------------------
$ before an optional \n, and the end of the
string
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
This is how I would do it:
// This checks for name"(\w)", then space, then 3H (\w{2}),
// this will only search for two characters, then space
// then date in the form mm-dd-yyyy or dd-mm-yyyy (\d{2}-\d{2}-\d{4})
Regex reg = new Regex(#"(\w*)\s(\w{2})\s(\w*)\s(\d{2}-\d{2}-\d{4})\.dwg");
if(reg.IsMatch(filename))
{
valid = true;
}
You would also be able to get each group. Note, that I didn't have the regex to validate the proper class period (or what I assume is class period, "#5,6-11H & #4,7,8-11H2, etc" part). This will provide a basic framework and then you can pull that group and do the checking in the code. It provides a cleaner regex.
EDIT:
Based on what #DaBears needs, I have come up with the following:
Regex reg = new Regex(#"(\w*|\w*-\w*|\w*\s\w*)\s(\w{2})\s(\w*)\s(\d{2}-\d{2}-\d{4})\.dwg");
if(reg.IsMatch(filename))
{
valid = true;
}
This will match for a last name, a hyphenated name, or a space last name and provide whatever they have in a group.