Best way to handle redundant code that has repeated logic? - c#

In my form I have four RadioButtons, based on user selection, this code is executed:
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
if (radioButtonName.Checked)
{
var Qr = from n in mylist where n.Name == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
if (radioButtonAge.Checked)
{
var Qr = from n in mylist where n.Age == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
if (radioButtonGender.Checked)
{
var Qr = from n in mylist where n.Gender == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
if (radioButtonOccupation.Checked)
{
var Qr = from n in mylist where n.Occu == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
}
The code seems very redundant and repeated, but also I can't find a way to handle all 4 RadioButtons in a single line that has only one variable linked to user choice.
myList is a List of a class I created that has 4 string properties (Name, Age, Gender, Occu)

wrap everything in a function like this one:
public void foo(RadioButton radioButton, Expression<Func<MyItem, bool>> expression)
{
if (radioButton.Checked)
{
var Qr = mylist.AsQueryable().Where(expression).Select(x => String.Format("Name: {0}, Age: {1}, Occ: {2}, Gender: {3}", x.Name, x.Age, x.Occu, x.Gender)).ToList();
foreach (var item in Qr)
{
listBox1.Items.Add(item);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
foo(radioButtonName, c => c.Gender == textBoxSearch.Text);
foo(radioButtonAge, c => c.Age == textBoxSearch.Text);
foo(radioButtonGender, c => c.Gender == textBoxSearch.Text);
foo(radioButtonOccupation, c => c.Occu == textBoxSearch.Text);
}
public class MyItem
{
public String Occu { get; set; }
public String Age { get; set; }
public String Name { get; set; }
public String Gender { get; set; }
}

The only difference is in filter (where) all the other can be combined:
private void button1_Click(object sender, EventArgs e) {
var lines = mylist
.Where(item => radioButtonName.Checked && item.Name == textBoxSearch.Text ||
radioButtonAge.Checked && item.Age == textBoxSearch.Text ||
radioButtonGender.Checked && item.Gender == textBoxSearch.Text ||
radioButtonOccupation.Checked && item.Occu == textBoxSearch.Text)
.Select(item => string.Format("Name: {0} Age: {1} Occupation: {2} Gender: {3}",
item.Name, item.Age, item.Occu, item.Gender));
listBox1.Items.Clear();
foreach (string line in lines)
listBox1.Items.Add(line);
}

You can use a dictionary to map the RadioButton to its Filter in one go. Assuming MyClass is the type of objects in your List:
private void button1_Click(object sender, EventArgs e)
{
var mapping = new Dictionary<RadioButton, Func<MyClass, bool>>()
{
{ radioButtonName , x => x.Name == textBoxSearch.Text },
{ radioButtonAge, x => x.Age == textBoxSearch.Text },
{ radioButtonGender, x => x.Gender == textBoxSearch.Text},
{ radioButtonOccupation, x => x.Occu == textBoxSearch.Text}
};
foreach(var map in mapping.Where(x=> x.Key.Checked))
{
var Qr = mylist.Where(map.Value).Select(n=> new {n.Name, n.Age, n.Occu, n.Gender});
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " "
+ " Occupation: " + item.Occu + " " + " Gender: "
+ item.Gender);
}
}
}
This way you can easily plug new Radio Buttons using one simple line in the dictionary.

You could first generate an anonymous list of your radio buttons and where predictions and then iterate through it (MyItem in this case is sample/placeholder for what your list contains, since I don't know the actual class name):
private void button1_Click(object sender, EventArgs e)
{
// Generate anonymous list of objects that are different
var radios = new[]
{
new { RadioButton = radioButtonName, CallBack = new Func<MyItem, bool>(x => x.Name == textBoxSearch.Text) },
new { RadioButton = radioButtonAge, CallBack = new Func<MyItem, bool>(x => x.Age == textBoxSearch.Text) },
new { RadioButton = radioButtonGender, CallBack = new Func<MyItem, bool>(x => x.Occu == textBoxSearch.Text) },
new { RadioButton = radioButtonOccupation, CallBack = new Func<MyItem, bool>(x => x.Gender == textBoxSearch.Text) },
};
// Iterate through list and add items to ListBox1, if RadioButtton is checked
listBox1.Items.Clear();
foreach (var radio in radios)
{
if (!radio.RadioButton.Checked)
{
continue;
}
var Qr = mylist.Where(radio.CallBack).Select(n => new { n.Name, n.Age, n.Occu, n.Gender });
foreach (var item in Qr)
{
listBox1.Items.Add($"Name: {item.Name} Age: {item.Age} Occupation: {item.Occu} Gender: {item.Gender}");
}
}
}

Related

how to allow the user to choose how to sort his list using a variable?

the information that the user enters can be sorted in many ways ascending and descending and i'm trying to make the user choose how he want to see the data:
so is there any way to set a variable that the user enter and sort the data without repeating the code multiple time depending on the input like:
var empName = el.Select(i => new { i.ID, i.FullName });
if(emsort.Text="text1")
empName.OrderBy(i.text1);
else if...
by doing something shorter like:
string sort=emsort.Text ;
empName.OrderBy(sort);
If I understood your problem right, that you want to sort dynamically with the property name which is stored in emsort.Text, then you can use Expressions:
assuming empName is IEnumerable<Employee>, then use this:
private static Func<Employee, dynamic> GetSortable(string sortablePoperty)
{
var param = Expression.Parameter(typeof(Employee), "e");
var member = Expression.Property(param, sortablePoperty);
Expression memberAsObject = Expression.Convert(member, typeof(object));
return Expression.Lambda<Func<Employee, dynamic>>(memberAsObject, param).Compile();
}
then use it:
string sort=emsort.Text ;
empName.OrderBy(GetSortable(sort));
if empName is IQueryable<Employee>, then use this:
private static Expression<Func<Employee, dynamic>> GetSortable(string sortablePoperty)
{
var param = Expression.Parameter(typeof(Employee), "e");
var member = Expression.Property(param, sortablePoperty);
Expression memberAsObject = Expression.Convert(member, typeof(object));
return Expression.Lambda<Func<Employee, dynamic>>(memberAsObject, param);
}
If the User in your question is some software that calls your function, then the user is aware of the type of the objects in the el sequence: he knows which properties the elements in the el sequence are, and he knows how he wants them ordered.
Why not let this user give you the keySelector to use in the OrderBy?
Creating an extension function:
static class QueryableExtensions
{
IQueryable<MyType> SelectAndSort<TSource, TResult>(this IQueryable<TSource source,
Expression<Func<TSource, TResult>> selector,
Expression<Func<TSource, TKey>> sortKeySelector,
System.ComponentModel.ListSortDirection sortDirection)
{
var selectBeforeOrdering = myRepository.el.Select(selector);
// note: this is still an IQueryable, only the query has been made,
// the database is not accessed yet!
IQueryable<TResult> result = (sortDirection == ListSortDirectrion.Ascending) ?
// sort in ascending order:
selectBeforeOrdering.OrderBy(sortKeySelector) :
// else: selec in descending order:
selectBeforeOrdering.OrderByDescending(sortKeySelector);
return result;
}
}
Usage: suppose your user knows your el, and he want several fields of it, ordered by one of the properties of your el
using (var myDbContext = new DbContext(...))
{
IQueryable myEls = myDbContext.el;
var mySelectedItems = myEls.SelectAndSore(
// selector: what properties do I as a user want?
el => new
{
Id = el.Id,
FullName = el.FullName,
... // other desired properties
},
// sortSelector: how do I want my selected items to be sorted?
selectedItem => selectedItem.FullName,
// direction:
ListSortDirection.Descending);
// note: until now the database has not been accessed
// only the Expression of the query has been created.
// depending on what you want as a result: ToList / ToArray / First / ...
return mySelectedItems.ToList();
}
If, on the other hand your user is not software, but an operator, who has to select by which column he wants his items to be ordered, he has to have a method to tell the computer which column should be sorted.
This is usually done by clicking the column. Another method could be by selecting a value in a combo box. Anyway, you'll have to attach something to the column or to the combobox values that holds the sortKeySelector:
class MySortableColumn<TDisplayedProperty> : DataGridViewColumn
// or whatever column class you are using
{
public Expression<Func<MyDisplayedItem, TDisplayedProperty>> SortKeySelector{get;set;}
}
Now you have several columns that each display one of the properties of MyDisplayedItem:
var columnId = new MySortableColumn<int>()
{
SortKeySelector = displayedItem => myDisplayedItem.Id,
};
var columnFullName = new MyStortableColumn<string>()
{
SortKeySelector = displayedItem => myDisplayedItem.Id,
}
// etc.
Whenever the operator clicks on a column, the sortKey is extracted and used as parameter to sort the items:
void SortColumn(SortableColumn column)
{
var sortedItems = Sort(column.SortKeySelector, ListSortOrder...);
Display(sortedItems);
}
As I assume, el is materialized(i.e., for example, it is not just entry point to database), so you can use reflection:
var sort = emsort.Text;
PropertyInfo property = null;
var sortedData = el.Select(i => new { i.ID, i.FullName })
.OrderBy(x =>
{
property = property ?? x.GetType().GetProperty(sort);
return property.GetValue(x);
}).ToList();
this is a part of the code that i had to write to do it :
if (emord.Text == "Ascending")
{
if (emsort.Text == "ID")
{
var empName = el.Select(i => new { i.ID, i.FullName }).OrderBy(x => x.ID);
var empNamenAmp = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per }).OrderBy(x => x.ID);
var empNamenAmpnwh = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per, i.Hours }).OrderBy(x => x.ID);
var empNamenwh = el.Select(i => new { i.ID, i.FullName, i.Hours }).OrderBy(x => x.ID);
var empNamenbd = el.Select(i => new { i.ID, i.FullName, i.Date }).OrderBy(x => x.ID);
var empNamenad = el.Select(i => new { i.ID, i.FullName, i.Location }).OrderBy(x => x.ID);
var empNamenpn = el.Select(i => new { i.ID, i.FullName, i.PhoneNb }).OrderBy(x => x.ID);
var empNamena = el.Select(i => new { i.ID, i.FullName, i.Age }).OrderBy(x => x.ID);
if (empfilcomb.Text == "Name")
{
dispfil.Clear();
foreach (var x in empName)
dispfil.Text += x.ID + ", " + x.FullName + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Amount paid")
{
dispfil.Clear();
foreach (var x in empNamenAmp)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name,Amount paid and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenAmpnwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Birthday")
{
dispfil.Clear();
foreach (var x in empNamenbd)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Date + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Address")
{
dispfil.Clear();
foreach (var x in empNamenad)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Location + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Phone Number")
{
dispfil.Clear();
foreach (var x in empNamenpn)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.PhoneNb + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Age")
{
dispfil.Clear();
foreach (var x in empNamena)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Age + Environment.NewLine;
}
}
else if (emsort.Text == "Name")
{
var empName = el.Select(i => new { i.ID, i.FullName }).OrderBy(x => x.FullName);
var empNamenAmp = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per }).OrderBy(x => x.FullName);
var empNamenAmpnwh = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per, i.Hours }).OrderBy(x => x.FullName);
var empNamenwh = el.Select(i => new { i.ID, i.FullName, i.Hours }).OrderBy(x => x.FullName);
var empNamenbd = el.Select(i => new { i.ID, i.FullName, i.Date }).OrderBy(x => x.FullName);
var empNamenad = el.Select(i => new { i.ID, i.FullName, i.Location }).OrderBy(x => x.FullName);
var empNamenpn = el.Select(i => new { i.ID, i.FullName, i.PhoneNb }).OrderBy(x => x.FullName);
var empNamena = el.Select(i => new { i.ID, i.FullName, i.Age }).OrderBy(x => x.FullName);
if (empfilcomb.Text == "Name")
{
dispfil.Clear();
foreach (var x in empName)
dispfil.Text += x.ID + ", " + x.FullName + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Amount paid")
{
dispfil.Clear();
foreach (var x in empNamenAmp)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name,Amount paid and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenAmpnwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Birthday")
{
dispfil.Clear();
foreach (var x in empNamenbd)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Date + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Address")
{
dispfil.Clear();
foreach (var x in empNamenad)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Location + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Phone Number")
{
dispfil.Clear();
foreach (var x in empNamenpn)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.PhoneNb + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Age")
{
dispfil.Clear();
foreach (var x in empNamena)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Age + Environment.NewLine;
}
}
and i was asking if there is a shorter way to do it:withing what i'm allowed to use. Seems that there isn't but thanks a lot for the help i'm sure what you guys provided is useful.

Orderby not working

I am sorting on multiple criteria using dynamic sorting, here's my code:
public ActionResult WebGrid(int page = 1, int rowsPerPage = 10, string sortCol = "OrderID", string sortDir = "ASC", string sortSecCol = "OrderID", string sortSecDir = "ASC")
{
List<Orders> res;
using (var nwd = new NorthwindEntities())
{
var _res = nwd.Orders
.AsQueryable()
.OrderBy(sortCol + " " + sortDir, sortSecCol + " " + sortSecDir)
.Skip((page - 1) * rowsPerPage)
.Take(rowsPerPage)
.Select(o => new Orders
{
What I am trying to do in here is I want the column OrderID be the secondary sort whenever it is not a primary sort, but it didn't work when I actually selected other column as a primary sort.
In order words, when other column is select as primary sort in descending order, OrderID should also be in descending order, I am not sure what did I missed in my code.
The OrderBy method I used is come from here (MSDN).
The method you are using has the following signature:
public static IQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string ordering,
params object[] values
)
so the sortSecCol + " " + sortSecDir goes to values argument, while the whole ordering is supposed to be provided as comma separated list in the ordering argument.
You can use something like this instead:
var ordering = sortCol + " " + sortDir;
if (sortSecCol != sortCol)
ordering += ", " + sortSecCol + " " + sortSecDir;
...
.OrderBy(ordering)
...
try this:
if (sortCol == "OrderID" && sortDir == "ASC")
{
_res = l.OrderBy(x => x.OrderId).ThenBy(x => x.AnotherField);
}
if (sortCol == "OrderID" && sortDir == "DESC")
{
_res = l.OrderByDescending(x => x.OrderId).ThenByDescending(x => x.AnotherField);
}
if (sortCol == "AnotherField" && sortDir == "ASC")
{
_res = l.OrderBy(x => x.AnotherField).ThenBy(x => x.OrderId);
}
if (sortCol == "AnotherField" && sortDir == "DESC")
{
_res = l.OrderByDescending(x => x.AnotherField).ThenByDescending(x => x.OrderId);
}
_res.Skip((page - 1) * rowsPerPage)
.Take(rowsPerPage)
.Select(o => new Orders
{

compare two list of objects using linq dynamically

Click to see the Output:
I have an Employee class
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string Age { get; set; }
public string Address { get; set; }
public string ContactNo { get; set; }
}
and have fill method to fill lists
private static void FillEmployeeList(ref List<Employee> lt1, ref List<Employee> lt2)
{
lt1 = new List<Employee> {new Employee{ID=1,Name="Kavya",Age="24",Address="No.1,Nehru Street,Chennai",ContactNo="9874521456"},
new Employee{ID=2,Name="Ravi",Age="24",Address="Flat No.25/A1,Gandhi Street,Chennai",ContactNo="9658745258"},
new Employee{ID=3,Name="Lavnya",Age="30",Address="No.12,Shastri nagar,Chennai",ContactNo="5214587896"},
new Employee{ID=4,Name="Rupa",Age="31",Address="No.23/5,Nehru Street,Chennai",ContactNo="9874521256"},
new Employee{ID=5,Name="Divya",Age="32",Address="No.1/227,Nehru Street,Chennai",ContactNo="8541256387"},
};
lt2 = new List<Employee> {new Employee{ID=1,Name="Kavya",Age="24",Address="No.1,Nehru Street,Chennai",ContactNo="9874521456"},
new Employee{ID=2,Name="Ravindran",Age="30",Address="Flat No.25/A1,Gandhi Street,Chennai",ContactNo="9658745258"},
new Employee{ID=3,Name="Chandru",Age="30",Address="No.12,Shastri nagar,Chennai",ContactNo="5214587896"},
new Employee{ID=4,Name="Rakesh",Age="32",Address="No.23/5,Nehru Street,Chennai",ContactNo="9874021256"},
new Employee{ID=5,Name="Suresh",Age="32",Address="No.1/227,Nehru Street,Chennai",ContactNo="8541056387"},
new Employee{ID=11,Name="Suryakala",Age="28",Address="No.1,Pillayar koil Street,Chennai",ContactNo="9541204782"},
new Employee{ID=12,Name="Thivya",Age="41",Address="No.42,Ellaiamman koil Street,Chennai",ContactNo="9632140874"},
};
}
Comparing two list of objects
protected List<Employee> ListCompare(List<Employee> lt1, List<Employee> lt2)
{
FillEmployeeList(ref lt1, ref lt2);
List<Employee> lst = new List<Employee>();
if (lt1.Count > 0 && lt2.Count > 0)
{
// Displaying Matching Records from List1 and List2 by ID
var result = (from l1 in lt1
join l2 in lt2
on l1.ID equals l2.ID
orderby l1.ID
select new
{
ID = l1.ID,
Name = (l1.Name == l2.Name) ? "$" : (l2.Name + " (Modified)"),
Age = (l1.Age == l2.Age) ? "$" : (l2.Age + " (Modified)"),
Address = (l1.Address == l2.Address) ? "$" : (l2.Address + " (Modified)"),
ContactNo = (l1.ContactNo == l2.ContactNo) ? "$" : (l2.ContactNo + " (Modified)")
}).ToList();
// Displaying Records from List1 which is not in List2
var result1 = from l1 in lt1
where !(from l2 in lt2
select l2.ID).Contains(l1.ID)
orderby l1.ID
select new
{
ID = l1.ID,
Name = " Deleted",
Age = " Deleted",
Address = " Deleted",
ContactNo = " Deleted"
};
// Displaying Records from List1 which is not in List2
var result2 = from l1 in lt2
where !(from l2 in lt1
select l2.ID).Contains(l1.ID)
orderby l1.ID
select new
{
ID = l1.ID,
Name = l1.Name + " (Added)",
Age = l1.Age + " (Added)",
Address = l1.Address + " (Added)",
ContactNo = l1.ContactNo + " (Added)"
};
var res1 = result.Concat(result1).Concat(result2);
foreach (var item in res1)
{
Employee emp = new Employee();
//Response.Write(item + "<br/>");
emp.ID = item.ID;
emp.Name = item.Name;
emp.Age = item.Age;
emp.Address = item.Address;
emp.ContactNo = item.ContactNo;
lst.Add(emp);
}
}
return lst;
}
Here I am calling compareList method and returns the result and displaying it on the html table.
List<Employee> lt1 = new List<Employee>();
List<Employee> lt2 = new List<Employee>();
List<Employee> resultset = new List<Employee>();
//string value = "ID";
StringBuilder htmlTable = new StringBuilder();
htmlTable.Append("<table border='1'>");
htmlTable.Append("<tr><th>ID</th><th>Name</th><th>Age</th><th>Address</th><th>ContactNo</th></tr>");
resultset = ListCompare(lt1, lt2);
foreach(var item in resultset)
{
htmlTable.Append("<tr>");
htmlTable.Append("<td>" + item.ID + "</td>");
htmlTable.Append("<td>" + item.Name + "</td>");
htmlTable.Append("<td>" + item.Age + "</td>");
htmlTable.Append("<td>" + item.Address + "</td>");
htmlTable.Append("<td>" + item.ContactNo + "</td>");
htmlTable.Append("</tr>");
}
htmlTable.Append("</table>");
PlaceHolder1.Controls.Add(new Literal { Text = htmlTable.ToString() });
My question is how to generalize this coding. I may have any class(like Employee or Student). I want the coding just i will pass the two lists of objects to CompareMethod(to compare method i will pass any type of list of objects) which will return the list as result. How to proceed, pls give any idea.
If you are interesting in collection equality, I would like to recomend next stuff:
Enumerable.SequenceEqual
Determines whether two sequences are equal by comparing their elements by using a specified IEqualityComparer. MSDN
in case of unit testing:
CollectionAssert.AreEquivalent
Verifies that two specified collections are equivalent. The assertion fails if the collections are not equivalent. MSDN
if you need to know about difference:
Enumerable.Except
Produces the set difference of two sequences. MSDN
You can create your compare method as the following:
protected List<TResult> ListCompare<TKey, TInput, TResult>(List<TInput> lt1, List<TInput> lt2, Func<TInput, TKey> key, Func<TInput, TInput, TResult> modified, Func<TInput, TResult> added, Func<TInput, TResult> deleted)
{
// Displaying Matching Records from List1 and List2 by ID
var matchingEmployees = lt1.Join(lt2, key, key, modified);
// Displaying Records from List1 which is not in List2
var lt1NotInlt2 = lt1
.Where(e1 => !lt2.Any(e2 => key(e2).Equals(key(e1))))
.Select(deleted);
// Displaying Records from List2 which is not in List1
var lt2NotInlt1 = lt2
.Where(e2 => !lt1.Any(e1 => key(e1).Equals(key(e2))))
.Select(added);
return matchingEmployees.Concat(lt1NotInlt2).Concat(lt2NotInlt1).ToList();
}
If you don't want to use the key, then your compare method should look like this and your class should implement the Equals method:
protected List<TResult> ListCompare<TInput, TResult>(List<TInput> lt1, List<TInput> lt2, Func<TInput, TInput, TResult> modified, Func<TInput, TResult> added, Func<TInput, TResult> deleted)
{
// Displaying Matching Records from List1 and List2 by ID
var matchingEmployees = lt1
.Where(e1 => lt2.Any(e2 => e2.Equals(e1)))
.Select(e1 =>
{
var e2 = lt2.First(e => e.Equals(e1));
return modified(e1, e2);
});
// Displaying Records from List1 which is not in List2
var lt1NotInlt2 = lt1
.Where(e1 => !lt2.Any(e2 => e2.Equals(e1)))
.Select(deleted);
// Displaying Records from List2 which is not in List1
var lt2NotInlt1 = lt2
.Where(e2 => !lt1.Any(e1 => e1.Equals(e2)))
.Select(added);
return matchingEmployees.Concat(lt1NotInlt2).Concat(lt2NotInlt1).ToList();
}
Then you can call the compare method like this:
var result = ListCompare<int, Employee, Employee>(
lt1,
lt2,
e => e.ID,
(e1, e2) => new Employee
{
ID = e1.ID,
Name = (e1.Name == e2.Name) ? "$" : (e2.Name + " (Modified)"),
Age = (e1.Age == e2.Age) ? "$" : (e2.Age + " (Modified)"),
Address = (e1.Address == e2.Address) ? "$" : (e2.Address + " (Modified)"),
ContactNo = (e1.ContactNo == e2.ContactNo) ? "$" : (e2.ContactNo + " (Modified)")
},
e => new Employee
{
ID = e.ID,
Name = e.Name + " (Added)",
Age = e.Age + " (Added)",
Address = e.Address + " (Added)",
ContactNo = e.ContactNo + " (Added)"
},
e => new Employee
{
ID = e.ID,
Name = " Deleted",
Age = " Deleted",
Address = " Deleted",
ContactNo = " Deleted"
});

LINQ - GROUP BY IEnumerable

Using LINQ, how can I group this list results by firstThree? My group method is not working.
var findOtherZipCodes = (from z in zipCodes
from results in zipFirstThree
where (results.region.ToUpper() == z.City
&& results.state == z.State)
select new ZipCodeFirstThree
{
region = z.City,
state = z.State,
firstThree = z.ZipCode1.ToString().Substring(0, 3),
zipCode = z.ZipCode1.ToString()
}).ToList();
findOtherZipCodes.GroupBy(x => x.firstThree).ToList();
foreach (var item in findOtherZipCodes) {
Console.WriteLine(item.region + " " + item.firstThree + " " + item.zipCode);
Thread.Sleep(500);
}
The reason your GroupBy is not working is that you are ignoring its results. It should be like this:
var groups = findOtherZipCodes.GroupBy(x => x.firstThree).ToList();
Change the foreach loop like this to complete the fix:
foreach (var group in groups) {
var firstInGroup = group.First();
Console.WriteLine(firstInGroup.region + " " + group.Key + " " + firstInGroup.zipCode);
Thread.Sleep(500);
}

LINQ list to sentence format (insert commas & "and")

I have a linq query that does something simple like:
var k = people.Select(x=>new{x.ID, x.Name});
I then want a function or linq lambda, or something that will output the names in sentence format using commas and "ands".
{1, John}
{2, Mark}
{3, George}
to
"1:John, 2:Mark and 3:George"
I'm fine with hardcoding the ID + ":" + Name part, but it could be a ToString() depending on the type of the linq query result. I'm just wondering if there is a neat way to do this with linq or String.Format().
public string ToPrettyCommas<T>(
List<T> source,
Func<T, string> stringSelector
)
{
int count = source.Count;
Func<int, string> prefixSelector = x =>
x == 0 ? "" :
x == count - 1 ? " and " :
", ";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < count; i++)
{
sb.Append(prefixSelector(i));
sb.Append(stringSelector(source[i]));
}
string result = sb.ToString();
return result;
}
Called with:
string result = ToPrettyCommas(people, p => p.ID.ToString() + ":" + p.Name);
Why Linq?
StringBuilder sb = new StringBuilder();
for(int i=0;i<k.Count();i++)
{
sb.Append(String.Format("{0}:{1}", k[i].ID, k[i].Name);
if(i + 2 < k.Count())
sb.Append(", ");
else if(i + 1 < k.Count())
sb.Append(" and ");
}
Really, all Linq will let you do is hide the loop.
Also, make sure you do or do not want the "Oxford Comma"; this algorithm will not insert one, but a small change will (append the comma and space after every element except the last, and also append "and " after the next-to-last).
Just for fun, here’s something that really uses functional LINQ — no loop and no StringBuilder. Of course, it’s pretty slow.
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" } };
var resultAggr = list
.Select(item => item.ID + ":" + item.Name)
.Aggregate(new { Sofar = "", Next = (string) null },
(agg, next) => new { Sofar = agg.Next == null ? "" :
agg.Sofar == "" ? agg.Next :
agg.Sofar + ", " + agg.Next,
Next = next });
var result = resultAggr.Sofar == "" ? resultAggr.Next :
resultAggr.Sofar + " and " + resultAggr.Next;
// Prints 1:John, 2:Mark and 3:George
Console.WriteLine(result);
Much like the rest, this isn't better than using a string builder, but you can go (ignoring the ID, you can add it in):
IEnumerable<string> names = new[] { "Tom", "Dick", "Harry", "Abe", "Bill" };
int count = names.Count();
string s = String.Join(", ", names.Take(count - 2)
.Concat(new [] {String.Join(" and ", names.Skip(count - 2))}));
This approach pretty much abuses Skip and Take's ability to take negative numbers, and String.Join's willingness to take a single parameter, so it works for one, two or more strings.
Using the Select operation that gives you an index, this can be written as a ONE LINE extension method:
public static string ToAndList<T>(this IEnumerable<T> list, Func<T, string> formatter)
{
return string.Join(" ", list.Select((x, i) => formatter(x) + (i < list.Count() - 2 ? ", " : (i < list.Count() - 1 ? " and" : ""))));
}
e.g.
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" } }.ToList();
Console.WriteLine(list.ToAndList(x => (x.ID + ": " + x.Name)));
Improving(hopefully) on KeithS's answer:
string nextBit = "";
var sb = new StringBuilder();
foreach(Person person in list)
{
sb.Append(nextBit);
sb.Append(", ");
nextBit = String.Format("{0}:{1}", person.ID, person.Name);
}
sb.Remove(sb.Length - 3, 2);
sb.Append(" and ");
sb.Append(nextBit);
This is not pretty but will do the job using LINQ
string s = string.Join(",", k.TakeWhile(X => X != k.Last()).Select(X => X.Id + ":" + X.Name).ToArray()).TrimEnd(",".ToCharArray()) + " And " + k.Last().Id + ":" + k.Last().Name;
Y'all are making it too complicated:
var list = k.Select(x => x.ID + ":" + x.Name).ToList();
var str = list.LastOrDefault();
str = (list.Count >= 2 ? list[list.Count - 2] + " and " : null) + str;
str = string.Join(", ", list.Take(list.Count - 2).Concat(new[]{str}));
How about this?
var k = people.Select(x=>new{x.ID, x.Name});
var stringified = people
.Select(x => string.Format("{0} : {1}", x.ID, x.Name))
.ToList();
return string.Join(", ", stringified.Take(stringified.Count-1).ToArray())
+ " and " + stringified.Last();
I have refined my previous answer and I believe this is the most elegant solution yet.
However it would only work on reference types that don't repeat in the collection (or else we'd have to use different means for finding out if item is first/last).
Enjoy!
var firstGuy = guys.First();
var lastGuy = guys.Last();
var getSeparator = (Func<Guy, string>)
(guy => {
if (guy == firstGuy) return "";
if (guy == lastGuy) return " and ";
return ", ";
});
var formatGuy = (Func<Guy, string>)
(g => string.Format("{0}:{1}", g.Id, g.Name));
// 1:John, 2:Mark and 3:George
var summary = guys.Aggregate("",
(sum, guy) => sum + getSeparator(guy) + formatGuy(guy));
This can be the way you can achieve your goal
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" }
}.ToList();
int i = 0;
string str = string.Empty;
var k = list.Select(x => x.ID.ToString() + ":" + x.Name + ", ").ToList();
k.ForEach(a => { if (i < k.Count() - 1) { str = str + a; } else { str = str.Substring(0, str.Length -2) + " and " + a.Replace("," , ""); } i++; });
Here's a method that doesn't use LINQ, but is probably as efficient as you can get:
public static string Join<T>(this IEnumerable<T> list,
string joiner,
string lastJoiner = null)
{
StringBuilder sb = new StringBuilder();
string sep = null, lastItem = null;
foreach (T item in list)
{
if (lastItem != null)
{
sb.Append(sep);
sb.Append(lastItem);
sep = joiner;
}
lastItem = item.ToString();
}
if (lastItem != null)
{
if (sep != null)
sb.Append(lastJoiner ?? joiner);
sb.Append(lastItem);
}
return sb.ToString();
}
Console.WriteLine(people.Select(x => x.ID + ":" + x.Name).Join(", ", " and "));
Since it never creates a list, looks at an element twice, or appends extra stuff to the StringBuilder, I don't think you can get more efficient. It also works for 0, 1, and 2 elements in the list (as well as more, obviously).
StringBuilder Approach
Here's an Aggregate with a StringBuilder. There's some position determinations that are made to clean up the string and insert the "and" but it's all done at the StringBuilder level.
var people = new[]
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mark" },
new { Id = 3, Name = "George" }
};
var sb = people.Aggregate(new StringBuilder(),
(s, p) => s.AppendFormat("{0}:{1}, ", p.Id, p.Name));
sb.Remove(sb.Length - 2, 2); // remove the trailing comma and space
var last = people.Last();
// index to last comma (-2 accounts for ":" and space prior to last name)
int indexComma = sb.Length - last.Id.ToString().Length - last.Name.Length - 2;
sb.Remove(indexComma - 1, 1); // remove last comma between last 2 names
sb.Insert(indexComma, "and ");
// 1:John, 2:Mark and 3:George
Console.WriteLine(sb.ToString());
A String.Join approach could have been used instead but the "and" insertion and comma removal would generate ~2 new strings.
Regex Approach
Here's another approach using regex that is quite understandable (nothing too cryptic).
var people = new[]
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mark" },
new { Id = 3, Name = "George" }
};
var joined = String.Join(", ", people.Select(p => p.Id + ":" + p.Name).ToArray());
Regex rx = new Regex(", ", RegexOptions.RightToLeft);
string result = rx.Replace(joined, " and ", 1); // make 1 replacement only
Console.WriteLine(result);
The pattern is simply ", ". The magic lies in the RegexOptions.RightToLeft which makes the match occur from the right and thereby makes the replacement occur at the last comma occurrence. There is no static Regex method that accepts the number of replacements with the RegexOptions, hence the instance usage.
Here's one using a slightly modified version of my answer to Eric Lippert's Challenge which is IMHO the most concise with easy to follow logic (if you're familiar with LINQ).
static string CommaQuibblingMod<T>(IEnumerable<T> items)
{
int count = items.Count();
var quibbled = items.Select((Item, index) => new { Item, Group = (count - index - 2) > 0})
.GroupBy(item => item.Group, item => item.Item)
.Select(g => g.Key
? String.Join(", ", g)
: String.Join(" and ", g));
return String.Join(", ", quibbled); //removed braces
}
//usage
var items = k.Select(item => String.Format("{0}:{1}", item.ID, item.Name));
string formatted = CommaQuibblingMod(items);
static public void Linq1()
{
var k = new[] { new[] { "1", "John" }, new[] { "2", "Mark" }, new[] { "3", "George" } };
Func<string[], string> showPerson = p => p[0] + ": " + p[1];
var res = k.Skip(1).Aggregate(new StringBuilder(showPerson(k.First())),
(acc, next) => acc.Append(next == k.Last() ? " and " : ", ").Append(showPerson(next)));
Console.WriteLine(res);
}
could be optimized by moving k.Last() computation to before the loop
public static string ToListingCommaFormat(this List<string> stringList)
{
switch(stringList.Count)
{
case 0:
return "";
case 1:
return stringList[0];
case 2:
return stringList[0] + " and " + stringList[1];
default:
return String.Join(", ", stringList.GetRange(0, stringList.Count-1))
+ ", and " + stringList[stringList.Count - 1];
}
}
This is the method is faster than the 'efficient' Join method posted by Gabe. For one and two items, it is many times faster, and for 5-6 strings, it is about 10% faster. There is no dependency on LINQ. String.Join is faster than StringBuilder for small arrays, which are typical for human-readable text. In grammar, these are called listing commas, and the last comma should always be included to avoid ambiguity. Here is the resulting code:
people.Select(x=> x.ID.ToString() + ":" + x.Name).ToList().ToListingCommaFormat();
There are ways to optimize this since it isn't very efficient, but something like this may work:
var k = people.Select(x => new {x.ID, x.Name}).ToList();
var last = k.Last();
k.Aggregate(new StringBuilder(), (sentence, item) => {
if (sentence.Length > 0)
{
if (item == last)
sentence.Append(" and ");
else
sentence.Append(", ");
}
sentence.Append(item.ID).Append(":").Append(item.Name);
return sentence;
});

Categories