javascript - Získajte všetky unikátne kombinácie hodnôt vzhľadom na skupiny


Mám súbor hodnôt atribútov:

Príklad :

[
  {
    memberAttribute: { attributeName: 'a' },
    value: '1'  
  },
  {
    memberAttribute: { attributeName: 'a' },
    value: '2'  
  },
  {
    memberAttribute: { attributeName: 'b' },
    value: '1'  
  },
  {
    memberAttribute: { attributeName: 'b' },
    value: '2'  
  }
]

Teraz chcem získať všetky jedinečné kombinácie pre dané atribúty člena.

Takže ak by som chcel jedinečné kombinácie pre atribúty člena 'a' a 'b', výsledok by bol:

[
   {
     memberAttribute: { attributeName: 'a' },
     value: '1'  
   },
   {
     memberAttribute: { attributeName: 'b' },
     value: '1'  
   }
],
[
   {
     memberAttribute: { attributeName: 'a' },
     value: '1'  
   },
   {
     memberAttribute: { attributeName: 'b' },
     value: '2'  
   }
],
[
   {
     memberAttribute: { attributeName: 'a' },
     value: '2'  
   },
   {
     memberAttribute: { attributeName: 'b' },
     value: '1'  
   }
],
[
   {
     memberAttribute: { attributeName: 'a' },
     value: '2'  
   },
   {
     memberAttribute: { attributeName: 'b' },
     value: '2'  
   }
]

Potrebujem byť schopný zadať n počet vstupných atributov člena, ale želaný výsledok dokážem dosiahnuť iba pre 2 vstupné atribúty.

Súčasné hrozné riešenie:

export const getAttributeCombinations = (
  attributes: MemberAttributeValue[]
) => {
  // TODO - This algorithm only supports 2 attribute types
  // It should support any number of attribute types

  const combinations = new Array<Array<MemberAttributeValue>>();

  for (const attribute of attributes) {
    let unusedAttributes = allExcept(attribute, attributes);

    const permutate = () => {
      const combination = [attribute];
      const toRemove = new Array<Number>();

      for (let i = 0; i < unusedAttributes.length; i++) {
        const unusedAttribute = unusedAttributes[i];

        if (!attributeTypeAlreadyExists(unusedAttribute, combination)) {
          toRemove.push(i);
          combination.push(unusedAttribute);
        }
      }

      for (const index of toRemove) {
        unusedAttributes = remove(index, 1, unusedAttributes);
      }

      combinations.push(combination);
    };

    permutate();

    while (unusedAttributes.length > 0) {
      permutate();
    }
  }

  const sortedCombinations = map(sortByAttributeName, combinations);
  return uniqByCombination(sortedCombinations);
};

Príklad zlyhávajúceho testu v Jest:

it('given 3 attribute types should return 12 combinations', () => {
    const inclusionAttributes: MemberAttributeValue[] = [
      {
        memberAttribute: {
          attributeName: 'gender',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: 'Male'
      },
      {
        memberAttribute: {
          attributeName: 'gender',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: 'Female'
      },
      {
        memberAttribute: {
          attributeName: 'age band',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: '0-50'
      },
      {
        memberAttribute: {
          attributeName: 'age band',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: '51+'
      },
      {
        memberAttribute: {
          attributeName: 'likes',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: 'cats'
      },
      {
        memberAttribute: {
          attributeName: 'likes',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: 'dogs'
      },
      {
        memberAttribute: {
          attributeName: 'likes',
          aliases: [],
          contentType: ContentType.String,
          type: AttributeType.Mandatory
        },
        value: 'goats'
      }
    ];

    const combinations = getAttributeCombinations(inclusionAttributes);

    expect(combinations.length).toBe(12);

    for (const combination of combinations) {
      expect(combination.length).toBe(3);
    }
  });

Odpoveď č. 1

To by bolo relatívne jednoduché v Ramda, okrem toho, že funkcia xprod v Ramda funguje iba na dve zoznamy. Ak by fungovala na zozname zoznamov, mohli by sme to urobiť len v niekoľkých krokoch. Ale je veľmi jednoduché napísať si vlastnú:

const xproduct = reduce(pipe(xprod, map(unnest)), [[]])

const transform = pipe(
  groupBy(path(['memberAttribute', 'attributeName'])),
  values,
  xproduct
)

const inclusionAttributes = [
  {"memberAttribute": {"attributeName": "gender"}, "value": "Male"}, 
  {"memberAttribute": {"attributeName": "gender"}, "value": "Female"}, 
  {"memberAttribute": {"attributeName": "age band"}, "value": "0-50"}, 
  {"memberAttribute": {"attributeName": "age band"}, "value": "51+"}, 
  {"memberAttribute": {"attributeName": "likes"}, "value": "cats"}, 
  {"memberAttribute": {"attributeName": "likes"}, "value": "dogs"}, 
  {"memberAttribute": {"attributeName": "likes"}, "value": "goats"}
]

console.log(transform(inclusionAttributes))
//=> Male/0-50/cats, Male/0-50/dogs, Male/0-50/goats, Male/51+/cats,...

Môžete to vidieť v akcii na Ramda REPL.