Cartesian product of attribute values in single list - c#

I have a list of attribute values that look like this:
public class AttributeOption
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class MyAttribute
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<AttributeOption> Options { get; set; } = new List<AttributeOption>();
}
I have a list of attributes i.e. var attributes = new List<MyAttribute>(); and the list contains three sets of attributes e.g. color, size and gender. Each attribute has its options. For example, color has red, white and blue options. Size has small, medium and large and finally gender has male and female.
How do I generate a cartesian product of all these attributes? I should end up with:
Red-Small-Male
Red-Small-Female
Red-Medium-Male
...
White-Small-Male
White-Small-Female
...

From this answer and this later answer, you can use an extension method to generate the Cartesian Product of an arbitrary number of sequences:
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) =>
sequences.Aggregate(Enumerable.Empty<T>().AsSingleton(),
(accumulator, sequence) => accumulator.SelectMany(prodSoFar => sequence.Select(prodSoFar.Append)));
public static IEnumerable<T> AsSingleton<T>(this T item) => new[] { item };
Using this extension method, you can use LINQ to generate your desired answer.
var ans = attributes.Select(a => a.Options.Select(o => o.Name))
.CartesianProduct()
.Select(s => String.Join("-", s))
.ToList();

Related

C# Sorting IEnumerable of IEnumerables

I have an object that looks something like this:
public class MyObj
{
public string Title { get; set; }
public IEnumerable<Section> Sections { get; set; }
}
public class Section
{
public string Title { get; set; }
public IEnumerable<Item> Items { get; set; }
public int SortOrder { get; set; }
}
public class Item
{
public string Title { get; set; }
public int SortOrder { get; set; }
}
Essentially I end up with an IEnumerable of sections, which in turn contains an IEnumerable of items. Both the list of sections and the items need to be sorted by their respective SortOrder properties.
I know that I can sort the sections by doing obj.Sections.OrderBy(s => s.SortOrder) but then I can't work out how to sort the items within each section too.
The context is that I'm writing a Sort function that takes an unsorted MyObj and returns one with both the sections and the items sorted.
public MyObj Sort(MyObj unsortedObj)
{
var sortedObj = unsortedObj.....
return sortedObj;
}
The expected data structure would be something like this:
- Section1
- Item1
- Item2
- Section2
- Item1
- Item2
It would be convenient for you to add methods that creates copies of these objects except for one property being different:
// in MyObj
public MyObj WithSections(IEnumerable<Section> sections) =>
new MyObj {
Title = this.Title,
Sections = sections
};
// in Section
public Section WithItems(IEnumerable<Items> items) =>
new Section {
Title = this.Title,
Items = items,
SortOrder = this.SortOrder
};
First, sort the sections
var sortedSections = unsortedObj.Sections.OrderBy(x => x.SortOrder);
Then for each of those sorted sections, transform them with Select so that their items are also sorted:
var sortedSectionsAndItems = sortedSections.Select(x => x.WithItems(x.Items.OrderBy(y => y.SortOrder)));
Now you can return a MyObj with the sorted sections and items:
return unsortedObj.WithSections(sortedSectionsAndItems);

Where condition inside lambda expression c#

I have a entity like
public class Program
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
}
and
public class EMetrics
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
public List<Program> Programs { get; set; }
}
I have repository method like,
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(x => x.ID)))
.ToList();
return metrics;
}
[The above code throwing build error]
Here only where I am facing problem to get the EMetrics based on the program Ids array params.
I want list Emetrics which are associated with the program.
You're incorrectly accessing the same input parameter in your LINQ. It should be refactored by changing your inner Select to use a different parameter:
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(y => y.ID)))
.ToList();
return metrics;
}
So you want to check if all elements of one collection are present in the other. In LINQ that can be done with combination of Except and Any:
var metrics = EntitySet
.Where(x => x.Programs.Select(p => p.ID).Except(programIds).Any())
.ToList();
Fyi - your current code is failing because Array.Contains expects a single item, an int in this case, while you are giving it a whole enumerable

Order Collection by child child collection

I have the following objects
public class ObjectA{
public string Name { get; set; }
public ICollection<ObjectB> ObjectBCollection { get; set; }
}
public class ObjectB{
public string Name { get; set; }
public ICollection<ObjectC> ObjectCCollection { get; set; }
}
public class ObjectC{
public string Name { get; set; }
public DateTime Date { get; set; }
public InternalType Type { get; set; }
}
public enum InternalType {
TypeA,
TypeB,
TypeC
}
Now i want to order a List of ObjectA by the Dates in ObjectC that are closests to the current date. To make things a little more interesting, I also want it sorted by the InteralType. But I want TypeB have priority over TypeA and TypeC comes last.
I was thinking of creating an extra value that presents the integer value of the timespan between the current date and the Date property and multiply that by the Type property, but I can't figure out how to actually do that.
First of all if you want specific ordering in Enum you can do this:
public enum InternalType : int
{
TypeA = 2,
TypeB = 1,
TypeC = 3
}
Next if I understood your question correctly you have:
var collection = new List<ObjectA>();
which you need to sort by ALL dates in child elements. You can use Linq expressions for this:
List<KeyvaluePair<DateTime, ObjectA>> collectionWithDates = collection
.Select
(
objectA => new KeyValuePair<DateTime, ObjectA>
(
objectA
.SelectMany(a => a.ObjectBCollection)
.SelectMany(b => b.ObjectCCollection)
.OrderBy(c => c.Date).ThenBy(c => (int)c.Type)
.Last()
.Date,
objectA
)
)
.ToList();
To get ordered list of ObjectA you just need to:
var orderedCollection = collectionWithDates
.OrderBy(d => d.Key)
.Select(d => d.value)
.ToList();
I believe this should work. However I didn't tested it.
Correct me in comments if I misunderstood requirements.
Last thing to add - as far as I know, Linq expressions are not the fastest way to sort collections.

RavenDB cross entity query matching on non-identifier property

I have the following entity collections in RavenDB:
public class EntityA
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
public class EntityB
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
The only thing shared is the Tags collection: a tag of EntityA may exist in EntityB, so that they may intersect.
How can I retrieve every EntityA that has intersecting tags with EntityB where the Name property of EntityB is equal to a given value?
Well, this is a difficult one. To do it right, you would need two levels of reducing - one by the tag which would expand out your results, and another by the id to collapse it back. Raven doesn't have an easy way to do this.
You can fake it out though using a Transform. The only problem is that you will have skipped items in your result set, so make sure you know how to deal with those.
public class TestIndex : AbstractMultiMapIndexCreationTask<TestIndex.Result>
{
public class Result
{
public string[] Ids { get; set; }
public string Name { get; set; }
public string Tag { get; set; }
}
public TestIndex()
{
AddMap<EntityA>(entities => from a in entities
from tag in a.Tags.DefaultIfEmpty("_")
select new
{
Ids = new[] { a.Id },
Name = (string) null,
Tag = tag
});
AddMap<EntityB>(entities => from b in entities
from tag in b.Tags
select new
{
Ids = new string[0],
b.Name,
Tag = tag
});
Reduce = results => from result in results
group result by result.Tag
into g
select new
{
Ids = g.SelectMany(x => x.Ids),
g.First(x => x.Name != null).Name,
Tag = g.Key
};
TransformResults = (database, results) =>
results.SelectMany(x => x.Ids)
.Distinct()
.Select(x => database.Load<EntityA>(x));
}
}
See also the full unit test here.
There is another approach, but I haven't tested it yet. That would be to use the Indexed Properties Bundle to do the first pass, and then map those results for the second pass. I am experimenting with this in general, and if it works, I will update this answer with the results.

LINQ - SelectMany from multiple properties in same object

Assume you have the following simple objects:
class Order
{
public Customer[] Customers { get; set; }
}
class Customer
{
public SaleLine[] SaleLines { get; set; }
}
class SaleLine
{
public Tax[] MerchTax { get; set; }
public Tax[] ShipTax { get; set; }
}
class Tax
{
public decimal Rate { get; set; }
public decimal Total { get; set; }
}
With these objects, I want to be able to get a list of all the unique tax rates used on the entire order, including both merchandise and shipping tax rates.
The following LINQ query will get me the list I need, but only for merchandise taxes:
var TaxRates = MyOrder.Customers
.SelectMany(customer => customer.SaleLines)
.SelectMany(saleline => saleline.MerchTax)
.GroupBy(tax => tax.Rate)
.Select(tax => tax.First().Rate
How can I get a list that contains list of unique tax rates that contains both merchandise and shipping rates?
It sounds like you want this:
var TaxRates = MyOrder.Customers
.SelectMany(customer => customer.SaleLines)
.SelectMany(saleline => saleline.MerchTax.Concat(saleline.ShipTax))
.GroupBy(tax => tax.Rate)
.Select(group => group.Key);
Basically the change is the call to Concat which will concatenate two sequences together.
It can also be used like this;
var TaxRates = MyOrder.Customers
.ToList()
.SelectMany(x => new[] { x.SaleLines, x.MerchTax })
.GroupBy(tax => tax.Rate)
.Select(group => group.Key);

Categories