3

I have a list object which I load from a web service. My list comes in this form:

list = [ {id:"name1 Surname1" , nb:120} ,
         {id:"name4 Surname4" , nb:120} ,
         {id:"name2", nb:632} , 
         {id:"name3Surname3", nb:102} , 
         {id:"surname4", nb:896} 
       ] 

My searching strategy is to go by "id". As you can see I have different formats:

separated name-surname with space, only name, only surname.

My purpose is to search member by id (its name or surname values). My result should be items whose IDS are containing my searched value item , even if name and surname inversed : the ordern of names and surnbames may be changefull

For example :

  • If I search for : "name1" -> result : name1 Surname1
  • If I search for : "surname4 name4" -> result : id:"name4 Surname4" , nb:120
  • If I search for : "name1 Surn" -> result : name1 Surname1
  • If I search for : "Surn name1" -> result : name1 Surname1
  • If I search for : "name1 SurAAA" -> result : (nothing)
  • If I search for : "AAAA Surname1" -> result : (nothing)
  • If I search for : "name1Surname1" -> result : (nothing)
  • If I search for : "name" -> result : name1 Surname1 , name2 , name3Surname3 , surname4

To summarize :

I should search items:

  • corresponding for my searched item
  • beginning with my searched item

I have tried the RegExp solution, like the following:

findPhones(written) {
    this.splits = written.split(' ');
    if (this.splits[0] === "") // Case of empty string
    {
      return this.filteredPhones = [];
    }
    this.filteredResult = List.filter((item) => {
      const regex = new RegExp(written, 'gi');  // g : global & i: insentisive case
      return item.List.match(regex);
    }); 

But this solution is fine , but it lacks the condition of inversed "name surname" as it may be inversed :

Ex: when i search for "surname4 name4" : i get 0 result ; but in reality i should catsh the second object of my list as a good result :

{id:"name4 Surname4" , nb:120}

(it seems that i should combine two filter actions )

Any suggestions to fix it ?

firasKoubaa
  • 4,824
  • 15
  • 46
  • 104
  • Easiest option is probably to split the search on space and then generate regex like `(?=.*surname4)(?=.*name4).*`, so creating `(?=.*X)` where `X` is the regex escaped string. – ctwheels Nov 21 '17 at 17:02
  • Sure be a lot easier if you control the data structure and you split them before storing them – charlietfl Nov 21 '17 at 17:03
  • @ctwheels : if there is an example going with my case , may be clearer – firasKoubaa Nov 21 '17 at 17:03
  • @firasKoubaa I've added 2 options below in my answer. The first option uses regex while the second does not. The method that doesn't use regex is the better method. – ctwheels Nov 21 '17 at 18:34

3 Answers3

0

Code

Option 1 - Regex

Note: The code below uses the EscapeRegExp function. This function was found in this answer. JavaScript doesn't have its own regex escape function like other languages do (such as Python). There may better methods that you can use, but I've used this code as a basis of implementation. You can always replace the function with another if you wish.

list = [
  {id: "name1 Surname1", nb: 120},
  {id: "name4 Surname4", nb: 120},
  {id: "name2", nb: 632},
  {id: "name3Surname3", nb: 102},
  {id: "surname4", nb: 896}
];

function Search(s) {
  var searches = s.value.split(" ").filter(String);
  var regex = GenerateRegExp(searches);
  console.log(regex);
  var matches = list.filter((i) => {
    const r = new RegExp(regex, 'gi');
    return r.test(i.id);
  });
  console.log(matches);
}

function GenerateRegExp(arr) {
  var s = "";
  arr.forEach(function(e) {
    s += "(?=.*" + EscapeRegExp(e) + ")";
  });
  return s += ".*";
}

function EscapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
<input id=search type=search onkeyup="Search(this)" />

Option 2 - Filters

This option is much more robust than the regex option above.

list = [
  {id: "name1 Surname1", nb: 120},
  {id: "name4 Surname4", nb: 120},
  {id: "name2", nb: 632},
  {id: "name3Surname3", nb: 102},
  {id: "surname4", nb: 896}
];

function Search(s) {
  var searches = s.value.split(" ").filter(String);
  console.log(all(searches, list));
}

function all(needles, haystacks) {
  return haystacks.filter((h) => {
    var string = h.id.toString().toLowerCase();
    var a = needles.filter((e) => {
      if (string.indexOf(e) !== -1) return e;
    });
    if (a.length === needles.length) return h;
  });
}
<input id=search type=search onkeyup="Search(this)" />
ctwheels
  • 19,377
  • 6
  • 29
  • 60
  • when i test with "surname4 name4" i should get only one result which is the first "name4 Surname4" , i shoul not also have "surname4" – firasKoubaa Nov 21 '17 at 21:44
  • @firasKoubaa why? `name4` also matches that string. – ctwheels Nov 21 '17 at 22:02
  • my searched string (example : Surname4 name4) should correspond completly to one of my list objects even if the order of words is different – firasKoubaa Nov 21 '17 at 22:11
  • @firasKoubaa it does: `surname4` is matched both by `surname4` (entirely) and `name4` (partially). I'm not quite sure I understand the problem otherwise, would you be able to explain why this shouldn't match? – ctwheels Nov 21 '17 at 22:13
0

Following should do what you need. It creates an array of matches by checking each term in the split input, then checks that the split input array length is same as the matches array length

const list =[ {id:"name1 Surname1" , nb:120} ,
     {id:"name4 Surname4" , nb:120} ,
     {id:"name2", nb:632} , 
     {id:"name3Surname3", nb:102} , 
     {id:"surname4", nb:896} 
   ] 


const findPhones = function(written) {
  const splits = written.trim().toLowerCase().split(' ').filter(w => w);
  return list.filter(o => {
    const names = o.id.split(' ');
    const matches = splits.filter(w => names.some(s => s.toLowerCase().includes(w)))
    return names.length >= splits.length && splits.length === matches.length;
  });
}


const terms = [
  "name1", "surname4 name4", "name1 Surn", "Surn name1", "name1 SurAAA", "AAAA Surname1", "name1Surname1", "name"
];

terms.forEach(term=>{
  console.log('Term: ', term)
  console.log('Matches:', JSON.stringify(findPhones(term), null, ' '))
})
.as-console-wrapper {top:0; max-height:none!important;height:100%;}
abi tom
  • 3
  • 3
charlietfl
  • 164,229
  • 13
  • 110
  • 143
0

@charlietfl 's code edited to make search case insensitive, when search has first and last name not to return if one word matches both first and last (eg: search term surname4 name4 not to return nb:896)

const list =[ {id:"name1 Surname1" , nb:120} ,
 {id:"name4 Surname4" , nb:120} ,
 {id:"name2", nb:632} , 
 {id:"name3Surname3", nb:102} , 
 {id:"surname4", nb:896} 
] 


const findPhones = function(written) {
    const splits = written.trim().toLowerCase().split(' ').filter(w => w);
    return list.filter(o => {
        const names = o.id.split(' ');
        const matches = splits.filter(w => names.some(s => s.toLowerCase().includes(w)))
        return names.length >= splits.length && splits.length === matches.length;
    });
}


const terms = [
  "name1", "surname4 name4", "name1 Surn", "Surn name1", "name1 SurAAA", "AAAA Surname1", "name1Surname1", "name"
];

terms.forEach(term=>{
  console.log('Term: ', term)
  console.log('Matches:', JSON.stringify(findPhones(term), null, ' '))
})
abi tom
  • 3
  • 3