简体   繁体   中英

JavaScript transform array of objects into another using lodash

I have an array of objects that looks like this:

[
  {
    type: 'car',
    choices: [
      'audi',
      'honda',
      'bmw',
      'ford'
    ],
  },
  {
    type: 'drink',
    choices: [
      'soda',
      'water',
      'tea',
      'coffee'
    ],
  },
  {
    type: 'food',
    choices: [
      'chips',
      'pizza',
      'cookie',
      'pasta'
    ],
  }
]

Using lodash how to transform it into something that looks like this:

[
  {
    question: [
      {
        drink: "tea"
      },
      {
        car: "bmw"
      }
    ]
  },
  {
    question: [
      {
        food: "cookie"
      },
      {
        car: "ford"
      }
    ]
  },
  {
    question: [
      {
        drink: "soda"
      },
      {
        food: "pizza"
      }
    ]
  },
  {
    question: [
      {
        food: "chips"
      },
      {
        drink: "water"
      }
    ]
  },
  {
    question: [
      {
        car: "audi"
      },
      {
        food: "pasta"
      }
    ]
  },
  {
    question: [
      {
        car: "honda"
      },
      {
        drink: "coffee"
      }
    ]
  },
]

The logic is as follow:

  • Every question has a combination of 2 choices where every choice is of different type example (car and food).
  • Combination of different types should occur only twice (car, food).
  • No duplication of choices.
  • The selection of choices should be randomized.

I tried to Flatten the array using this function

    let flattenItems = _.flatMap(items, ({ type, choices}) =>
      _.map(choices, choice => ({
        question: [
          { type: type, choice: choice },
          { type: type, choice: choice }
        ],
      })
    ));

but it's not what I need, and it's not random. I not sure my approach is the correct one, I'm thinking I should use a filter or reduce

Any help on how to solve this would be appreciated using JS or lodash would be good.

You could get a combination from types and a random choices selecten with a check if a value is aleady used.

 function getCombinations(array, size) { function c(left, right) { function getQuestion({ type, choices }) { var random; do { random = choices[Math.floor(Math.random() * choices.length)]; } while (taken.get(type).has(random)) taken.get(type).add(random); return { [type]: random }; } left.forEach((v, i, a) => { var temp = [...right, v]; if (temp.length === size) { result.push({ question: temp.map(getQuestion) }); } else { c([...a.slice(0, i), ...a.slice(i + 1)], temp); } }); } var result = [], taken = new Map(array.map(({ type }) => [type, new Set])); c(array, []); return result; } var data = [ { type: 'car', choices: ['audi', 'honda', 'bmw', 'ford'] }, { type: 'drink', choices: ['soda', 'water', 'tea', 'coffee'] }, { type: 'food', choices: ['chips', 'pizza', 'cookie', 'pasta'] } ]; console.log(getCombinations(data, 2)); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

Using Lodash

 function randomizedQues(items) { let result = []; let flattenItems = _.flatMap(items, ({ type, choices }) => _.map(choices, choice => ({ type: type, choice: choice }) )) while(flattenItems.length > 1) { let r1 = _.random(flattenItems.length - 1), e1 = flattenItems[r1]; let r2 = _.random(flattenItems.length - 1), e2 = flattenItems[r2]; if(e1.type === e2.type) continue result.push({ question: [ {[e1.type]: e1.choice}, {[e2.type]: e2.choice} ] }) _.pullAt(flattenItems, [r1, r2]) } return result } let items = [{"type":"car","choices":["audi","honda","bmw","ford"]},{"type":"drink","choices":["soda","water","tea","coffee"]},{"type":"food","choices":["chips","pizza","cookie","pasta"]}] console.log(randomizedQues(items)) 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script> 

This was my thinking, each different combination of types needs to appear twice. So I looped forwards over the array and combined each type with the proceeding types. Then I looped backwards over the array and combined each type with the preceeding types. At the same time I used Math.random() to pick a random choice from the choices subarray. The only problem is that this does not enforce strict duplicate elimination but relies on RNG to guarantee a low chance of duplicates. You should be able to add duplicate checking code inside each loop just before you create the new question.

 function buildQuestions(data) { const questions = [] for (let i = 0; i < data.length; i++) for (let j = i + 1; j < data.length; j++) questions.push({question: [{[data[i].type]: data[i].choices[Math.round(Math.random() * (data[i].choices.length - 1))]}, {[data[j].type]: data[j].choices[Math.round(Math.random() * (data[j].choices.length - 1))]}]}) for (let i = data.length - 1; i > 0; i--) for (let j = i - 1; j >= 0; j--) questions.push({question: [{[data[i].type]: data[i].choices[Math.round(Math.random() * (data[i].choices.length - 1))]}, {[data[j].type]: data[j].choices[Math.round(Math.random() * (data[j].choices.length - 1))]}]}) return questions } const choices = [{ type: 'car',choices: ['audi','honda','bmw','ford'],},{type: 'drink', choices: ['soda','water','tea','coffee'],},{type: 'food',choices: ['chips','pizza','cookie','pasta'],}] console.log(buildQuestions(choices)) 

You can use a recursive function to keep removing items from each array until you're left without enough options remaining to fill in any more questions.

To help do this, we have functions which take in an array, and return a random item, plus the array without that item. We can then build the questions with that data, ensuring that each item is only used once.

 const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x) const data = [ { type: 'car', choices: ['audi', 'honda', 'bmw', 'ford'] }, { type: 'drink', choices: ['soda', 'water', 'tea', 'coffee'] }, { type: 'food', choices: ['chips', 'pizza', 'cookie', 'pasta'] } ]; const getArrayIndexPair = array => [ array, getRandom(array), ]; const subtractItemFromArray = ([array, index]) => [ array.slice(index, index + 1)[0], [ ...array.slice(0, index), ...array.slice(index + 1, array.length) ] ]; const getRandom = array => Math.floor(Math.random()*array.length); const takeRandom = pipe( getArrayIndexPair, subtractItemFromArray, ); const choicesKeyedByType = data .reduce((p, c) => ({ ...p, [c.type]: c.choices, }), {}) const formQuestions = (choices, questions=[]) => { if (Object.keys(choices).length <= 1) { return questions; } const [keyOne, remainingKeys] = takeRandom(Object.keys(choices)); const [keyTwo] = takeRandom(remainingKeys); const [choiceOne, remainingKeyOneChoices] = takeRandom(choices[keyOne]); const [choiceTwo, remainingKeyTwoChoices] = takeRandom(choices[keyTwo]); const newChoices = { ...choices, [keyOne]: remainingKeyOneChoices, [keyTwo]: remainingKeyTwoChoices, }; const newChoicesWithoutEmpty = Object.keys(newChoices) .filter(key => newChoices[key].length > 0) .reduce((p, c) => ({ ...p, [c]: newChoices[c] }), {}); const newQuestions = [ ...questions, { [keyOne]: choiceOne, [keyTwo]: choiceTwo, } ]; return formQuestions( newChoicesWithoutEmpty, newQuestions, ); }; console.dir(formQuestions(choicesKeyedByType)) 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM