HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Find unique variants of a product

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
uniquevariantsproductfind

Problem

I am writing a piece of code that returns all the unique variants that a product is available in for an ecommerce app. For example, a shirt product can be available in different colors, sizes, and linen. If the available attributes are red, green, L, XL, Cotton, and Polyester, then a list of the unique variants should be eventually returned as:

[{red; L; Cotton} ; {red; L; Polyester} ; {red; XL; Cotton} ; {red; XL; Polyester} ; {green; L; Cotton} ; {green; XL; Cotton} ; {green; L; Polyester} ;


{green; XL; Polyester}]

This would be the unique variants available for the product.

The code below works and eventually returns a string list of IDs representing each variant available for the product. The only problem that I am having with this is that it generates a duplicate of each variant. I can easily take care of that with a Set.ofList function after this code runs, but would like to solve that problem here internally. I'm new to F#, so what can I do to optimize this code?

```
type NewProductAttributeInfo = {
AttributeId : string;
AttributeCategoryId : string
}

let rec private returnVariant (curIdx: int) (listLength: int)
(attList: (int NewProductAttributeInfo NewProductAttributeInfo) list)
(curList: NewProductAttributeInfo list) =

match curList with
| x when x.Length = listLength -> curList
| x ->
let attTup =
attList
|> List.filter (fun x' ->
let idx1,att1,att2' = x'
idx1 >= curIdx && not(curList
|> List.exists (fun x'' ->
x'' = att2'))
)
let idx1,att1,att2 = attTup |> List.head
let newList = curList @ [att2]
returnVariant idx1 newList.Length attList newList

let rec calculateVariants (attList: NewProductAttributeInfo list)
(current

Solution

If I understand correctly, you're looking for the Cartesian product of the attributes in each attribute category.

To get the Cartesian product, I've adapted* Eric Lippert's solution from his blog post Computing a Cartesian product with LINQ.

let cartesianProduct xs =
    Seq.fold (fun acc xs -> seq {
        for accSeq in acc do
        for x in xs do
        yield Seq.append accSeq (Seq.singleton x)
    }) (Seq.singleton Seq.empty) xs


Then we need to group by the attribute category, and pull out just the attributes**.

let variants (attributes : seq) =
    attributes
        |> Seq.groupBy (fun attribute -> attribute.AttributeCategoryId)
        |> Seq.map (snd >> Seq.map (fun attribute -> attribute.AttributeId))
        |> cartesianProduct


Here is a test on the sample data you provided

let attributes = [
    { AttributeId = "red"; AttributeCategoryId = "Color" };
    { AttributeId = "L"; AttributeCategoryId = "Size" };
    { AttributeId = "XL"; AttributeCategoryId = "Size" };
    { AttributeId = "Cotton"; AttributeCategoryId = "Material" };
    { AttributeId = "green"; AttributeCategoryId = "Color" };
    { AttributeId = "Polyester"; AttributeCategoryId = "Material" }
]

for variant in variants attributes do
    printfn "%A" variant


Which gives:

seq ["red"; "L"; "Cotton"]
seq ["red"; "L"; "Polyester"]
seq ["red"; "XL"; "Cotton"]
seq ["red"; "XL"; "Polyester"]
seq ["green"; "L"; "Cotton"]
seq ["green"; "L"; "Polyester"]
seq ["green"; "XL"; "Cotton"]
seq ["green"; "XL"; "Polyester"]


* Hopefully without introducing errors.

**
This is a little bit nicer in C# since we have the overload of GroupBy that takes an elementSelector parameter:

return attributes.GroupBy(attribute => attribute.CategoryId, attribute => attribute.Id)
    .CartesianProduct();

Code Snippets

let cartesianProduct xs =
    Seq.fold (fun acc xs -> seq {
        for accSeq in acc do
        for x in xs do
        yield Seq.append accSeq (Seq.singleton x)
    }) (Seq.singleton Seq.empty) xs
let variants (attributes : seq<NewProductAttributeInfo>) =
    attributes
        |> Seq.groupBy (fun attribute -> attribute.AttributeCategoryId)
        |> Seq.map (snd >> Seq.map (fun attribute -> attribute.AttributeId))
        |> cartesianProduct
let attributes = [
    { AttributeId = "red"; AttributeCategoryId = "Color" };
    { AttributeId = "L"; AttributeCategoryId = "Size" };
    { AttributeId = "XL"; AttributeCategoryId = "Size" };
    { AttributeId = "Cotton"; AttributeCategoryId = "Material" };
    { AttributeId = "green"; AttributeCategoryId = "Color" };
    { AttributeId = "Polyester"; AttributeCategoryId = "Material" }
]

for variant in variants attributes do
    printfn "%A" variant
seq ["red"; "L"; "Cotton"]
seq ["red"; "L"; "Polyester"]
seq ["red"; "XL"; "Cotton"]
seq ["red"; "XL"; "Polyester"]
seq ["green"; "L"; "Cotton"]
seq ["green"; "L"; "Polyester"]
seq ["green"; "XL"; "Cotton"]
seq ["green"; "XL"; "Polyester"]
return attributes.GroupBy(attribute => attribute.CategoryId, attribute => attribute.Id)
    .CartesianProduct();

Context

StackExchange Code Review Q#110316, answer score: 6

Revisions (0)

No revisions yet.