Home > Software engineering >  Javascript how to compare two arrays and get the values which were closest to each other
Javascript how to compare two arrays and get the values which were closest to each other

Time:04-05

I have two arrays:

let arr1 = [6, 10, 7, 7] 
let arr2 = [6, 16, 20, 9]

Each value in arr1 represents the score of a question, where each value in arr2 represents the max score of the question. So the values are kind of connected. For example question 1 the score was 6 where the max score was also 6, question 2 gave 10 points where the max score could have been 16 etc.

Now I want to compare the values and get the 3 values, which were closest to each other. So in this case it would question 1: 6 out of 6 (0 difference), question 2: 10 out of 16 (difference of 6) and question 4: 7 out of 9 (difference of 2) and maybe sort them by difference?

How can I achieve that?

CodePudding user response:

I would create a new array with the info in arr1, arr2 and also the difference. If you do that, sorting and selecting the top 3 elements become rather trivial:

let arr1 = [6, 10, 7, 7] 
let arr2 = [6, 16, 20, 9]

// create a new array with an object for each pair in arr1 and arr2
let zipped = []
for (let i = 0; i < arr1.length; i  ) {
    zipped.push({
        score: arr1[i],
        max: arr2[i],
        diff: arr2[i] - arr1[i]
    })
}

// sort the array by diff
zipped.sort((a, b) => a.diff - b.diff)

// print out the first 3 elements
console.log(zipped.slice(0, 3))

CodePudding user response:

I would start by combining all relevant data for each question together in one object. Instead of two separate arrays we could have one combined array. Depending on the info you need after the operation you might want to include/omit certain info in this object.

If you need the index of the question after the operation, you'll probably want to store it ahead of time. If you want to sort based on the difference between the max score and actual score, it's faster to calculate this ahead of time.

A fairly easy way to collect your data would be to use map():

const combined = arr1.map((score, index) => {
  const max   = arr2[index];
  const diff  = max - score;
  const ratio = score / max;
  return { score, max, index, diff, ratio };
});

After collecting the necessary data you can sort() your array based on your criteria. For example:

combined.sort((a, b) => a.diff - b.diff);

After sorting the data you can grab the first 3 elements using. slice().

// Takes element 0 up to but not including 3 and put them in a new array.
const result = combined.slice(0, 3);
console.log(result);

Alternatively you could also use splice() to mutate combined, removing all unwanted elements.

// Removes all elements starting at element 3.
combined.splice(3);
console.log(combined);

const actualScores = [6, 10,  7, 7];
const maxScores    = [6, 16, 20, 9];

const scores = actualScores.map((score, index) => {
  const max   = maxScores[index];
  const diff  = max - score;
  const ratio = score / max;
  return { score, max, index, diff, ratio };
});

// sort by ratio descending
scores.sort((a, b) => b.ratio - a.ratio);
// take elements 0-3 (excluding) from `scores`
const scoresA = scores.slice(0, 3);

// sort by diff ascending
scores.sort((a, b) => a.diff - b.diff);
// take elements 0-3 (excluding) from `scores`
const scoresB = scores.slice(0, 3);

// remove all elements with index 3 or higher
scores.splice(3)

console.log("first 3 scores ordered by ratio descending",        scoresA);
console.log("first 3 scores ordered by diff ascending (slice)",  scoresB);
console.log("first 3 scores ordered by diff ascending (splice)", scores);

CodePudding user response:

From the above comment ...

"The items which are closest to each other are not determined by the minimum difference but by the quotient which is closest/equal to the maximum value of 1."

... thus, one could choose a reduce based approach which creates a rated score list by creating and collecting rated score items from the corresponding values of each array, the one with the scores achieved and the one with the achievable scores.

Sorting then becomes a piece of cake since every rated score item does feature following properties ... achieved, achievable, scoreDelta and rating. The advice is to sort by a descending rating. But of cause the OP can always sort by an ascending scoreDelta.

function createAndCollectRatedScoreItem({ achievables, result }, achieved, idx) {
  const achievable = achievables[idx];
  result.push({
    rating: achieved / achievable,
    achieved,
    achievable,
    scoreDelta: achievable - achieved,
  });
  return { achievables, result };
}
const achievedScoreList = [6, 10, 7, 7, 1];
const achievableScoreList = [6, 16, 20, 9, 2];

const ratedScoreList = achievedScoreList
  .reduce(
    createAndCollectRatedScoreItem,
    { achievables: achievableScoreList, result: [] }
  )
  .result
  .sort((a, b) => b.rating - a.rating);
 
const bestRatedScoreTripple = ratedScoreList.slice(0, 3);

console.log({ ratedScoreList, bestRatedScoreTripple });
console.log(
  'rated score list sorted by ascending score delta ...',
  ratedScoreList.sort((a, b) => a.scoreDelta - b.scoreDelta)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Another approach which enables re-usable code utilizes map in combination with its 2nd thisArg parameter.

The implementation of the above code example then changes slightly to ...

function createRatedScoreItemFromBoundAchievableScores(achieved, idx) {
  const achievable = this[idx];
  return {
    rating: achieved / achievable,
    achieved,
    achievable,
    scoreDelta: achievable - achieved,
  }
}
const achievedScoreList = [6, 10, 7, 7, 1];
const achievableScoreList = [6, 16, 20, 9, 2];

const ratedScoreList = achievedScoreList
  .map(
    createRatedScoreItemFromBoundAchievableScores,
    achievableScoreList
  )
  .sort((a, b) => b.rating - a.rating);
 
const bestRatedScoreTripple = ratedScoreList.slice(0, 3);

console.log({ ratedScoreList, bestRatedScoreTripple });
console.log(
  'rated score list sorted by ascending score delta ...',
  ratedScoreList.sort((a, b) => a.scoreDelta - b.scoreDelta)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

CodePudding user response:

You can create a third array diff[] containing the difference between arr2 and arr1:

let diff = [];
arr2.forEach( (o, i) => diff.push(o-arr1[i]) );

CodePudding user response:

let arr1 = [6, 10, 7, 7] 
let arr2 = [6, 16, 20, 9]

function difference(a,b) {
 const diff = [];
 for(let i = 0; i < a.length; i  ) {
  diff[i] = b[i]-a[i];
 }
 return diff;
}

console.log(difference(arr1,arr2).sort((a,b) => a-b).slice(0,3))
  • Related