1
 groupSelectedQuestions(selectedQuestions){
     var questions = [
         { q: 'why?', group: 'no-group', date: '1' }, 
         { q: 'what?', group: 'group 1', date: '1' }, 
         { q: 'when?', group: 'group 2', date: '2' }, 
         { q: 'where?', group: 'group 1', date: '2' }, 
         { q: 'which', group: 'group 2', date: '3' }
     ],
     result = questions.reduce(function (r, a) {
         r[a.group] = r[a.group] || [];
         r[a.group].push(a);
         return r;
     }, {});
     /**
     more code here.
     Here I would put if statements that check for all condtions
     I have stated in the question below
     */
 }

I am trying to make a function that users call and groups questions in certain groups. Above you can see part of the code I have come up with.

I have a number of conditions on how I want to group the questions.

  1. No group can have less than two number of questions.
  2. 'no-group' means the question is not in any group. So 'no-group' can be just one question or all of them.
  3. Groups should be assigned by the earliest date in the questions of that group. For example 'group 1' earliest(in terms of date) question should be earlier than the earliest question in 'group 2' and this should also be readjusted when questions are regrouped or if a question is removed from a group.
  4. Grouped questions can be regrouped. And when doing so if any question is left in a group alone it should marked as 'no-group'.
  5. When assigning groups when 'group 1' is taken assign 'group 2', when 'group 2' is taken assign 'group 3' and so on.

The way I would do it is use if statements. But since the array of questions can have up to twenty questions and the groups can go from 'group 1', 'group 2'... to 'group 20', the number of if statements will become many.

I made a stackblitz to communicate better what I am trying to achieve. Is there a way I can use recursion to achieve what I want to achieve and avoid many if statements?

If there is something that is not clear kindly ask I will be glad to make it clear.

The code in the stackblitz is as follows (it is an Angular stackblitz):

the controller

  questions = [
    { _id:1, q: 'why?', group: 'no-group', date: '1', selected:false }, 
    { _id:2, q: 'what?', group: 'group 1', date: '1', selected:false }, 
    { _id:3, q: 'when?', group: 'group 2', date: '2', selected:false }, 
    { _id:4, q: 'where?', group: 'group 1', date: '2', selected:false }, 
    { _id:5, q: 'which?', group: 'group 2', date: '3', selected:false }
  ];

  selectOrUnselectQuestion(question){
    let newQuestions = this.questions.map(newQuestion => {
      if(newQuestion._id === question._id){
        if(!newQuestion.selected){
            newQuestion.selected = true;
          } else {
            newQuestion.selected = false;
          }
        return newQuestion;
        } else {
          return newQuestion;
        }
      })
      this.questions = newQuestions; 
  }

  groupSelectedQuestions(){
    let selectedQuestions = this.questions.filter(q => q.selected);
    let selectedQuestionIds = selectedQuestions.map(selectedQuestion=>{ return selectedQuestion._id; })
    let newQuestions = this.questions.map(question => {
      if(selectedQuestions.length==1 && selectedQuestionIds.includes(question._id)){
        question.group = 'no-group';
        question.selected = false;
        return question
      } else 
      if(selectedQuestions.length>1 && selectedQuestionIds.includes(question._id)){
        question.group = 'group 1';
        question.selected = false;
        return question
      } else {
        return question;
      }
    })
    this.questions = newQuestions;

    // deselect selected questions

  }

the view:

<div style="text-align:center">Questions</div>

<div style="text-align:center; padding:10px;">

    <div *ngFor="let question of questions" (click)="selectOrUnselectQuestion(question)"
        [ngClass]="{'selected': question.selected}" class="question">
        <span style="padding-right:10px">{{question.q}}</span>
        <span>{{question.group}}</span>
    </div>

    <button (click)="groupSelectedQuestions()" style="margin:10px 0" type="button">
    group selected questions
  </button>

</div>
10
  • You should use lodash groupBy function for that Commented Dec 7, 2020 at 15:01
  • @NinaScholz What about now? I have logged in to my stackblitz so that the link can work on all devices. Commented Dec 7, 2020 at 15:18
  • @NinaScholz I have updated the question Commented Dec 7, 2020 at 15:31
  • I think this link will now work. Kindly try it and let me know if it does. Commented Dec 7, 2020 at 15:38
  • now the link works. Commented Dec 7, 2020 at 15:49

1 Answer 1

2

I'm afraid that the discussion in the comments did not do much to help me understand.

Here is an attempt that still guesses at some of your requirements:

// utility functions
const groupBy = (prop) => (xs) => 
  xs .reduce (
    (a, {[prop]: p, ...rest}) => ({...a, [p]: [...(a[p] || []), rest]}),
    {}
  )

const partition = (pred) => (xs) =>
  xs .reduce (([yes, no], x) => pred (x) ? [[...yes, x], no] : [yes, [...no, x]], [[], []])

// main function
const makeGroups = questions => {
  const {'no-group': groupless, ...rest} = groupBy ('group') (questions)
  const [largeEnough, tooSmall] = partition ((v) => v.length > 1) (Object .values (rest))
  const noGroup = [...groupless, ...tooSmall.flat()].sort((a, b) => a.date - b.date)
  return {
    ...Object .fromEntries (
      largeEnough
        .map (group => group.sort ((a, b) => a .date - b .date))
        .sort ((group1, group2) => group1 [0] .date - group2 [0] .date)
        .map ((group, i) => [`group ${i + 1}`, group])
    ),
    'no-group': noGroup
  }
}

// sample data
const questions = [
  {_id: 1, q: 'why?', group: 'no-group', date: '8', selected: false }, 
  {_id: 2, q: 'what?', group: 'A', date: '6', selected: false }, 
  {_id: 3, q: 'when?', group: 'C', date: '7', selected: false }, 
  {_id: 4, q: 'where?', group: 'A', date: '5', selected: false }, 
  {_id: 5, q: 'which?', group: 'B', date: '3', selected: false },
  {_id: 6, q: 'who?', date: '0', selected: false }, // no group supplied so will end up in no-group
  {_id: 7, q: 'why not?', group: 'B', date: '9', selected: false }, 
  {_id: 8, q: 'who, me?', group: 'A', date: '4', selected: false }, 
  {_id: 9, q: 'where is waldo?', group: 'A', date: '1', selected: false }, 
  {_id: 10, q: 'which way is up?', group: 'B', date: '2', selected: false },
  {_id: 11, q: 'when is lunch?', group: 'D', date: '10', selected: false }, 
];
// demo
console .log (makeGroups (questions))
.as-console-wrapper {max-height: 100% !important; top: 0}

The output will look like this:

{
  'group 1': [
    {_id: 9, q: "where is waldo?", date: "1", selected: false},
    {_id: 8, q: "who, me?", date: "4", selected: false},
    {_id: 4, q: "where?", date: "5", selected: false},
    {_id: 2, q: "what?", date: "6", selected: false}
  ],
  'group 2': [
    {_id: 10, q: "which way is up?", date: "2", selected: false},
    {_id: 5, q: "which?", date: "3", selected: false},
    {_id: 7, q: "why not?", date: "9", selected: false}
  ],
  'no-group': [
    {_id: 6, q: "who?", date: "0", selected: false},
    {_id: 3, q: "when?", date: "7", selected: false},
    {_id: 1, q: "why?", date: "8", selected: false},
    {_id: 11, q: "when is lunch?", date: "10",selected: false}
  ]
}

The groups are internally sorted by date, and the groups are sorted between them by the first date in their list. Any group without at least two entries is folded into no-group and the group numbers are assigned sequentially.

The big question is whether this fits your needs. If not, can you show a sample input and expected output as I have done here?

Update: inlining helpers

Although I'm a big fan of helper functions and I use that partition occasionally and a slightly generalized version of that groupBy quite frequently, I'd like to point out that as each is only used once, we could inline them quite simply:

const makeGroups = questions => {
  const {'no-group': groupless, ...rest} = questions .reduce (
    (a, {group = 'no-group', ...rest}) => ({...a, [group]: [...(a[group] || []), rest]}),
    {}
  )
  const [largeEnough, tooSmall] = Object .values (rest) .reduce (
      ([yes, no], x) => x.length > 1 ? [[...yes, x], no] : [yes, [...no, x]], [[], []]
  )
  const noGroup = [...groupless, ...tooSmall.flat()].sort((a, b) => a.date - b.date)
  return {
    ...Object .fromEntries (
      largeEnough
        .map (group => group.sort ((a, b) => a .date - b .date))
        .sort ((group1, group2) => group1 [0] .date - group2 [0] .date)
        .map ((group, i) => [`group ${i + 1}`, group])
    ),
    'no-group': noGroup
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this works. With this I will be able to achieve what I wanted.
@YulePale: glad to hear it. Please note in future questions that if you can create at least one input-output pair as I did here, it will make it easier for people to help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.