0

Note:

My need

I'm working in a project having a module which should suggest zero or one profile (person) to the logged user.

All profiles saved in the database (except the logged user & limit 100 & select randomly) are returned via an API (http://example.com/suggest) as a JSON result and the structure of each profile is like this:

{
name: <userName>,
age: <userAge | default 0>,
language: <userlanguage | default en>,
hobbies: <anArrayOfHobbies>
}

The objectif is to suggest to the current logged user the "most relevent profile" based on:

  • Properties (in my case only age, language and hobbies).

  • How much the property is important for the logged user.

Example

Let's say that the API returned this result:

enter image description here

and we suppose that the logged user has this profile

enter image description here

This user can use an internal form to search for the "most relevant profile". This form let him/her associate percents of importance to each property (SUM should be equal 100%).

  • Case 1

enter image description here

I'm looking for the profile who has the same age (the closest one) as me.

The intended result is {name: "Jane Doe", ...}

  • Case 2

enter image description here

The most important for me in this case is the language.

The intended result is {name: "Jane Roe", ...}

  • Case 3

enter image description here

The most important in this case is the number of hobbies that a profile share with me

The intended result is {name: "Jane Doe", ...}

What already did

This is what I already developed:

var Profile = {
 name: "",
 age: 0,
 language: "en",
 hobbies: [],
 
 getmostRelevantProfile: function(listOfProfiles){
  var mostRelevantProfile = {
   age: {value: null, profile: null},
   language: [],
   hobbies: []
  };
  listOfProfiles.forEach((profile)=>{
   //console.info(profile);
   //No need to check profile.name
   
   //Check relevent profile.age
   var differencAgeBetweenMeAndCurrentProfile = this.age - profile.age;
   if(differencAgeBetweenMeAndCurrentProfile<0){
    differencAgeBetweenMeAndCurrentProfile *= -1;
   }
   var differencAgeBetweenMeAndmostRelevantProfile = this.age - mostRelevantProfile.age.value;
   if(differencAgeBetweenMeAndmostRelevantProfile<0){
    differencAgeBetweenMeAndmostRelevantProfile *= -1;
   }
   if( mostRelevantProfile.age.value === null  || differencAgeBetweenMeAndCurrentProfile < differencAgeBetweenMeAndmostRelevantProfile ){
    mostRelevantProfile.age.value = profile.age;
    mostRelevantProfile.age.profile = profile;
   }
   
   
   //Check if this profile speakes the same language as me
   if( profile.language === this.language ){
    mostRelevantProfile.language.push(profile);
   }
   
   //Check if I'm sharing some hobbies with this profile
   if(this.hobbies.some((hobby)=>{
    if( profile.hobbies.indexOf(hobby) > -1 ){
     return true;
    }
    return false;
   })){
    mostRelevantProfile.hobbies.push(profile);
   }
   
  });
  
  //If at least one profile saved, return it
  if( mostRelevantProfile.age.value !== null ){
   return mostRelevantProfile;
  }
  return null;
 }
}

var me = Object.create(Profile);
me.name = "John Doe";
me.age = 77;
me.language = "es";
me.hobbies = ["music", "boating", "cooking", "drawing"];

var johnRoe = Object.create(Profile);
johnRoe.name = "John Roe";
johnRoe.age = 20;
johnRoe.language = "fr";
johnRoe.hobbies = ["basebal"];

var janeDoe = Object.create(Profile);
janeDoe.name = "Jane Doe";
janeDoe.age = 43;
janeDoe.language = "de";
janeDoe.hobbies = ["stronomy", "music", "drawing"];

var janeRoe = Object.create(Profile);
janeRoe.name = "Jane Roe";
janeRoe.age = 76;
janeRoe.language = "es";
janeRoe.hobbies = ["stronomy", "music", "walking"];

var mostRelevantProfileForMe = me.getmostRelevantProfile([johnRoe, janeDoe, janeRoe], {age: 13, language: 64, hobbies: 23});
console.info("mostRelevantProfileForMe:", mostRelevantProfileForMe);

What is missing (my question)

  • How can I add the percent of importance to select the right profile and then how can I filter the mostRelevantProfileForMe to keep only "the right" profile? Is there a better approach to achieve this task?
Community
  • 1
  • 1
Ala Eddine JEBALI
  • 7,033
  • 6
  • 46
  • 65
  • Why don't you use a set of arrays that define percentages? e.g. Age_Difference = { 0:100%, 1:99%, 2:98 }; etc and then do something similar for the rest, then all you need to do is count and reference the array? and for hobbies, set an array of percentage importance for each hobby, calculate the total and then the score, then create your percentage... – krisph Jan 31 '17 at 15:49
  • I don't quite get the need for percentages here? "_The most important for me in this case is ..._" could be accomplished by choosing the order of importance: 1. ..., 2. ..., 3. ... - How does the percentage affect the selection if I have set 90% for language? Either the languages match or not... – Andreas Jan 31 '17 at 15:57
  • @andreas when I set language to 90% and there are 2 profiles. The first one speak the same language as me and the second has the same age of me and no hobby is found in both of them. The result should be the first profile because of the 90% – Ala Eddine JEBALI Jan 31 '17 at 16:16
  • The percent is what it interest me the most – Ala Eddine JEBALI Jan 31 '17 at 16:17

1 Answers1

2

The main problem i see there is the forEach method, which does all sorts of changes to an external object, and not all of those are consistent (like pushing a matching language to the array, looks like an error to me).

I would go about the problem in a different way. First of all it looks like you want to rank (ie. sort) the possible matches based on a scoring system. So why not use Array.sort which is a natural fit for the job.

Then we need the scoring algorithm, which needs to score the three different properties of each match, then scale each one down based on the predefined bias, and then combine the three values together, let say by averaging the values.

Since i don't like methods that access/modify objects outside their direct scope, let see if we can build something that compose a scoring function and then passes it around to evaluate the matches.

Here's a possible implementation:

var me = {};
me.name = "John Doe";
me.age = 39;
me.language = "es";
me.hobbies = ["music", "boating", "cooking", "drawing"];

var johnRoe = {};
johnRoe.name = "John Roe";
johnRoe.age = 20;
johnRoe.language = "fr";
johnRoe.hobbies = ["basebal"];

var janeDoe = {};
janeDoe.name = "Jane Doe";
janeDoe.age = 43;
janeDoe.language = "de";
janeDoe.hobbies = ["stronomy", "music", "drawing"];

var janeRoe = {};
janeRoe.name = "Jane Roe";
janeRoe.age = 76;
janeRoe.language = "es";
janeRoe.hobbies = ["stronomy", "music", "walking"];

// helper functions for the scoring system    

function getAgeScore(target, match) {
  // return value in range [0,1] with 1 when same age
  return 1 / (1 + Math.abs(target.age - match.age))
}

function getLanguageScore(target, match) {
  // 1 if same language, 0 if different
  return target.language == match.language
}

function getHobbiesScore(target, match) {
  // scores in range [0,1] based on how many matching hobbies
  return target.hobbies.reduce((count, hobby) => count + match.hobbies.indexOf(hobby) > 0, 0) / target.hobbies.length
}

function getScaledScoring(target, bias) {
  return function(match) {
    var ageScore = getAgeScore(target, match) * bias.age / 100
    var languageScore = getLanguageScore(target, match) * bias.language / 100
    var hobbyScore = getHobbiesScore(target, match) * bias.hobbies / 100
    return (ageScore + languageScore + hobbyScore) / 3
  }
}

function findBest(profiles, matcher) {
  if (profiles == null) return null
  var copy = profiles.slice(); // don't want to change the original list
  copy.sort(function(a, b) {
    return matcher(b) - matcher(a);
  })
  return copy[0] // get the first one ie. the best
}

var bias = {
  age: 3,
  language: 8,
  hobbies: 89
}
var matcher = getScaledScoring(me, bias) // generates the scoring method based on myself and the bias object

var best = findBest([johnRoe, janeDoe, janeRoe], matcher)

console.log("find best match for", me)
console.log("using bias", bias)
console.log("Result:", best)

Note: I made a few changes from your original code in term of inheritance etc, just to make it simpler to follow


Bonus tip:

With sorting you guarantee there will always be at least one match, no matter how 'far' from the reference point (me).

If you'd rather have only matches above a certain score, you can replace sort with filter and add a threshold parameter to weed out profiles under a certain score.

alebianco
  • 2,475
  • 18
  • 25