public class Derp
{
public Derp
{
listOfStrings = new List<string>();
}
public string strName;
public List<string> listOfStrings;
public int unrequiredInt;
public bool unrequiredBool;
}
List<Derp> derp1 = ... //generate data assume strName is unique in list, but not across lists;
List<Derp> derp2 = ... //generate data;
List<Derp> derp3 = ... //generate data;
List<Derp> mergedDerp = new List<Derp>();
I need to merge derp1 and derp2 and derp3 with the condition derp1[x].strName == derp2[y].strName == derp3[z].strName. The merged list should have all Derps but merge derp1,2,3 into one derp based on the condition above (unrequiredInt and unrequiredBool's content doesn't matter). I know it can be done in LINQ but I'm quite at a loss. Something like ...
mergedDerp = derp1.Join(derp2, d1 => derp1, d2 => derp2, (d1,d2) => new { ... ;
//and the next derp would be (i assume)
mergedDerp = mergedDerp.Join(derp3, md => mergedDerp, ...;
But i'm not getting it.
The result should contain a list of unique Derps by their strName, and if any Derps were merged, the listOfStrings should all be appended into the new Derp.
Using GroupBy instead of Join seems more suitable in your case:
var mergedDerp = derp1.Union(derp2).Union(derp3).GroupBy(x => x.strName)
.Select(x => new Derp
{
strName = x.Key,
// I guess you want to merge the list of strings as well?
listOfStrings = x.SelectMany(d => d.listOfStrings).ToList()
// Leave unrequired fields as default or just use the first derp's value
// unrequiredInt = x.First().unrequiredInt,
// unrequiredBool = x.First().unrequiredBool,
})
.ToList();
It sounds like you want to determine equality based on the strName value. If so, simply implement the Equals and GetHashCode methods on the object:
public class Derp
{
public Derp()
{
listOfStrings = new List<string>();
}
public string strName;
public List<string> listOfStrings;
public int unrequiredInt;
public bool unrequiredBool;
public override bool Equals(object obj)
{
return ((Derp) obj).strName.Equals(strName);
}
public override int GetHashCode()
{
return strName.GetHashCode();
}
}
Then when you combine them, you can just use Union and Distinct:
var derp1 = new List<Derp>();
derp1.Add(new Derp() {strName = "X"});
derp1.Add(new Derp() { strName = "Y" });
derp1.Add(new Derp() { strName = "Z" });
var derp2 = new List<Derp>();
derp2.Add(new Derp() {strName = "A"});
derp2.Add(new Derp() { strName = "B" });
derp2.Add(new Derp() { strName = "X" });
var derp3 = new List<Derp>();
derp3.Add(new Derp() { strName = "J" });
derp3.Add(new Derp() { strName = "B" });
derp3.Add(new Derp() { strName = "X" });
var merged = derp1.Union(derp2.Union(derp3)).Distinct();
Console.WriteLine(merged.Count()); // Returns 6: X, Y, Z, A, B, J
Related
I have a Generic List of type List<InstanceDataLog> which has huge number of properties in it. I want to pass names of a few properties to a method and want to extract a refined List from within this list.
public void Export(string col) //a,b,c
{
string [] selectedcol = col.Split(',');
var grid = new GridView();
var data = TempData["InstanceDataList"];
List<InstanceDataLog> lst = new List<InstanceDataLog>();
List<EToolsViewer.APIModels.InstanceDataLog> lstrefined = new List<InstanceDataLog>();
lst= (List<EToolsViewer.APIModels.InstanceDataLog>)TempData["InstanceDataList"];
var r= lst.Select(e => new {e.a, e.b}).ToList();// I would like to replace these hardcoded properties with names of properties present in `selectedcol `
grid.DataSource =r;
grid.DataBind();
}
To clear things up further, suppose InstanceDataLog has 5 properties : a,b,c,d,e I would like pass a,b and be able to extract a new list with only properties a,b
EDIT:
$('#export').mousedown(function () {
window.location = '#Url.Action("Export", "TrendsData",new { col = "a,b,c" })';
});
You could use such method to get properties:
private object getProperty(EToolsViewer.APIModels.InstanceDataLog e, string propName)
{
var propInfo =typeof(EToolsViewer.APIModels.InstanceDataLog).GetProperty(propName);
return propInfo.GetValue(e);
}
and with another function you could get all properties you want:
private dynamic getProperties(string[] props, EToolsViewer.APIModels.InstanceDataLog e )
{
var ret = new ExpandoObject() as IDictionary<string, Object>;;
foreach (var p in props)
{
ret.Add(p, getProperty(e, p));
}
return ret;
}
The problem occurs if you try to assign DataSource with expando object. Solution is described hier:
Binding a GridView to a Dynamic or ExpandoObject object
We do need one more method:
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
var data = items.ToArray();
if (data.Count() == 0) return null;
var dt = new DataTable();
foreach (var key in ((IDictionary<string, object>)data[0]).Keys)
{
dt.Columns.Add(key);
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}
and use it:
var r = lst.Select(e => getProperties(selectedcol, e)).ToList();
grid.DataSource = ToDataTable(r);
The same thing, but ready to run for LinqPad:
void Main()
{
var samples = new[] { new Sample { A = "A", B = "B", C = "C" }, new Sample { A = "A1", B = "B2", C = "C1" } };
var r = samples.Select(e => getProperties(new[] {"A", "C", "B"}, e)).ToList();
r.Dump();
}
private object getProperty(Sample e, string propName)
{
var propInfo = typeof(Sample).GetProperty(propName);
return propInfo.GetValue(e);
}
private dynamic getProperties(string[] props, Sample e)
{
var ret = new ExpandoObject() as IDictionary<string, Object>; ;
foreach (var p in props)
{
ret.Add(p, getProperty(e, p));
}
return ret;
}
public class Sample
{
public string A { get; set;}
public string B { get; set;}
public string C { get; set;}
}
With output:
To keep compiler type/name checking suggest to pass a Func<InstanceDataLog, TResult> instead of array of names
public void Export<TResult>(Func<InstanceDataLog, TResult> selectProperties)
{
var grid = new GridView();
var data = TempData["InstanceDataList"];
var originalList = (List<InstanceDataLog>)TempData["InstanceDataList"];
var filteredList = originalList.Select(selectProperties).ToList();
grid.DataSource = filteredList;
grid.DataBind();
}
Then use it:
Export(data => new { Id = data.Id, Name = data.Name });
Can any body help on this.
here i am using datasource as list for combobox.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
LoadDropdownlist();
}
public void LoadDropdownlist()
{
var makes = new List<string> {
"Audi",
"BMW",
"Ford",
"Vauxhall",
"Volkswagen"
};
comboBox1.DataSource = makes;
}
as per above code i am getting result in combobox as
Audi,
BMW,
Ford,
Vauxhall,
Volkswagen
but i want to display unique records based on first character.So i am expecting below result in combobox.
Audi,
BMW,
Ford,
Vauxhall
thanks,
satish
public void LoadDropdownlist()
{
var makes = new List<string> {"Volkswagen","Audi","BMW","Ford","Vauxhall"};
FirstLetterComparer comp = new FirstLetterComparer();
comboBox1.DataSource= makes.Distinct(comp).ToList();//makes.GroupBy(x=>x[0]).Select(x=>x.First()).ToList();
}
public class FirstLetterComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y))
return false;
//ignoring case
return string.Compare(x[0].ToString(), y[0].ToString(), 0) == 0;
}
public int GetHashCode(string str)
{
return 1;
}
}
When checking two objects for equality the following happens:
First, GetHashCode is called on both objects. If the hash code is different, the objects are considered not equal and Equals is never called.
Equals is only called when GetHashCode returned the same value for both objects.
Solution for .Net 2
In .Net 2 you need to loop through items as this
public List<string> FetchFirstLetterUniques(List<string> source)
{
List<string> result = new List<string>();
foreach (string item in source)
{
bool isAdded = false;
foreach (string item2 in result)
{
if (string.Compare(item2[0].ToString(), item[0].ToString(), 0) == 0)
{
isAdded = true;
break;
}
}
if (!isAdded)
result.Add(item);
}
return result;
}
public void LoadDropdownlist()
{
var makes = new List<string> {
"Audi",
"BMW",
"Ford",
"Vauxhall",
"Volkswagen"
};
comboBox1.DataSource = FetchFirstLetterUniques(makes);
}
You can group them based on first characters:
comboBox1.DataSource = makes
.GroupBy(x => x[0])
.Select(x => x.First())
.ToList();
try this
var makes = new List<string> {
"Audi",
"BMW",
"Ford",
"Vauxhall",
"Volkswagen"
}.GroupBy(t => t[0])
.Select(t => t.First()).ToList();
Each item/string in my array starts with two letters followed by two or three numbers and then sometimes followed by another letter.
Examples, RS01 RS10 RS32A RS102 RS80 RS05A RS105A RS105B
I tried to sort this using the default Array.Sort but it came back with this...
RS01
RS05A
RS10
RS102
RS105A
RS105B
RS32A
RS80
But I need it like this..
RS01
RS05A
RS10
RS32A
RS80
RS102
RS105A
RS105B
Any Ideas?
Here is sorting with custom comparison delegate and regular expressions:
string[] array = { "RS01", "RS10", "RS32A", "RS102",
"RS80", "RS05A", "RS105A", "RS105B" };
Array.Sort(array, (s1, s2) =>
{
Regex regex = new Regex(#"([a-zA-Z]+)(\d+)([a-zA-Z]*)");
var match1 = regex.Match(s1);
var match2 = regex.Match(s2);
// prefix
int result = match1.Groups[1].Value.CompareTo(match2.Groups[1].Value);
if (result != 0)
return result;
// number
result = Int32.Parse(match1.Groups[2].Value)
.CompareTo(Int32.Parse(match2.Groups[2].Value));
if (result != 0)
return result;
// suffix
return match1.Groups[3].Value.CompareTo(match2.Groups[3].Value);
});
UPDATE (little refactoring, and moving all stuff to separate comparer class). Usage:
Array.Sort(array, new RSComparer());
Comparer itself:
public class RSComparer : IComparer<string>
{
private Dictionary<string, RS> entries = new Dictionary<string, RS>();
public int Compare(string x, string y)
{
if (!entries.ContainsKey(x))
entries.Add(x, new RS(x));
if (!entries.ContainsKey(y))
entries.Add(y, new RS(y));
return entries[x].CompareTo(entries[y]);
}
private class RS : IComparable
{
public RS(string value)
{
Regex regex = new Regex(#"([A-Z]+)(\d+)([A-Z]*)");
var match = regex.Match(value);
Prefix = match.Groups[1].Value;
Number = Int32.Parse(match.Groups[2].Value);
Suffix = match.Groups[3].Value;
}
public string Prefix { get; private set; }
public int Number { get; private set; }
public string Suffix { get; private set; }
public int CompareTo(object obj)
{
RS rs = (RS)obj;
int result = Prefix.CompareTo(rs.Prefix);
if (result != 0)
return result;
result = Number.CompareTo(rs.Number);
if (result != null)
return result;
return Suffix.CompareTo(rs.Suffix);
}
}
}
You can use this linq query:
var strings = new[] {
"RS01","RS05A","RS10","RS102","RS105A","RS105B","RS32A","RS80"
};
strings = strings.Select(str => new
{
str,
num = int.Parse(String.Concat(str.Skip(2).TakeWhile(Char.IsDigit))),
version = String.Concat(str.Skip(2).SkipWhile(Char.IsDigit))
})
.OrderBy(x => x.num).ThenBy(x => x.version)
.Select(x => x.str)
.ToArray();
DEMO
Result:
RS01
RS05A
RS10
RS32A
RS80
RS102
RS105A
RS105B
You'll want to write a custom comparer class implementing IComparer<string>; it's pretty straightforward to break your strings into components. When you call Array.Sort, give it an instance of your comparer and you'll get the results you want.
I'm trying to construct a lambda expression that will match elements of one array with a second. Below is a simplified version of this query:
class Program
{
static void Main(string[] args)
{
string[] listOne = new string[] { "test1", "test2", "test3" };
MyClass[] listTwo = new MyClass[] { new MyClass("test1") };
string[] newVals = listOne.Where(p => listTwo.Select(e => e.Name).Equals(p)).ToArray();
//string[] newVals2 = listOne.Intersect(listTwo.Select(t => t.Name)).ToArray();
}
class MyClass
{
public MyClass(string name)
{
Name = name;
}
public string Name {get; set;}
}
}
I would expect newVals to return an array of 1 value, but it's empty. I realise that uncommenting myVals2 would achieve the same result, but the lists of classes differ more fundamentally than shown.
You are using Equals but you should use Contains. You are checking wheter IEnumerable<> is equal to p, but you want to check if IEnumerable<> contains p, so replace:
string[] newVals = listOne.
Where(p => listTwo.Select(e => e.Name).Equals(p)).
ToArray();
with
string[] newVals = listOne.
Where(p => listTwo.Select(e => e.Name).Contains(p)).
ToArray();
Try this:
string[] listOne = new string[] { "test1", "test2", "test3" };
MyClass[] listTwo = new MyClass[] { new MyClass("test1") };
string[] newVals = listOne
.Where(p => listTwo.Select(e => e.Name).Contains(p))
.ToArray();
listTwo.Select(e => e.Name) is a IEnumerable<string>
You probably want to perform a Join on the 2 collections.
var q =
listOne
.Join(
listTwo,
l2 => l2,
l1 => l1.Name,
(l2, l1) => new { l2, l1, });
You can change the selector (the last parameter) to suit your needs, if it's just values from listOne for example then have (l2, l1) => l1.
The other solutions will work, but maybe not as you would expect.
Using Linq-Objects Contains within a where clause will cause the entire of listTwo to be iterated for each entry in listOne.
How about something like this:
string[] newVals = listOne.Where(p => listTwo.Any(e => e.Name.Contains(p))).ToArray();
or to be more strict use == instead of Contains.
But if you want to obtain the items that are common between the 2 why not just call .Intersect()??
You are trying to perform a join, technically you are better off simplifying your linq statement to use a join. An example is included below.
static void Main(string[] args)
{
string[] listOne = new [] { "test1", "test2", "test3" };
MyClass[] listTwo = new [] { new MyClass("test1") };
string[] newVals = (from str1 in listOne
join str2 in listTwo.Select(e => e.Name) on str1 equals str2
select str1).ToArray();
foreach (var newVal in newVals)
{
Console.WriteLine(newVal);
}
//string[] newVals2 = listOne.Intersect(listTwo.Select(t => t.Name)).ToArray();
}
class MyClass
{
public MyClass(string name)
{
Name = name;
}
public string Name { get; set; }
}
I have a LINQ to object query to select all the persons that are above 20 years old
IEnumerable<Object> result = null;
result = (from person in AllPersons.ToList()
where person.age > 20
select new
{
FirstName= person.FirstName,
LastName= person.LastName,
Email= person.Email,
PhoneNumber= person.PhoneNumber
});
return result;
I have a parameter string SortProperty I want to use to sort the result based on the property.
So for example if SortProperty="FirstName" I want to sort the result based on the first name.
I tried to do the following:
return result.OrderBy(x => x.GetType().GetProperty(SortProperty));
but it did not work
any idea how to do it?
PS: I don't want to test all the possibilities, and do a if-else on each, or a case switch. I'm looking for an efficient way to do this
Thanks
Check out the Dynamic Linq Extensions Libraries...
It has extension Methods which accept strings instead of Properties.
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
Since your SortProperty is already a string you could do
var result = (from person in AllPersons.ToList()
where person.age > 20
select new
{
FirstName= person.FirstName,
LastName= person.LastName,
Email= person.Email,
PhoneNumber= person.PhoneNumber
}
).OrderBy(SortProperty);
return result;
Also, depending on what AllPersons is, it might not make sense to Enumerate that by calling ToList() until the end. e.g.
var result = (from person in AllPersons
...
).OrderBy(SortProperty).ToList();
Try
return result.OrderBy(x => x.GetType().GetProperty(SortProperty).GetValue(x, null));
return result.OrderBy( x => TypeHelper.GetPropertyValue( x, sortProperty ) )
.ToList();
I'm using something like this :
var sortExpression = #"A,C";
var expressions = sortExpression.Split(new[] { ',' });
var cmpPredicates = new Dictionary<string, Func<Person, Person, int>>(3);
cmpPredicates.Add(#"A", (x, y) => x.A.CompareTo(y.A));
cmpPredicates.Add(#"B", (x, y) => x.B.CompareTo(y.B));
cmpPredicates.Add(#"C", (x, y) => x.C.CompareTo(y.C));
cmpPredicates.Add(#"Default", (x, y) => x.Id.CompareTo(y.Id));
var currentPredicates = new Func<Person, Person, int>[expressions.Length + 1];
for (int i = 0; i < expressions.Length; i++)
{
currentPredicates[i] = cmpPredicates[expressions[i]];
}
// Default sort order
currentPredicates[currentPredicates.Length - 1] = cmpPredicates[#"Default"];
persons.Sort((x, y) =>
{
var cmp = 0;
var index = 0;
while (cmp == 0 && index < currentPredicates.Length)
{
cmp = currentPredicates[index++](x, y);
}
return cmp;
});
where the Person class has the following definition
public class Person
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
public long Id { get; set; }
public Person()
{
this.A = string.Empty;
this.B = string.Empty;
this.C = string.Empty;
}
}
The main benefit is the multiproperty support. With additional checks(duplicates & exist & predicate limit) is can be user provided.