1

My javascript array

const response = [
{
    "userId": "1",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "1",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "2",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "2",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "3",
    "questionId": "1",
    "answeredIndex": 0
},
{
    "userId": "3",
    "questionId": "2",
    "answeredIndex": 3
},
{
    "userId": "4",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "4",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "5",
    "questionId": "1",
    "answeredIndex": 0
},
{
    "userId": "5",
    "questionId": "2",
    "answeredIndex": 0
}]

I am looking for a solution which will return an array of userIds. Each user has answered two questions(questionId: 1 and questionId: 2). I want the users who have answered both questions same. So, I want to filter my array with an AND condition like below :

if user selected answeredIndex: 1 for questionId: 1 AND if also the same user selected answeredIndex: 0 for questionId: 2 then, I want this user to be pushed in the result array.

I tried below code but it is not working unfortunately.

const targetUsers: string[] = [];
response.forEach((feedback) => {
  if ((feedback.questionId === '1' && feedback.answeredIndex === 1) ||
       feedback.questionId === '2' && feedback.answeredIndex === 0) {
    targetUsers.push(feedback.userId);
  }
});
console.log([...new Set(targetUsers)]);

My Expected output should be like below:

[
{
    "userId": "1",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "1",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "2",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "2",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "4",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "4",
    "questionId": "2",
    "answeredIndex": 0
}]

So the combination of attribute pairs (questionId, answeredIndex) should be (1,1) AND (2,0) for each user, only then the user will be considered. Will highly appreciate if anyone helps me out here. Thanks in advance.

1
  • why do you have different types for ID? by using numerical value, you could use for all ID a numerical value, but not irritating mixed types. Commented Apr 12, 2021 at 19:51

3 Answers 3

1

You could take a single loop approach for the answer off all users. Then take only the ones who have all correct answered the questions.

This approach counts right answers and filter later by the needed count.

const
    response = [{ userId: "1", questionId: "1", answeredIndex: 1 }, { userId: "1", questionId: "2", answeredIndex: 0 }, { userId: "2", questionId: "1", answeredIndex: 1 }, { userId: "2", questionId: "2", answeredIndex: 0 }, { userId: "3", questionId: "1", answeredIndex: 0 }, { userId: "3", questionId: "2", answeredIndex: 3 }, { userId: "4", questionId: "1", answeredIndex: 1 }, { userId: "4", questionId: "2", answeredIndex: 0 }, { userId: "5", questionId: "1", answeredIndex: 0 }, { userId: "5", questionId: "2", answeredIndex: 0 }],
    answers = { 1: 1, 2: 0 },
    temp = response.reduce((r, { userId, questionId, answeredIndex }) => {
        r[userId] ??= 0;
        r[userId] += answers[questionId] === answeredIndex;
        return r;
    }, {}),
    targetUsers = Object
        .keys(temp)
        .filter((count => k => temp[k] === count)(Object.keys(answers).length));
 
console.log(targetUsers);

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks @Nina Scholz, I am accepting your answer, short and nice...cheers...
Should the ; after the temp declaration be a , ? (Line 8)
@BenStephens, it should. ty
1

It's trick because the filter depends on multiple items in the array. The easier to undertand way to solve this is to keep the information of the previous answers we already iterated over:

const response = [
{
    "userId": "1",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "1",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "2",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "2",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "3",
    "questionId": "1",
    "answeredIndex": 0
},
{
    "userId": "3",
    "questionId": "2",
    "answeredIndex": 3
},
{
    "userId": "4",
    "questionId": "1",
    "answeredIndex": 1
},
{
    "userId": "4",
    "questionId": "2",
    "answeredIndex": 0
},
{
    "userId": "5",
    "questionId": "1",
    "answeredIndex": 0
},
{
    "userId": "5",
    "questionId": "2",
    "answeredIndex": 0
}]


//object to make it easier to add mulitple questions
const rightQuestions = {
    "1": 1,
  "2": 0
}

//create a map to use as reference for each user
const usersToKeep = new Map()

response.forEach(item => {
    
  //if it's false, it has already an wrong answer
    if(usersToKeep.get(item.userId) === false) {
    return
  }
  
  // from here it either has not responded yet (undefined), or has a right answer (true)
  
    if(item.answeredIndex === rightQuestions[item.questionId]) {
    //the answer is valid
    usersToKeep.set(item.userId, true)
  } else {
        //the answer is not valid
    usersToKeep.set(item.userId, false)
  }
})

//now filter for the users we should keep
const results = response.filter(item => usersToKeep.get(item.userId))

//clear the map (not needed if the filter is inside a funcion)
usersToKeep.clear()


6 Comments

use for..of loop, and use break keyword in else block, remove conditional return statement
won't the use of break possibly prevent the loop to iterate other users answers?
oo, I never thought that,
@Nur , would continue do what you were thinking? What's your reason for preferring for..of over .forEach? Just curious.
.forEach use function, so its less perform and there is no break keyword , What if there is 10k items! But only 2 questions need to check... then break the loop!
|
1

Not sure if this is a goof idea or not but it might allow for complex filters:

const responses = [
  { "userId": "1", "questionId": "1", "answeredIndex": 1 },
  { "userId": "1", "questionId": "2", "answeredIndex": 0 },
  { "userId": "2", "questionId": "1", "answeredIndex": 1 },
  { "userId": "2", "questionId": "2", "answeredIndex": 0 },
  { "userId": "3", "questionId": "1", "answeredIndex": 0 },
  { "userId": "3", "questionId": "2", "answeredIndex": 3 },
  { "userId": "4", "questionId": "1", "answeredIndex": 1 },
  { "userId": "4", "questionId": "2", "answeredIndex": 0 },
  { "userId": "5", "questionId": "1", "answeredIndex": 0 },
  { "userId": "5", "questionId": "2", "answeredIndex": 0 }
];

// intersection from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
function intersection(setA, setB) {
    let _intersection = new Set()
    for (let elem of setB) {
        if (setA.has(elem)) {
            _intersection.add(elem)
        }
    }
    return _intersection
}

const matchers = [
  (item) => item.questionId === '1' && item.answeredIndex === 1,
  (item) => item.questionId === '2' && item.answeredIndex === 0,
];

const matching_users = matchers.map(
  (matcher) =>
    responses.reduce(
      (acc, response) => matcher(response) ? acc.add(response.userId) : acc,
      new Set()
    )
).reduce(
  (acc, set) => intersection(acc, set)
);

const result = responses.filter((item) => matching_users.has(item.userId));

console.log(result);

To explain what it (hopefully) does:

For each matcher it checks each response to see if it matches the condition, and if it does it adds it to a set containing the user ids. Then it finds the common elements of the user ids sets to find the user ids that matched all the matchers. Finally it filters the responses based on the list of user ids.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.