I have a list for example List<string> ListProviderKeys that has some values in it.
I also have a second list from a class below, for example List<ChangesSummary> SecondList;
public class ChangesSummary
{
public string TableName { get; set; }
public string ProviderKey { get; set; }
public string ProviderAdrsKey { get; set; }
public string ProviderSpecialtyKey { get; set; }
public string FieldName{ get; set; }
}
Imagine the values that first list holds is the same kind of values we put in ProviderKey field in the second list.
Now What I want is to trim down the second list to only have values that their ProviderKey IS NOT already in the first list.
How Can I do that? I know the operator Except but not sure how to apply it in this situation!
The best I can think of is :
A) Create dictionary and use its fast lookups
B) Use LINQ .Where method with .ContainsKey() on this dictionary which internally uses Hashtable and performs quick lookups.
This should reduce search complexity to almost O(1) rather than O(N) ro worse (when we use LINQ .Where() with .Any() or .Contains() and that leads to nested loops).
From MSDN page :
The Dictionary generic class provides a mapping from a set of keys to
a set of values. Each addition to the dictionary consists of a value
and its associated key. Retrieving a value by using its key is very
fast, close to O(1), because the Dictionary class is implemented as a
hash table.
So what we can do is :
Dictionary<string, string> dict = ListProviderKeys.ToDictionary(s => s);
var newList = SecondList.Where(e => !dict.ContainsKey(e.ProviderKey)).ToList();
Here is a very simple, short, but complete example illustrating it and also testing its performance :
class Person
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<int> ints = new List<int>();
List<Person> People = new List<Person>(1000);
for (int i = 0; i < 7500; i++)
{
ints.Add(i);
ints.Add(15000 - i - 1);
}
for (int i = 0; i < 45000; i++)
People.Add(new Person() { Id = i });
Stopwatch s = new Stopwatch();
s.Start();
// code A (feel free to uncomment it)
//Dictionary<int, int> dict = ints.ToDictionary(p => p);
//List<Person> newList = People.Where(p => !dict.ContainsKey(p.Id)).ToList();
// code B
List<Person> newList = People.Where(p => !ints.Contains(p.Id)).ToList();
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);
Console.WriteLine("Number of elements " + newList.Count);
Console.ReadKey();
}
On release mode results are :
Both code A & code B outputs 30 000 elements but :
It took more than 2000 ms with code B and only 5 ms with code A
public class Programm
{
public static void Main()
{
List<ChangesSummary> summaries = new List<ChangesSummary>();
summaries.Add(new ChangesSummary()
{
FieldName = "1",
ProviderKey = "Test1",
});
summaries.Add(new ChangesSummary()
{
FieldName = "2",
ProviderKey = "Test2",
});
summaries.Add(new ChangesSummary()
{
FieldName = "3",
ProviderKey = "Test3",
});
List<string> listProviderKeys = new List<string>();
listProviderKeys.Add("Test1");
listProviderKeys.Add("Test3");
var res = summaries.Where(x => !listProviderKeys.Contains(x.ProviderKey));
res.ToList().ForEach(x => Console.WriteLine(x.ProviderKey));
Console.ReadLine();
}
}
public class ChangesSummary
{
public string TableName { get; set; }
public string ProviderKey { get; set; }
public string ProviderAdrsKey { get; set; }
public string ProviderSpecialtyKey { get; set; }
public string FieldName { get; set; }
}
I think in this case simple Where would be easier and more readable to apply.
var first = new List<string> { "a" };
var second = new List<ChangesSummary>()
{
new ChangesSummary() { ProviderKey = "a" },
new ChangesSummary() { ProviderKey = "b" }
};
var result = second.Where(item => !first.Contains(item.ProviderKey));
// result
// .ToList()
// .ForEach(item => Console.WriteLine(item.ProviderKey));
I believe this will work:
List<ChangesSummary> ExceptionList = SecondList.
Where(x => !ListProviderKeys.Any(key => x.ProviderKey == key)).ToList();
Related
I am developing an inventory management system and I want to add a product with variants. I can add a product with three variants(color, size, material) and the options for each as below:
color - Black, Blue, Grey
size - S,M,L,XL
material - Cotton, Wool
If specify only 2 variants(e.g. color and size) my code is generating all the options correctly but if I add a 3rd variant then its not giving me the expected output.
Suppose I have a product called Jean my expected output would be as below:
Jean-Black/S/Cotton
Jean-Black/S/Wool
Jean-Black/M/Cotton
Jean-Black/M/Wool
Jean-Black/L/Cotton
Jean-Black/L/Wool
Jean-Black/XL/Cotton
Jean-Black/XL/Wool
===========================================
Jean-Blue/S/Cotton
Jean-Blue/S/Wool
Jean-Blue/M/Cotton
Jean-Blue/M/Wool
Jean-Blue/L/Cotton
Jean-Blue/L/Wool
Jean-Blue/XL/Cotton
Jean-Blue/XL/Wool
===========================================
Jean-Grey/S/Cotton
Jean-Grey/S/Wool
Jean-Grey/M/Cotton
Jean-Grey/M/Wool
Jean-Grey/L/Cotton
Jean-Grey/L/Wool
Jean-Grey/XL/Cotton
Jean-Grey/XL/Wool
My model is as below:
public class CreateModel : PageModel
{
[Required]
[BindProperty]
public string? Name { get; set; }
[BindProperty]
public List<ProductVariantModel> Variants { get; set; }
}
ProductVariantModel
public class ProductVariantModel
{
public string? Name { get; set; }
public string? Options { get; set; }
}
I'm creating the combinations as below:
List<ProductVariantOption> productOptions = new();
try
{
int variantsTotal = model.Variants.Count;
for (int a = 0; a < variantsTotal; a++)
{
string[] options = model.Variants[a].Options.Split(',');
for (int i = 0; i < options.Length; i++)
{
string? option = $"{model.Name}-{options[i]}";
if (variantsTotal > 1)
{
int index = a + 1;
if (index < variantsTotal)
{
var levelBelowOptions = model.Variants[index].Options.Split(',');
var ops = GetOptions(option, levelBelowOptions);
productOptions.AddRange(ops);
}
}
}
a += 1;
}
}
GetOptions method
private List<ProductVariantOption> GetOptions(string option, string[] options)
{
List<ProductVariantOption> variantOptions = new();
for (int i = 0; i < options.Length; i++)
{
string sku = $"{option}/{options[i]}";
string opt = $"{option}/{options[i]}";
variantOptions.Add(new ProductVariantOption(opt, sku));
}
return variantOptions;
}
ProductVariantOption
public class ProductVariantOption
{
public string Name { get; private set; }
public string SKU { get; private set; }
public Guid ProductVariantId { get; private set; }
public ProductVariant ProductVariant { get; private set; }
public ProductVariantOption(string name, string sku)
{
Guard.AgainstNullOrEmpty(name, nameof(name));
Name = name;
SKU = sku;
}
}
Where am I getting it wrong?
If you generalize your problem, you can describe it as follows:
For every potential variable of the model starting with a single variant (base model name), generate every possible combination of models so far with this variable
Having generated those combinations, map each generated combination into ProductVariantOption.
So you might want to generate cross products of all lists of variables. This could be achieved with an .Aggregate which does .SelectMany inside (note that I have simplified the definition of the final output, but you can construct it as you want inside the .BuildModel method:
private static ProductVariantOption BuildModel(string[] modelParts) {
if (modelParts.Length == 1) {
return new ProductVariantOption {
Name = modelParts.Single()
};
}
var baseName = modelParts.First();
var variantParts = string.Join('/', modelParts.Skip(1));
return new ProductVariantOption {
Name = $"{baseName}-{variantParts}"
};
}
public static IList<ProductVariantOption> GetVariants(CreateModel model) {
// Prepare all possible variables from the model in advance
var allVariables = model.Variants.Select(v => v.Options.Split(",")).ToArray();
var initialParts = new List<string[]> { new[] { model.Name } };
// Generate cross product for every subsequent variant with initial list as a seed
// Every iteration of aggregate produces all possible combination of models with the new variant
var allModels = allVariables.Aggregate(initialParts, (variantsSoFar, variableValues) =>
variantsSoFar
.SelectMany(variant => variableValues.Select(variableValue => variant.Append(variableValue).ToArray()))
.ToList()
);
// Map all lists of model parts into ProductVariantOption
return allModels.Select(BuildModel).ToList();
}
This approach has the benefit of being able to handle any amount of potential variables including cases where there are no variables (in this case only a single variant is produced - In your example it would be just "Jean")
Complete running example:
https://dotnetfiddle.net/XvkPZQ
I'm not sure why you have the name and SKU fields identical, but using your model, you need to initialize a list of the first level product variants, and then use that list as product variants so far and add the next variant to every product variant in the list.
I just used a List<string> to store the name so far, and added to it each variant's options:
var productVariantsName = model.Variants[0].Options.Split(',')
.Select(o => $"{model.Name}-{o}")
.ToList();
foreach (var variant in model.Variants.Skip(1)) {
var pvNameSoFar = productVariantsName;
productVariantsName = new();
foreach (var pvName in pvNameSoFar) {
foreach (var option in variant.Options.Split(','))
productVariantsName.Add($"{pvName}/{option}");
}
}
var productOptions = productVariantsName
.Select(pvName => new ProductVariantOption(pvName, pvName))
.ToList();
You can also do this with LINQ using the CartesianProduct LINQ Method from the answer (I use a slightly different lambda version).
With that defined, you can do:
var productOptions = model.Variants
.Select(v => v.Options.Split(','))
.CartesianProduct()
.Select(vs => $"{model.Name}-{vs.Join("/")}")
.Select(pvName => new ProductVariantOption(pvName, pvName))
.ToList();
PS: This uses the obvious definition for the Join string extension method.
For completeness, here are the extensions used:
public static class IEnumerableExt {
public static string Join(this IEnumerable<string> ss, string sep) => String.Join(sep, ss);
public static IEnumerable<T> AsSingleton<T>(this T item) => new[] { item };
// ref: https://stackoverflow.com/a/3098381/2557128
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) =>
sequences.Aggregate(Enumerable.Empty<T>().AsSingleton(),
(accumulator, sequence) => accumulator.SelectMany(_ => sequence,
(accseq, item) => accseq.Append(item)));
}
I am looking for a way of optimizing my LINQ query.
Classes:
public class OffersObject
{
public List<SingleFlight> Flights { get; set; }
public List<Offer> Offers { get; set; } = new List<Offer>();
}
public class SingleFlight
{
public int Id { get; set; }
public string CarrierCode { get; set; }
public string FlightNumber { get; set; }
}
public class Offer
{
public int ProfileId { get; set; }
public List<ExtraOffer> ExtraOffers { get; set; } = new List<ExtraOffer>();
}
public class ExtraOffer
{
public List<int> Flights { get; set; }
public string Name { get; set; }
}
Sample object:
var sampleObject = new OffersObject
{
Flights = new List<SingleFlight>
{
new SingleFlight
{
Id = 1,
CarrierCode = "KL",
FlightNumber = "1"
},
new SingleFlight
{
Id = 2,
CarrierCode = "KL",
FlightNumber = "2"
}
},
Offers = new List<Offer>
{
new Offer
{
ProfileId = 41,
ExtraOffers = new List<ExtraOffer>
{
new ExtraOffer
{
Flights = new List<int>{1},
Name = "TEST"
},
new ExtraOffer
{
Flights = new List<int>{2},
Name = "TEST"
},
new ExtraOffer
{
Flights = new List<int>{1,2},
Name = "TEST"
}
}
}
}
};
Goal of LINQ query:
List of:
{ int ProfileId, string CommercialName, List<string> fullFlightNumbers }
FullFlightNumber should by created by "Id association" of a flight. It is created like: {CarrierCode} {FlightNumber}
What I have so far (works correctly, but not the fastest way I guess):
var result = sampleObject.Offers
.SelectMany(x => x.ExtraOffers,
(a, b) => {
return new
{
ProfileId = a.ProfileId,
Name = b.Name,
FullFlightNumbers = b.Flights.Select(f => $"{sampleObject.Flights.FirstOrDefault(fl => fl.Id == f).CarrierCode} {sampleObject.Flights.First(fl => fl.Id == f).FlightNumber}").ToList()
};
})
.ToList();
Final note
The part that looks wrong to me is:
.Select(f => $"{sampleObject.Flights.FirstOrDefault(fl => fl.Id == f)?.CarrierCode} {sampleObject.Flights.FirstOrDefault(fl => fl.Id == f)?.FlightNumber}").ToList()
I am basically looking for a way of "joining" those two lists of the OffersObject by Flight's Id.
Any tips appreciated.
If there will only be a few flights defined in sampleObject.Flights, a sequential search using a numeric key is hard to beat.
However, if the number of flights times the number of offers is substantial (1000s or more), I would suggest loading the list of flights into a dictionary with Id as the key for efficient lookup. Something like:
var flightLookup = sampleObject.Flights.ToDictionary(f => f.Id);
And then calculate your FullFlightNumbers as
FullFlightNumbers = b.Flights
.Select(flightId => {
flightLookup.TryGetValue(flightId, out SingleFlight flight);
return $"{flight?.CarrierCode} {flight?.FlightNumber}";
})
.ToList()
TryGetValue above will quietly return a null value for flight if no match is found. If you know that a match will always be present, the lookup cold alternately be coded as:
SingleFlight flight = flightLookup[flightId];
The above also uses a statement lambda. In short, lambda functions can have either expression or statement blocks as bodies. See the C# reference for more information.
I'd suggest replacing the double .FirstOrDefault() approach with .IntersectBy(). It is available in the System.Linq namespace, starting from .NET 6.
.IntersectBy() basically filters sampleObject.Flights by matching the flight ID for each flight in sampleObject with flight IDs in ExtraOffers.Flights.
In the code below, fl => fl.Id is the key selector for sampleObject.Flights (i.e. fl is a SingleFlight).
var result = sampleObject.Offers
.SelectMany(x => x.ExtraOffers,
(a, b) => {
return new
{
ProfileId = a.ProfileId,
Name = b.Name,
FullFlightNumbers = sampleObject.Flights
.IntersectBy(b.Flights, fl => fl.Id)
.Select(fl => fl.FullFlightNumber) // alternative 1
//.Select(fl => $"{fl.CarrierCode} {fl.FlightNumber}") // alternative 2
.ToList()
};
})
.ToList();
In my suggestion I have added the property FullFlightNumber to SingleFlight so that the Linq statement looks slightly cleaner:
public class SingleFlight
{
public int Id { get; set; }
public string CarrierCode { get; set; }
public string FlightNumber { get; set; }
public string FullFlightNumber => $"{CarrierCode} {FlightNumber}";
}
If defining SingleFlight.FullFlightNumber is not possible/desirable for you, the second alternative in the code suggestion can be used instead.
Example fiddle here.
At the beginning I am aware that there are similar questions, but mine is a little bit different.
I implemented a function that allows the user to select the columns he wants to see.
I've created a stored procedure that gets all column names from the UserColumns table, creates a dynamic sql query and then runs the exec (#command) query. The functionality described above works very well, but there are more requirements that I can't handle this way.
There is TasksViewModel:
public class TasksViewModel
{
public List<Dictionary<List<string>, List<List<object>>>> Tasks { get; set; }
public List<UserDefaultStatusesViewModel> UserStatuses { get; set; }
public List<ZgloszenieStatus> TaskStatuses { get; set; }
public TasksViewModel()
{
}
}
Tasks is filled by stored procedure that runs SELECT x,y,z... FROM table... query.
I'm using this method:
private static IEnumerable<Dictionary<List<string>, List<List<object>>>> Read(DbDataReader reader)
{
var dict = new Dictionary<List<string>, List<List<object>>>();
var cols = new List<string>();
for (int temp = 0; temp < reader.FieldCount; temp++)
{
cols.Add(reader.GetName(temp));
}
var items = new List<List<object>>();
while (reader.Read())
{
var tmp = new List<object>();
for (int i = 0; i < reader.FieldCount; i++)
{
tmp.Add(reader.GetValue(i));
}
items.Add(tmp);
}
dict.Add(cols, items);
foreach (var item in dict)
{
}
yield return dict;
}
I find this very overcomplicated, but at the moment I have no idea if there is another way to do this.
I'm using Entity Framework in my application.
Imagine that I'm using List<Tasks> instead of List<Dictionary<List<string>, List<List<object>>>>. Tasks is database table.
public class Tasks
{
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime Date { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
User wants to see only ID,Title,Description columns, so the UserColumns table looks like this:
UserId | ColumnName
1 | ID
2 | Title
3 | Description
Is there a way to select specific columns from List<Tasks> based on UserColumns table using Entity Framework ?
You can create the lambda for Column List dynamically
static Func<Tasks, Tasks> CreateSelect(string[] columns)
{
var parameterExpression = Expression.Parameter(typeof(Tasks), "p");
var newExpression = Expression.New(typeof(Tasks));
var bindings = columns.Select(o => o.Trim())
.Select(o =>
{
var pi = typeof(Tasks).GetProperty(o);
var memberExpression = Expression.Property(parameterExpression, pi);
return Expression.Bind(pi, memberExpression);
}
);
var memberInitExpression = Expression.MemberInit(newExpression, bindings);
var lambda = Expression.Lambda<Func<Tasks, Tasks>>(memberInitExpression, parameterExpression);
return lambda.Compile();
}
and create a LINQ query based on that lambda (columnNameList array is rows from UserColumns table)
static void Foo()
{
var columnNameList = new string[] { "ID", "Title", "Description" };
var tasksList = new List<Tasks>
{
new Tasks{ ID=1, Title="T1", FirstName="F1", LastName="L1", Description="D1", Date=DateTime.UtcNow },
new Tasks{ ID=2, Title="T2", FirstName="F2", LastName="L2", Description="D2", Date=DateTime.UtcNow }
};
var tasks = tasksList.Select(CreateSelect(columnNameList)).FirstOrDefault();
}
I hope that answers your question.
I have to find out the difference between two lists of class Category.
My Category class has these properties:
public class Category
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsQuantitative
{
get { return Products.Any(x => x.IsMultiPart); }
}
public List<Product> Products { get; set; }
public string Image { get; set; }
public string Description { get; set; }
}
Try this
var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();
link
I would sort the two lists by Id first.
Then run through it and compare the Objects.
This is a good Object Comparer:
https://www.nuget.org/packages/CompareNETObjects/
Here a little example:
//Here you set the config like you want to have it compared
ComparisonConfig comparisonConfig = new ComparisonConfig()
{
CompareChildren = true,
CompareFields = true,
CompareReadOnly = true,
CompareProperties = true,
MaxDifferences = 1,
MaxByteArrayDifferences = 1
};
CompareLogic comparer = new CompareLogic() { Config = comparisonConfig };
list1 = list1.OrderBy(x => x.Id).ToList();
list2 = list2.OrderBy(x => x.Id).ToList();
for (int i =0;i> list1.count;i++)
{
//Here you get a bool if the two Objects are Equal
bool areEqual = comparer.Compare(list1[i], list2[i]).AreEqual;
//Here you get a List of Differences Objects. It contains Values like "expected and "actual" etc.
var differences = comparer.Compare(list1[i], list2[i]).Differences;
//Here you handle Differences etc.
}
I an having Two Lists. I want to get the matched and unmatched values based on ID and add the results to another List. I can get both of these using Intersect/Except.
But I can get only ID in the resultant variables (matches and unmatches) . I need all the properties in the Template.
List<Template> listForTemplate = new List<Template>();
List<Template1> listForTemplate1 = new List<Template1>();
var matches = listForTemplate .Select(f => f.ID)
.Intersect(listForTemplate1 .Select(b => b.ID));
var ummatches = listForTemplate .Select(f => f.ID)
.Except(listForTemplate1.Select(b => b.ID));
public class Template
{
public string ID{ get; set; }
public string Name{ get; set; }
public string Age{ get; set; }
public string Place{ get; set; }
public string City{ get; set; }
public string State{ get; set; }
public string Country{ get; set; }
}
public class Template1
{
public string ID{ get; set; }
}
If you don't want to implement IEquality for this simple task, you can just modify your LINQ queries:
var matches = listForTemplate.Where(f => listForTemplate1.Any(b => b.ID == f.ID));
and
var unmatches = listForTemplate.Where(f => listForTemplate1.All(b => b.ID != f.ID));
You might want to check for null before accessing ID, but it should work.
You are looking for the overloaded function, with the second parameter IEqualityComparer. So make your comparer ( example: http://www.blackwasp.co.uk/IEqualityComparer.aspx ), and use the same comparer in intersect / except.
And for the generic part: maybe you should have a common interface for templates e.g. ObjectWithID describing that the class have a string ID property. Or simply use dynamic in your comparer (but I think this is very-very antipattern because you can have run time errors if using for the bad type).
You also have a problem: intersecting two collections with two different types will result in a collection of Object (common parent class). Then you have to cast a lot (antipattern). I advise you to make a common abstract class/interface for your template classes, and it is working. If you need to cast the elements back, do not cast, but use the visitior pattern: http://en.wikipedia.org/wiki/Visitor_pattern
Example (good):
static void Main(string[] args)
{
// http://stackoverflow.com/questions/16496998/how-to-copy-a-list-to-another-list-with-comparsion-in-c-sharp
List<Template> listForTemplate = new Template[] {
new Template(){ID = "1"},
new Template(){ID = "2"},
new Template(){ID = "3"},
new Template(){ID = "4"},
new Template(){ID = "5"},
new Template(){ID = "6"},
}.ToList();
List<Template1> listForTemplate1 = new Template1[] {
new Template1(){ID = "1"},
new Template1(){ID = "3"},
new Template1(){ID = "5"}
}.ToList();
var comp = new ObjectWithIDComparer();
var matches = listForTemplate.Intersect(listForTemplate1, comp);
var ummatches = listForTemplate.Except(listForTemplate1, comp);
Console.WriteLine("Matches:");
foreach (var item in matches) // note that item is instance of ObjectWithID
{
Console.WriteLine("{0}", item.ID);
}
Console.WriteLine();
Console.WriteLine("Ummatches:");
foreach (var item in ummatches) // note that item is instance of ObjectWithID
{
Console.WriteLine("{0}", item.ID);
}
Console.WriteLine();
}
}
public class ObjectWithIDComparer : IEqualityComparer<ObjectWithID>
{
public bool Equals(ObjectWithID x, ObjectWithID y)
{
return x.ID == y.ID;
}
public int GetHashCode(ObjectWithID obj)
{
return obj.ID.GetHashCode();
}
}
public interface ObjectWithID {
string ID { get; set; }
}
public class Template : ObjectWithID
{
public string ID { get; set; }
public string Name { get; set; }
public string Age { get; set; }
public string Place { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
public class Template1 : ObjectWithID
{
public string ID { get; set; }
}
Output:
Matches:
1
3
5
Ummatches:
2
4
6
Press any key to continue . . .
For comparison, this should also work (the first part is a variation on #MAV's answer):
var matches = from item in listForTemplate
join id in listForTemplate1 on item.ID equals id.ID
select item;
var unmatches = listForTemplate.Where(item => matches.All(elem => elem.ID != item.ID));
matches and unmatches will both be IEnumerable<Template> which is the type you require.
However, MAV's answer works fine so I'd go for that one.
As mentioned, Implement the IEqualityComparer<T> interface.
IEqualityComparer<T> MSDN
Then use this as an argument in your method for Except() and Intersect()
Intersect
There is a good example of how to do so on the link for the Intersect() method.
If you don't absolutely have to use LINQ, why not code something like this?
var matches = new List<Template>();
var unmatches = new List<Template>();
foreach (var entry in listForTemplate)
{
bool matched = false;
foreach (var t1Entry in listForTemplate1)
{
if (entry.ID == t1Entry.ID)
{
matches.Add(entry);
matched = true;
break;
}
}
if (!matched)
{
unmatches.Add(entry);
}
}
A disadvantage of the LINQ approach is that you're traversing the lists twice.