How do I write this in Linq to reduce code? - c#

I'm trying to reduce code, how can I use linq query to reduce number of lines in this method?
private IEnumerable<OutputResponse> GetOutput(IEnumerable<Item> items)
{
foreach (var item in items)
{
var products= item.Products?.Select(x => new ProductName(x));
if (products!= null)
{
foreach (var product in products)
{
yield return new OutputResponse
{
Name = product.Name,
Description = product.Description
};
}
}
}
}

You can try
private IEnumerable<OutputResponse> GetOutput(IEnumerable<Item> items)
{
var results=items.SelectMany(item=>item.Products ?? Enumerable.Empty<Product>())
.Select(p=>new ProductName(p))
.Select(pn=>new OutputResponse {
Name=pn.Name,
Description=pn.Description
});
return results;
}
or
private IEnumerable<OutputResponse> GetOutput(IEnumerable<Item> items)
{
var results =from item in items
from p in item.Products ?? Enumerable.Empty<Product>()
let pn=new ProductName(p)
select new OutputResponse {
Name=pn.Name,
Description=pn.Description
};
return results;
}
As everyone else mentioned though, that ProductName seems to have no purpose.
Writing those queries requires some guessing and assumptions though, eg what each class contains, and what Products contains. I assume the classes involved looks something like this :
class OutputResponse
{
public string Name;
public string Description;
}
class ProductName
{
public string Name;
public string Description;
public ProductName(Product x)=>(Name,Description)=(x.Name,x.Description);
}
class Product
{
public string Name;
public string Description;
}
class Item
{
public List<Product> Products;
}

items.SelectMany(x => x.Products ?? Array.Empty<Product>).
.Select(x =>
{
var product = new ProductName(x);
return new OutputResponse
{
Name = product.Name,
Description = product.Description
};
}
But try to get rid of this ProductName conversion.
If it's really not possible you can also use this:
items.SelectMany(x => x.Products ?? Array.Empty<Product>).
.Select(x => new ProductName(x))
.Select(product =>
new OutputResponse
{
Name = product.Name,
Description = product.Description
});

Depends on how far you want to take it really in my opinion:
private IEnumerable<OutputResponse> GetOutput(IEnumerable<Item> items)
{
var products = items
.SelectMany(x => x.Products
.Where(p => p != null)
.Select(p => new ProductName(p)));
foreach (var product in products)
{
yield return new OutputResponse
{
Name = product.Name,
Description = product.Description
};
}
}
Here you can see that I've encompassed the logic in a linq statement.
I am looping through the items to get the products, looping through products to get the ones that are not null looping again to convert the products into product names (may want to ask yourself if this is nessasary, seeing as the information that you need to create an OutputResponse is on the Product class, could save yourself a potential looping.
Hope this makes sense.

Taking the https://stackoverflow.com/a/59844773/637968 even further:
private IEnumerable<OutputResponse> GetOutput(IEnumerable<Item> items)
{
return items.SelectMany(x => x.Products ?? Array.Empty<Product>)
.Select(x => new ProductName(x))
.Select(p => new OutputResponse() { Name = product.Name, Description = product.Description});
}
It will iterate twice over the whole collection but OP asked for LOC reduction and not performance improvement.

You can try the following,
private static IEnumerable<OutputResponse> GetOutput(IEnumerable<Item> items) {
return items.SelectMany(x => x.Products)?.Select(x=> new OutputResponse() { Name = new ProductName(x).Name, Description = new ProductName(x).Description });
}

Related

How to write a linqu query with this foreach loop

This below is my code and I want to write a linq query for this three list (Dictionaryfilter,collectionfilter,reffrencefilter) this as are mmy list and want to add when item is selected then add into a SelectedIdList ,Using Linq in c#
SelectedIdList = new List<long>();
foreach (var item in DictionariesFilter)
{
if (item.IsSelected)
{
SelectedIdList.Add(item.DictionaryId);
}
}
foreach (var item in CollectionsFilter)
{
if (item.IsSelected)
{
SelectedIdList.Add(item.DictionaryId);
}
}
foreach (var item in RefrencesFilter)
{
if (item.IsSelected)
{
SelectedIdList.Add(item.DictionaryId);
}
}
It could look something like:
SelectedIdList.AddRange(
DictionariesFilter.Where(x=>x.IsSelected).Select(x=>(long)x.DictionaryId)
);
SelectedIdList.AddRange(
CollectionsFilter.Where(x=>x.IsSelected).Select(x=>(long)x.DictionaryId)
);
SelectedIdList.AddRange(
RefrencesFilter.Where(x=>x.IsSelected).Select(x=>(long)x.DictionaryId)
);
You can do like this.
var results1 = from item in DictionariesFilter
where item.IsSelected
select item.DictionaryId;
selectedList.Add(results1);
and in similar way you could do for the rest of loops.
You could try, if possible:
public interface IFilter
{
bool IsSelected { get; }
int DictionaryId { get; }
}
SelectedIdList = new IFilter[] { DictionariesFilter, CollectionsFilter, ReferencesFilter}
.SelectMany(dic => dic.Where(x => x.IsSelected).Select(x = > (long)x.DictionaryId) )
.ToList();
One way of doing this is to simply use Where and Concat.
SelectedIdList = DictionariesFilter.Where(x => x.IsSelected).Select(x => (long)x.DictionaryId)
.Concat(CollectionsFilter.Where(x => x.IsSelected).Select(x => (long)x.DictionaryId))
.Concat(RefrencesFilter.Where(x => x.IsSelected).Select(x => (long)x.DictionaryId))
.ToList();
If they have a common interface it could be simplified.
public interface IFilter
{
bool IsSelected { get; }
long DictionaryId { get; }
}
SelectedIdList = DictionariesFilter
.Concat(CollectionsFilter)
.Concat(RefrencesFilter)
.Where(x => x.IsSelected)
.Select(x => x.DictionaryId)
.ToList();

c# pass class variable as paramater in method LINQ like?

I have a class with a lot of different string values
I have a collection class holding these with some sorting functions. Currently i have lots of reproduced code for "GetListOfDifferentProducts" "GetListOfDifferentManufacturers" etc.
Is this possible? Or am i going about this the wrong way.
These are all simple foreach loops
public List<string> GetListOfDifferentProducts()
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
if (listOfResults.Contains(prod.Name.ToLower()) == false)
listOfResults.Add(prod.Name.ToLower());
}
return listOfResults;
}
I'd like to pass in a class variable (Like LINQ?)
public List<string> GetListOfDifferentVariables(variableType)
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
if (listOfResults.Contains(prod.variableType.ToLower()) == false)
listOfResults.Add(prod.variableType.ToLower());
}
return listOfResults;
}
example Usage:
ProductList.GetListOfDifferentVariables(o => o.Name);
Sample input (Variable string Name)
Apple
Apple
Apple
Pear
Banana
Banana
Output
apple
pear
banana
mvc
class Product
{
public string Name;
public string Manufacturer;
public string Description;
public string Location;
}
class ProductCollection
{
List<Product> listOfProducts;
public List<string> GetListOfDifferentProducts()
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
if (listOfResults.Contains(prod.Name.ToLower()) == false)
listOfResults.Add(prod.Name.ToLower());
}
return listOfResults;
}
}
If you are looking for the distinct set of names, consider:
listOfProducts.Select(z => z.Name.ToLower()).Distinct();
Similarly for variableType:
listOfProducts.Select(z => z.variableType.ToLower()).Distinct();
This will avoid you needing to write explicit GetListOfDifferentVariables etc methods.
If you really want those methods, try something like:
public List<string> GetDistinctNames()
{
return GetDistinctProperties(product => product.Name.ToLower());
}
public List<string> GetDistinctProperties(Func<Product, string> evaluator)
{
return listOfProducts.Select(evaluator).Distinct().ToList();
}
whereby GetDistinctNames passes in the particular field (i.e. Name) and manipulation (i.e. ToLower) it is interested in.
string[] tmp =
{
"Apple",
"Apple",
"Apple",
"Pear",
"Banana",
"Banana"
};
string[] tmp2 =
{
"Pear",
"Banana"
};
IEnumerable<string> uniqueList = tmp.Distinct(); // Removes doubles
IEnumerable<string> notInTmp2 = tmp.Distinct().Except(tmp2); // could be also interesting ;)
Console.WriteLine(string.Join(", ", uniqueList));
Console.WriteLine(string.Join(", ", notInTmp2));
If you want to pass the Property name as a parameter then you can use Reflection.
You will need to slightly tweak your Product class to have public properties.
public class Product
{
private string _name, _manufacturer;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public string Manufacturer
{
get
{
return _manufacturer;
}
set
{
_manufacturer = value;
}
}
}
Then you can write your GetListOfDifferentVariables function as
public List<string> GetListOfDifferentVariables(string propertyName)
{
List<string> listOfResults = new List<string>();
foreach (Product prod in listOfProducts)
{
string propertyValue = (string)prod.GetType().GetProperty(propertyName).GetValue(prod);
if (listOfResults.Contains(propertyValue.ToLower()) == false)
listOfResults.Add(propertyValue.ToLower());
}
return listOfResults.Distinct().ToList();
}
you can call the function using the name of the property
var names = GetListOfDifferentVariables("Name");
var manufacturers = GetListOfDifferentVariables("Manufacturer");
Create general method:
public IEnumerable<T2> GetListOfDifferent<T1, T2>(Func<T1,T2> selector, IEnumerable<T1> inputs)
{
var result = inputs.Select(selector).Distinct();
return result;
}
public IEnumerable<T2> GetListOfDifferent<T1, T2>(string propertyName, IEnumerable<T1> inputs)
{
var paramExp = Expression.Parameter(typeof(T1), "p1");
var member = Expression.PropertyOrField(paramExp, propertyName);
var lamdaExp = Expression.Lambda<Func<T1, T2>>(member, new[] { paramExp });
var selector = lamdaExp.Compile();
var result = GetListOfDifferent(selector, inputs);
return result;
}
Usage:
var result =GetListOfDifferent<Product,string>(product => product.Name, items)
or
var result = GetListOfDifferent<Product,string>(nameof(Product.Name), items);

Creating sub lists with objects of the same attribute from a list using LINQ

I have a Person which looks like:
public class Person {
public string name;
public int age;
}
and I have a method:
public List<List<Person>> createPairedList(List<Person> plist){
List<List<Person>> newList = new List<List<Person>>();
foreach (Person person in plist)
{
bool breaking = false;
for (int i = 0; i<newList.Count(); i++)
{
foreach (Person p in newList[i])
{
if (p.age == person.age)
{
newList[i].Add(person);
breaking = true;
break;
}
}
if (breaking){break;}
}
if (!breaking)
{
newList.Add(new List<Person>(){person});
}
}
return newList;
}
where each item in the sublist has the same age.
So the content would look something like:
[{name="bob", age="20"},
{name="billy", age="21"},
{name="bobby", age="20"},
{name="john", age="21"},
{name="george", age="22"}]
then after this method it would looke like:
[[{name="bob", age="20"},{name="bobby", age="20"}],
[{name="billy", age="21", {name="john", age="21"}}],
[{name="george", age="22"}]]
Is there a way to do this using a LINQ expression?
var newList = list.GroupBy(p => p.age).Select(p => p.ToList()).ToList();
will return List<List<Person>> grouped by age

how to yield an anonymous class in IEnumerable.GroupBy<T> "on the fly" (without enumerating result) in C# LINQ?

I do like this now (thanks to StackOverflow):
IEnumerable<object> Get()
{
var groups = _myDatas.GroupBy(
data => new { Type = data.GetType(), Id = data.ClassId, Value = data.Value },
(key, rows) => new
{
ClassId = key.Id,
TypeOfObject = key.Type,
Value = key.Value,
Count = rows.Count()
}));
foreach (var item in groups)
{
yield return item;
}
}
IEnumerable<MyData> _myDatas;
But is possible to make faster or more "elegant" by not having last foreach loop, but yielding it when the group/anonymous class instance is created?
I would guess fastest way would be to write it open and:
sort the _myDatas
enumerate it and when group changes yield the last group
But I'm trying to learn some LINQ (and C# features in general) so I don't want to do that.
The rest of example is here:
public abstract class MyData
{
public int ClassId;
public string Value;
//...
}
public class MyAction : MyData
{
//...
}
public class MyObservation : MyData
{
//...
}
You should be able to return groups directly, though you might need to change your return type from IEnumerable<Object> to just IEnumerable.
So:
IEnumerable Get()
{
var groups = _myDatas.GroupBy(
// Key selector
data => new {
Type = data.GetType(),
Id = data.ClassId,
Value = data.Value
},
// Element projector
(key, rows) => new
{
ClassId = key.Id,
TypeOfObject = key.Type,
Value = key.Value,
Count = rows.Count()
}
);
return groups;
}
groups has the type IEnumerable< IGrouping< TKey = Anonymous1, TElement = Anonymous2 > >, so you can return it directly.

how to display unique record in combobox in C#

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();

Categories

Resources