Order of groups with dynamic linq - c#

Somewhat similar to this question:
Where do I put the "orderby group.key" in this LINQ statement?
Except I'm using Dynamic.Linq which makes this a bit harder. I have a bunch of data coming from a database and then I'm grouping by some field and then outputing the result. The problem is that the ordering of the groups seems to randomly jump around which isn't very convenient for the end-user. So taking inspiration from the linked question, if I had this:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
// note the first non-dynamic select here was just because I don't think dynamic linq
// will support indexing a string like that and it's not an important detail anyway
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word");
foreach(IGrouping<object, dynamic> g in wordList)
{
Console.WriteLine("Words that being with {0}:",
g.Key.ToString().ToUpper());
foreach (var word in g)
Console.WriteLine(" " + word);
}
Console.ReadLine();
How would I get it to order the keys? At least part of the problem is that the dynamic GroupBy returns an IEnumerable. I thought it might be as easy as:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word")
.OrderBy("Key");
But that gives me a System.ArgumentException (At least one object must implement IComparable.) when it hits the foreach loop.
My actual code in my project is a little more complicated and looks something like this:
var colGroup = row.GroupBy(string.Format("new({0})",
string.Join(",", c)), string.Format("new({0})",
string.Join(",", v)));
Where c is a list of strings that I need to group by and v is a list of strings that I need to select in each group.

Ok - this is one way to do it, but it might be a little to static to be useful. The problem is that I had this part:
.GroupBy("new (FirstLetter)","Word");
Using new because I can't use a value type as a key (I had another question about that: https://stackoverflow.com/a/26022002/1250301). When with the OrderBy("Key") part, the problem is that it doesn't have a way to compare those dynamic types. I could solve it like this:
var wordList = words.Select(w => new {FirstLetter = w[0].ToString(), Word = w})
.GroupBy("FirstLetter","Word")
.OrderBy("Key");
Making the key a string. Or like this:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter as k)","Word")
.OrderBy("Key.k");
Making it order by something (a char) that is comparable.
I can make it work with my actual problem like this (but it's kind of ugly):
var colGroup = row.GroupBy(string.Format("new({0})", string.Join(",", c)),
string.Format("new({0})", string.Join(",", v)))
.OrderBy(string.Join(",", c.Select(ob => string.Format("Key.{0}", ob))));

I am not sure what you are trying to do, but is that syntax even compiling?
try:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy(x => x.FirstLetter, x => x.Word)
.OrderBy(x => x.Key);

Related

TextBox display closest match string

How can I get the string from a list that best match with a base string using the Levenshtein Distance.
This is my code:
{
string basestring = "Coke 600ml";
List<string> liststr = new List<string>
{
"ccoca cola",
"cola",
"coca cola 1L",
"coca cola 600",
"Coke 600ml",
"coca cola 600ml",
};
Dictionary<string, int> resultset = new Dictionary<string, int>();
foreach(string test in liststr)
{
resultset.Add(test, Ldis.Compute(basestring, test));
}
int minimun = resultset.Min(c => c.Value);
var closest = resultset.Where(c => c.Value == minimun);
Textbox1.Text = closest.ToString();
}
In this example if I run the code I get 0 changes in string number 5 from the list, so how can I display in the TextBox the string itself?
for exemple : "Coke 600ml" Right now my TextBox just returns:
System.Linq.Enumerable+WhereEnumerableIterator`1
[System.Collections.Generic.KeyValuePair`2[System.String,System.Int32]]
Thanks.
Try this
var closest = resultset.First(c => c.Value == minimun);
Your existing code is trying to display a list of items in the textbox. I looks like it should just grab a single item where Value == min
resultset.Where() returns a list, you should use
var closest = resultset.First(c => c.Value == minimun);
to select a single result.
Then the closest is a KeyValuePair<string, int>, so you should use
Textbox1.Text = closest.Key;
to get the string. (You added the string as Key and changes count as Value to resultset earilier)
There is a good solution in code project
http://www.codeproject.com/Articles/36869/Fuzzy-Search
It can be very much simplified like so:
var res = liststr.Select(x => new {Str = x, Dist = Ldis.Compute(basestring, x)})
.OrderBy(x => x.Dist)
.Select(x => x.Str)
.ToArray();
This will order the list of strings from most similar to least similar.
To only get the most similar one, simply replace ToArray() with First().
Short explanation:
For every string in the list, it creates an anonymous type which contains the original string and it's distance, computed using the Ldis class. Then, it orders the collection by the distance and maps back to the original string, so as to lose the "extra" information calculated for the ordering.

Is there a one-liner to get specific parts of this string?

I'm having a string like "a.b.c.d.e".
If I want to get an array like "a.b.c.d.e", "b.c.d.e", "c.d.e", "d.e", "e" in C#. What's the simplest approach?
Something like this will do:
var stringParts = input.Split('.');
var result = Enumerable.Range(0, stringParts.Length)
.Select(i => string.Join(".", stringParts.Skip(i)));
But like I said in my comment, please show the code you came up with and why you want to make it a one-liner, which usually doesn't serve any benefit. This isn't codegolf.
If you really do it with one statement, you can try this:
var str = "a.b.c.d.e";
var parts = str.Split('.')
.Select((x,idx) => new { idx })
.Select(p => string.Join(".",
str.Split('.').Skip(p.idx))).ToList();
This could be more efficient if you use Split first:
var parts = str.Split('.');
var result = parts
.Select((x,idx) => new { idx })
.Select(p => string.Join(".",
parts.Skip(p.idx))).ToList();
You can also do it without creating anonymous type(s), just create an int variable:
int i = 0;
var result = parts
.Select(p => string.Join(".", parts.Skip(i++)))
.ToList();
This is fairly neat:
var text = "a.b.c.d.e";
var results =
text
.Split('.')
.Reverse()
.Scan("", (a, x) => x + "." + a)
.Select(x => x.TrimEnd('.'))
.Reverse();
You do need to add the Microsoft Reactive Extensions Team's "Interactive Extensions" to get the Scan operator. Use NuGet and look for "Ix-Main".
I actually kind of like this question, not necessarily production but a bit of brain-bendy fun:
"a.b.c.d.e".Split('.').Reverse()
.Aggregate(Enumerable.Empty<string>(), (acc, c) =>
acc.Concat(new [] { c+(acc.LastOrDefault()??"") })
).Reverse()
Dotnetfiddle
What this does is move through each character in the split array and build up a new array by prepending the last value in the array with the current character. It's a fairly common functional programming technique.
Well, this is how I might write it.. I know, not "one line", but if you're gonna use (and I do recommend) a method anyway..
IEnumerable<string> AllComponentPartsForward (string s) {
IEnumerable<string> p = s.Split('.');
while (p.Any()) {
yield return string.Join(".", p); // p.ToArray() for .NET 3.5
p = p.Skip(1);
}
}
(I suppose it could be "more efficient" with IndexOf/Substring, but that's also harder for me to write and reason about!)

Regexp find and replace with the found value

I have UK postcodes data and I would like to sort them alphabeticaly, when I do that the result is as follows;
N10-XX
N1-XX
N2-XX
N3-XX
N4-XX
N5-XX
What I want is that as follows;
N1-XX
N2-XX
N3-XX
N4-XX
N5-XX
N10-XX
Basicaly I need to add 0 at the begining of the number if it is 1 digit. like N1 should be N01 to be able to do that, what is the regexp pattern for that?
Many thanks.
Well if you are bent on using Regex, then this should do it
var text = #"N10-XX
N1-XX
N2-XX
N3-XX
N4-XX
N5-XX";
text = Regex.Replace(text, #"^N(\d)-", "N0$1-", RegexOptions.Multiline);
that said you obviously will be altering the original data, so I am not sure if this is even applicable
If you want to sort numerically, but preserve the original data, then you may need to do something like this
text.Split('\n')
.Select(o => new { Original = o, Normal = Regex.Replace(o, #"^N(\d)-", "N0$1-", RegexOptions.Compiled)})
.OrderBy(o => o.Normal)
.Select(o => o.Original)
I'm not sure from the example which numbers in the post code need to be ordered. here is some regex examples for valid uk post codes http://blogs.creative-jar.com/post/Valid-UK-Postcdoe-formats.aspx. if you incorporate this using the method above you should be able to do it.
Here is a sort function returning original string in natural(?) order.
List<string> list1 = new List<string>{ "N10-XX","N1-XX","N2-XX","N3-XX","N4-XX","N5-XX" };
List<string> list2 = new List<string>() { "File (5).txt", "File (1).txt", "File (10).txt", "File (100).txt", "File (2).txt" };
var sortedList1 = MySort(list1).ToArray();
var sortedList2 = MySort(list2).ToArray();
public static IEnumerable<string> MySort(IEnumerable<string> list)
{
int maxLen = list.Select(s => s.Length).Max();
Func<string, char> PaddingChar = s => char.IsDigit(s[0]) ? ' ' : char.MaxValue;
return
list.Select(s =>
new
{
OrgStr = s,
SortStr = Regex.Replace(s, #"(\d+)|(\D+)", m => m.Value.PadLeft(maxLen, PaddingChar(m.Value)))
})
.OrderBy(x => x.SortStr)
.Select(x => x.OrgStr);
}

Get records which do not start with an alphabetical character in linq

I need to get a list of records that do not start with an alphabetical character, i.e. which either starts with a numerical character or any special character.
Whats the simple LINQ query to get this list?
List<string> Entries = new List<string>();
Entries.Add("foo");
Entries.Add("bar");
Entries.Add("#foo");
Entries.Add("1bar");
var NonAlphas = (from n in Entries
where !char.IsLetter(n.ToCharArray().First())
select n);
For Linq-to-sql you could hydrate your retrieval from the database by by enumerating the query (call ToList). From that point on, your operations will be against in-memory objects and those operations will not be translated into SQL.
List<string> Entries = dbContext.Entry.Where(n => n.EntryName).ToList();
var NonAlphas = Entries.Where(n => !char.IsLetter(n.First()));
Something like this?
List<string> lst = new List<string>();
lst.Add("first");
lst.Add("second");
lst.Add("third");
lst.Add("2abc");
var result = from i in lst where !char.IsLetter(i[0]) select i;
List<string> output = result.ToList();
Edit: I realized that using Regex here was overkill and my solution wasn't perfect anyway.
string[] x = new string[3];
x[0] = "avb";
x[1] = "31df";
x[2] = "%dfg";
var linq = from s in x where !char.IsLetter(s.ToString().First()) select s;
List<string> simplelist = new List<string>(linq);
/* in simple list you have only "31df" & "dfg" */
One thing to note is that you don't need to convert the string to a chararray to use linq on it.
The more consise version would be:
var list = new List<string> {"first","third","second","2abc"};
var result = list.Where(word => !char.IsLetter(word.First()));

Extract portion of string

I have got a collection. The coll has strings:
Location="Theater=2, Name=regal, Area=Area1"
and so on. I have to extract just the Name bit from the string. For example, here I have to extract the text 'regal'
I am struggling with the query:
Collection.Location.???? (what to add here)
Which is the most short and precise way to do it?
[Edit] : What if I have to add to a GroupBy clause
Collection.GroupBy(????);
Expanding on Paul's answer:
var location = "Theater=2, Name=regal, Area=Area1";
var foo = location
.Split(',')
.Select(x => x.Split('='))
.ToDictionary(x => x[0].Trim(), x => x[1]);
Console.WriteLine(foo["Name"]);
This populates the original string into a dictionary for easy reference. Again, no error checking or anything.
Location.Split(",").Select(x => x.Split("=")[1])
That's the extremely lazy, completely-without-error-handling way to do it :)
The quick and dirty way is a simple IndexOf/Substring extraction:
string location = "Theater=2, Name=regal, Area=Area1";
int startPos = location.IndexOf("Name=") + 5;
int endPos = location.IndexOf(",", startPos);
string name = location.Substring(startPos, endPos - startPos);
If Regex is an option you can use the lookaround constructs to pluck out a precise match. The sample I used below should work great in c#. The nice thing about this is that it will continue to work even if more comma delimited items are added before the name part.
System.Text.RegularExpressions.Match m =
System.Text.RegularExpressions.Regex.Match(
"Theater=2, Name=regal, Area=Area", #"(?<=Name=)[a-zA-Z0-9_ ]+(?=,)");
Console.WriteLine(m.Value);
Another LINQ-style answer (without the overhead of a dictionary):
var name = (from part in location.Split(',')
let pair = part.Split('=')
where pair[0].Trim() == "Name"
select pair[1].Trim()).FirstOrDefault();
re group by (edit):
var records = new[] {
new {Foo = 123, Location="Theater=2, Name=regal, Area=Area1"},
new {Foo = 123, Location="Name=cineplex, Area=Area1, Theater=1"},
new {Foo = 123, Location="Theater=2, Area=Area2, Name=regal"},
};
var qry = from record in records
let name = (from part in record.Location.Split(',')
let pair = part.Split('=')
where pair[0].Trim() == "Name"
select pair[1].Trim()).FirstOrDefault()
group record by name;
foreach (var grp in qry)
{
Console.WriteLine("{0}: {1}", grp.Key, grp.Count());
}

Categories