String split to object with Linq? - c#

Given:
class T
{
public string A { get; set; }
public string B { get; set; }
}
string s = "A|B";
Is there a way to split s on the | and return a T object "inline"? I know I can do something like:
s.Select(x => { string[] arr = s.Split(); return new T() { A = arr[0], B = arr[1] };
But I'm wondering if there is some obscure linq thing to do it "inline" without declaring the array and splitting inside of the select. Something more along the lines of:
s.Split().Select(x => new T() { A = x[0], B = x[1] });
Obviously that'll give you a compiler error, but you get the idea... is there a way to do it like that?

If you want to do it in one line, sure:
var s = "A|B";
var t = new T{ A = s.Split('|')[0], B = s.Split('|')[1] };
But obviously that uses Split twice and it looks bad.
This is probably a sign that you need a method:
private static T ParseT(string s) {
// do the conversion *properly* here
}
Then you can just call it:
ParseT("A|B")
Alternatively, add an explicit (recommended) or implicit conversion:
public static explicit operator T(string s) {
// do the conversion *properly* here
}

If you use query syntax then you could do something like this:
var strings=new string[] { "A|B","C|D"};
var query= from s in strings
let x=s.Split('|')
select new T{ A = x[0], B = x[1] };
Update
If "A|B" is the data source I don't recommend use Linq for that, you just could do:
var arr= str.Split('|');
var instance=new T{A = arr[0],B=arr[1]};
Or do the same in the constructor as #James recommended in his answer.

It's not really better than what you originally had, but this is one way (if s is a IEnumerable<string>:
s.Select(x=>x.Split('|')).Select(x=>new T{A=x[0],B=x[1]});
if s is a single string, then you would do:
new List<string>{s} // Now List<string> with 1 string in the list
.Select(x=>x.Split('|')) // now IEnumerable<string[]> with 1 string array in it
.Select(x=>new T{A=x[0],B=x[1]}) // now IEnumerable<T> with 1 T in it
.First(); // Now just T

Why fight actual language concepts:
void Main()
{
var strs = new List<string> { "A|B", "CCC|DD", "E|FFF"};
var Ts = strs.Select(s =>s.ToT() );
Ts.Dump();
}
static class Ext
{
static public T ToT(this string str)
{
return new T(str);
}
}
public class T
{
public string A { get; set; }
public string B { get; set; }
public T(string str)
{
var arr= str.Split('|');
A = arr[0];
B = arr[1];
}
}

NOTE I do not recommend doing it this way this was more of a fun way to do.
public static T CreateLinq(string s)
{
return s.Aggregate((a: new StringBuilder(), b: new StringBuilder(), c: false),
(acc, c) => (a: (!acc.c && c != '|' ? acc.a.Append(c) : acc.a),
b: (acc.c && c != '|' ? acc.b.Append(c) : acc.b),
c: acc.c || c == '|'),
acc => new T { A = acc.a.ToString(), B = acc.b.ToString() });
}
And the performance is not as bad as it seams on the first glance.
public class T
{
public string A { get; set; }
public string B { get; set; }
};
static void Main(string[] args)
{
var s1 = Enumerable.Range(0, 1000000).Aggregate(new StringBuilder(), (acc, i) => acc.Append("A")).ToString();
var s2 = Enumerable.Range(0, 1000000).Aggregate(new StringBuilder(), (acc, i) => acc.Append("B")).ToString();
var text =$"{s1}|{s2}";
for (int i = 0; i < 5; i++)
{
Stopwatch sw = new Stopwatch();
Console.WriteLine("Start");
sw.Start();
var t1 = CreateT(text);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
var t2 = CreateLinq(text);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
}
Console.ReadLine();
}
public static T CreateLinq(string s)
{
return s.Aggregate((a: new StringBuilder(), b: new StringBuilder(), c: false),
(acc, c) => (a: (!acc.c && c != '|' ? acc.a.Append(c) : acc.a),
b: (acc.c && c != '|' ? acc.b.Append(c) : acc.b),
c: acc.c || c == '|'),
acc => new T { A = acc.a.ToString(), B = acc.b.ToString() });
}
public static T CreateT(string s)
{
var split = s.Split('|');
return new T { A = split[0], B = split[1] };
}

Related

C# all common sequences in lists of strings

I am trying to find the longest common sequence of strings within the provided arrays.
I have 25,000 lists with sequences, with a total of 450,000 of words that I need to order by length, then by count.
List<string> listA = new List<string>() {"Step1", "Step3", "Process", "System", "Process"};
List<string> listB = new List<string>() {"Process", "System", "Process"};
List<string> listC = new List<string>() {"Terminal", "Step1", "Step3"};
...
The desired output that prints all possible sequences and their length and count is:
Sequence Length Count
Step1->Step3->Process->System->Process 5 1
Step1->Step3->Process->System 4 1
Step3->Process->System->Process 4 1
Process->System->Process 3 2
Step1->Step3->Process 3 1
Step3->Process->System 3 1
Terminal->Step1->Step3 3 1
Step1->Step3 2 2
Process->System 2 2
System->Process 2 2
Step3->Process 2 1
Terminal->Step1 2 1
Process 1 4
Step1 1 2
Step3 1 2
System 1 2
Terminal 1 1
I could only find an implementation of substrings, and not whole words that can take multiple lists as input.
Ok so you can actually overload GetHashCode and Equals to treat strings like chars in a string. Also it might be reasonable create list segment to prevent flooding runtime with multiple collections.
public class ListSegment<T>
{
private sealed class ListSegmentEqualityComparer : IEqualityComparer<ListSegment<T>>
{
public bool Equals(ListSegment<T> x, ListSegment<T> y)
{
if (x.Length != y.Length)
{
return false;
}
return x.Lst.Skip(x.Offset).Take(x.Length)
.SequenceEqual(y.Lst.Skip(y.Offset).Take(y.Length));
}
public int GetHashCode(ListSegment<T> obj)
{
unchecked
{
int hash = 17;
for (int i = obj.Offset; i < obj.Offset + obj.Length; i++)
{
hash = hash * 31 + obj.Lst[i].GetHashCode();
}
return hash;
}
}
}
public static IEqualityComparer<ListSegment<T>> Default { get; } = new ListSegmentEqualityComparer();
public List<T> Lst { get; set; }
public int Offset { get; set; }
public int Length { get; set; }
public IEnumerable<T> GetEnumerable()
{
return Lst.Skip(Offset).Take(Length);
}
public override string ToString()
{
return string.Join("->", GetEnumerable());
}
}
And then you run through list of lists counting number of occurrences
public List<KeyValuePair<ListSegment<string>, int>> GetOrderedPairs(List<List<string>> data)
{
var segmentsDictionary = new Dictionary<ListSegment<string>, int>(ListSegment<string>.Default);
foreach (var list in data)
{
for (int i = 0; i < list.Count; i++)
for (int j = i + 1; j <= list.Count; j++)
{
var segment = new ListSegment<string>
{
Lst = list,
Length = j-i,
Offset = i,
};
if (segmentsDictionary.TryGetValue(segment, out var val))
{
segmentsDictionary[segment] = val + 1;
}
else
{
segmentsDictionary[segment] = 1;
}
}
}
return segmentsDictionary.OrderByDescending(pair => pair.Key.Length).ToList();
}
To test it run following
List<string> listA = new List<string>() { "Step1", "Step3", "Process", "System", "Process" };
List<string> listB = new List<string>() { "Process", "System", "Process" };
List<string> listC = new List<string>() { "Terminal", "Step1", "Step3" };
var pairs = GetOrderedPairs(new List<List<string>>()
{
listA, listB, listC
});
foreach (var keyValuePair in pairs)
{
Console.WriteLine(keyValuePair.Key + " " + keyValuePair.Key.Length + " " + keyValuePair.Value);
}
Using some extension methods, you can create an IEQualityComparer that compares IEnumerable sequences. Using this, you can use LINQ Distinct to compare by sequences:
public static class IEnumerableExt {
public static IEnumerable<IEnumerable<T>> DistinctIE<T>(this IEnumerable<IEnumerable<T>> src) => src.Distinct(Make.IESequenceEqualityComparer<T>());
// IEnumerable<string>
public static string Join(this IEnumerable<string> src, string sep) => String.Join(sep, src);
}
public static class Make {
public static IEqualityComparer<IEnumerable<T>> IESequenceEqualityComparer<T>() => new IEnumerableSequenceEqualityComparer<T>();
public static IEqualityComparer<IEnumerable<T>> IESequenceEqualityComparer<T>(T _) => new IEnumerableSequenceEqualityComparer<T>();
public class IEnumerableSequenceEqualityComparer<T> : IEqualityComparer<IEnumerable<T>> {
public bool Equals(IEnumerable<T> x, IEnumerable<T> y) =>
Object.ReferenceEquals(x, y) || (x != null && y != null && (x.SequenceEqual(y)));
public int GetHashCode(IEnumerable<T> src) {
var hc = new HashCode();
foreach (var v in src)
hc.Add(v);
return hc.ToHashCode();
}
}
}
With these tools, you can create an extension method to generate all the subsequences of a List and all the distinct subsequences:
public static class ListExt {
public static IEnumerable<IEnumerable<T>> Subsequences<T>(this List<T> src) {
IEnumerable<T> Helper(int start, int end) {
for (int j3 = start; j3 <= end; ++j3)
yield return src[j3];
}
for (int j1 = 0; j1 < src.Count; ++j1) {
for (int j2 = j1; j2 < src.Count; ++j2)
yield return Helper(j1, j2);
}
}
public static IEnumerable<IEnumerable<T>> DistinctSubsequences<T>(this List<T> src) => src.Subsequences().DistinctIE();
}
Now you can compute the answer.
First, compute all the subsequences and combine them:
var ssA = listA.DistinctSubsequences();
var ssB = listB.DistinctSubsequences();
var ssC = listC.DistinctSubsequences();
var ssAll = ssA.Concat(ssB).Concat(ssC).DistinctIE();
Then, create some helpers for counting occurrences:
var hA = ssA.ToHashSet(Make.IESequenceEqualityComparer<string>());
var hB = ssB.ToHashSet(Make.IESequenceEqualityComparer<string>());
var hC = ssC.ToHashSet(Make.IESequenceEqualityComparer<string>());
Func<IEnumerable<string>, HashSet<IEnumerable<string>>, int> testIn = (s, h) => h.Contains(s) ? 1 : 0;
Func<IEnumerable<string>,int> countIn = s => testIn(s,hA)+testIn(s,hB)+testIn(s,hC);
Finally, compute the answer:
var ans = ssAll.Select(ss => new { Sequence = ss.Join("->"), Length = ss.Count(), Count = countIn(ss) }).OrderByDescending(sc => sc.Sequence.Length);

ASP.NET Order bool with not set

How do I order a bool by null first, then true, then false
return View("Index", db.HolidayRequestForms.ToList().OrderByDescending(e => e.Approved).ThenBy(e => e.RequestID))
I am using a using a custom display template for the bool, I don't know if that matters
You could use a custom comparer
public class ApprovedComparer : IComparer<bool?>
{
public int Compare(bool? x, bool? y)
{
var a = 0;
var b = 0;
if (x.HasValue)
a = x.Value ? 1 : 2;
if (y.HasValue)
b = y.Value ? 1 : 2;
return a - b;
}
}
Usage:
return View("Index", db.HolidayRequestForms.ToList()
.OrderBy(e => e.Approved, new ApprovedComparer())
.ThenBy(e => e.RequestID))
Can be tested in LinqPad (or a normal console app)
public class Thing
{
public string Name { get; set; }
public bool? Approved { get; set; }
}
public class ApprovedComparer : IComparer<bool?>
{
public int Compare(bool? x, bool? y)
{
var a = 0;
var b = 0;
if (x.HasValue)
a = x.Value ? 1 : 2;
if (y.HasValue)
b = y.Value ? 1 : 2;
return a - b;
}
}
void Main()
{
var thing1 = new Thing { Approved = null, Name = "Thing 1" };
var thing2 = new Thing { Approved = true, Name = "Thing 2", };
var thing3 = new Thing { Approved = false, Name = "Thing 3" };
//note the 'incorrect' order
var listOfThings = new[] { thing3, thing2, thing1 };
listOfThings
.OrderBy(x => x.Approved, new ApprovedComparer())
.Select(x => x.Name) //just for outputting the names
.Dump(); //LinqPad specifc
}
Output
As of .net 4.5, you can use Comparer<T>.Create() to create a static comparer, which can be 'inline' - ie, no separate class required.
Personally, I find the separate class a bit cleaner to read. Just my opinion, however.
var comparer = Comparer<bool?>.Create((x, y) =>
{
var a = 0;
var b = 0;
if (x.HasValue)
a = x.Value ? 1 : 2;
if (y.HasValue)
b = y.Value ? 1 : 2;
return a - b;
});
listOfThings
.OrderBy(x => x.Approved, comparer)
.Select(x => x.Name) //just for outputting the names
.Dump(); //LinqPad specifc
You can use this:
myList.OrderBy(v => !v)

Get result from list inside list

I have a structure like
class a
{
public IList<b> bs{ get; set; }
public class b
{
public string r{ get; set; }
public IList<sl> sls{ get; set; }
public class sl
{
public string sn{ get; set; }
public string st{ get; set; }
}
}
}
the query is like if sn == "abc" then get r
I have done
a aobj = new a();
var aa = aobj.bs.Where(c => c.sl != null).Select(c => c).ToList(); // here I get `r = "qwerty", sls will have data like sn = "qwerty0", st= "1" ; sn = "asdf" , st="2"; sn = "zxc" st = "abc"; sn="me" , st = "abc"
var bb = aa.where(c => c.sl.Select(dr => dr.st.ToLower().Contains("abc"))); // I 'm here checking that `sn` contain abc or not
var cc = bb.Select(c => c.r).ToList(); // result
my expected output of query is "zxc", "me"
but I am getting all the list not only contains abc.. can anyone suggest me what should I do? I am partitioning this query to debug.
Thank you
You'll need to use the Any operator to check if an enumerable collection has an item that meets a criteria.
You can't use Select as that only projects an item, it isn't returning an predicate and as such has no function in a where clause.
Here is your (fixed for syntax errors) changed code:
var aa = aobj.bs.Where(c => c.sls != null).Select(c => c).ToList();
// use Any here
var bb = aa.Where(c => c.sls.Any(dr => dr.sn.ToLower().Contains("abc")));
var cc = bb.Select(c => c.r).ToList();
And here is the test set I used:
a aobj = new a();
aobj.bs = new List<b>();
aobj.bs.Add(new b {
r ="bar",
sls = new List<sl>{
new sl { sn="tets"},
new sl { sn="no"}
}
});
aobj.bs.Add(new b {
r ="foo",
sls = new List<sl>{
new sl { sn="no"},
new sl { sn="abc"}
}
});
aobj.bs.Add(new b {
r ="fubar",
sls = new List<sl>{
new sl { sn="no"},
new sl { sn="abc"}
}
});
This will output:
foo
fubar
If you combine all operators together you'll get:
var merged = aobj
.bs
.Where(c => c.sls != null
&& c.sls.Any(dr => dr.sn.ToLower().Contains("abc")))
.Select(c => c.r);
I think you can use a code like this:
var cc = a.bs
.Where(w => w.sls?.Any(s => s.st?.ToLower().Contains("abc") ?? false) ?? false)
.Select(c => c.r);

Use list<int> as a linq group by key

This is from LinqPad (hence the .Dump())
void Main()
{
var k1 = new List<int>(){1,2,3,4,5};
var k2 = new List<int>(){1,2,3,4,5};
var o1 = new A(){k=k1, objectName="o1"};
var o2 = new A(){k=k2, objectName="o2"};
var l = new List<A>();
l.Add(o1);
l.Add(o2);
var b = from a in l
group a.objectName by a.k into g
select new {g.Key, n = String.Join(",",g)};
b.Dump();
}
public class A {
public List<int> k;
public string objectName;
}
The problem is that it doesn't work, the code above yields:
I know why this is happening, it's because the list objects are separate objects, but what I was wondering is if there is a way to tell group by to use content of the list rather than the object instance.
public static void Main()
{
var k1 = new List<int>(){1,2,3,4,5};
var k2 = new List<int>(){1,2,3,4,5};
var o1 = new A(){k=k1, objectName="o1"};
var o2 = new A(){k=k2, objectName="o2"};
var l = new List<A>();
l.Add(o1);
l.Add(o2);
// Use custom comparer for the list
var b = l.GroupBy(a => a.k, new ListComparer<int>())
.Select(g => new
{
Key = String.Join(",", g.Key.Select(i => i)),
n = String.Join(",",g.Select(i => i.objectName))
});
foreach(var item in b)
{
Console.WriteLine(string.Format("{0} : {1}", item.Key, item.n));
// 1,2,3,4,5 : o1,o2
}
}
public class A
{
public List<int> k;
public string objectName;
}
public class ListComparer<T> : IEqualityComparer<List<T>>
{
// Ignore the order and compare with the sequence value for the equality
public bool Equals(List<T> left, List<T> right)
{
return left.OrderBy(i => i).SequenceEqual(right.OrderBy(i => i));
}
public int GetHashCode(List<T> list)
{
return list.Count;
}
}
.Net Fiddle
You can convert array to string.
var b = l.GroupBy(o => string.Join(";", o.k))
.Select(g => new { Key = g.First().k,
n = string.Join(",", g.Select(o => o.objectName)) });
I managed to accomplish the following:
var b = l.SelectMany(a => a.k.Select(i => new { Key = i, n = a.objectName }))
.GroupBy(i => i.Key);

Select distinct values from a list using LINQ in C#

I have a collection of Employee
Class Employee
{
empName
empID
empLoc
empPL
empShift
}
My list contains
empName,empID,empLoc,empPL,empShift
E1,1,L1,EPL1,S1
E2,2,L2,EPL2,S2
E3,3,L3,EPL3,S3
E4,4,L1,EPL1,S1
E5,5,L5,EPL5,S5
E6,6,L2,EPL2,S2
I need to take the employees having distinct values
empLoc,empPL,empShift.
Is there is any way to achieve this using LINQ ?
You can use GroupBy with anonymous type, and then get First:
list.GroupBy(e => new {
empLoc = e.empLoc,
empPL = e.empPL,
empShift = e.empShift
})
.Select(g => g.First());
You could implement a custom IEqualityComparer<Employee>:
public class Employee
{
public string empName { get; set; }
public string empID { get; set; }
public string empLoc { get; set; }
public string empPL { get; set; }
public string empShift { get; set; }
public class Comparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
return x.empLoc == y.empLoc
&& x.empPL == y.empPL
&& x.empShift == y.empShift;
}
public int GetHashCode(Employee obj)
{
unchecked // overflow is fine
{
int hash = 17;
hash = hash * 23 + (obj.empLoc ?? "").GetHashCode();
hash = hash * 23 + (obj.empPL ?? "").GetHashCode();
hash = hash * 23 + (obj.empShift ?? "").GetHashCode();
return hash;
}
}
}
}
Now you can use this overload of Enumerable.Distinct:
var distinct = employees.Distinct(new Employee.Comparer());
The less reusable, robust and efficient approach, using an anonymous type:
var distinctKeys = employees.Select(e => new { e.empLoc, e.empPL, e.empShift })
.Distinct();
var joined = from e in employees
join d in distinctKeys
on new { e.empLoc, e.empPL, e.empShift } equals d
select e;
// if you want to replace the original collection
employees = joined.ToList();
You can try with this code
var result = (from item in List
select new
{
EmpLoc = item.empLoc,
EmpPL= item.empPL,
EmpShift= item.empShift
})
.ToList()
.Distinct();
I was curious about which method would be faster:
Using Distinct with a custom IEqualityComparer or
Using the GroupBy method described by Cuong Le.
I found that depending on the size of the input data and the number of groups, the Distinct method can be a lot more performant. (as the number of groups tends towards the number of elements in the list, distinct runs faster).
Code runs in LinqPad!
void Main()
{
List<C> cs = new List<C>();
foreach(var i in Enumerable.Range(0,Int16.MaxValue*1000))
{
int modValue = Int16.MaxValue; //vary this value to see how the size of groups changes performance characteristics. Try 1, 5, 10, and very large numbers
int j = i%modValue;
cs.Add(new C{I = i, J = j});
}
cs.Count ().Dump("Size of input array");
TestGrouping(cs);
TestDistinct(cs);
}
public void TestGrouping(List<C> cs)
{
Stopwatch sw = Stopwatch.StartNew();
sw.Restart();
var groupedCount = cs.GroupBy (o => o.J).Select(s => s.First()).Count();
groupedCount.Dump("num groups");
sw.ElapsedMilliseconds.Dump("elapsed time for using grouping");
}
public void TestDistinct(List<C> cs)
{
Stopwatch sw = Stopwatch.StartNew();
var distinctCount = cs.Distinct(new CComparerOnJ()).Count ();
distinctCount.Dump("num distinct");
sw.ElapsedMilliseconds.Dump("elapsed time for using distinct");
}
public class C
{
public int I {get; set;}
public int J {get; set;}
}
public class CComparerOnJ : IEqualityComparer<C>
{
public bool Equals(C x, C y)
{
return x.J.Equals(y.J);
}
public int GetHashCode(C obj)
{
return obj.J.GetHashCode();
}
}
Try,
var newList =
(
from x in empCollection
select new {Loc = x.empLoc, PL = x.empPL, Shift = x.empShift}
).Distinct();

Categories