How to group objects in an array and sort them depending on an object property

强颜欢笑 提交于 2021-02-17 04:30:53

问题


 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>

回答1:


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
  }
}


来源:https://stackoverflow.com/questions/65184098/how-to-group-objects-in-an-array-and-sort-them-depending-on-an-object-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!