0

I am looking for a way to search for a name within an array, including situations where the search input (of multiple words: first name, surname) may be reversed.

The array would look like this with a series of names.

const names = ['Alan Hope', 'Greg Day', 'Alan Peters']

The search input could be as follows 'peter Al'

What would the code look like to achieve this. This is what I have so far and I know it is totally wrong.


const studentNames = ['Alan Hope', 'Greg Day', 'Alan Peters']

function search () {
  const bankingSheet = ss.getSheetByName('Banking')
  const searchInput = 'Hope Al'
  const searchWords = searchInput.split(/\s+/)

  const filtered = studentNames.filter(function(name) {
    searchWords.every(function(word) {
      return name.toString().toLowerCase().indexOf(word) !== -1
    })  
})
Logger.log(filtered)
}

From what I gather I need to first split the search input into the constituent words. I then need to filter through the names array. For each name in the array I need to check if all search words appear in some way in the name. I think this may involve the every method.

For each name, if the return value is true that is what I need to return.

Is this thinking correct?

Here is what I was hoping to replicate from a Youtube video I found along the same lines

Thank you in advance! This is really hurting my head at the moment!

TheMaster
  • 45,448
  • 6
  • 62
  • 85
lmcc
  • 63
  • 7
  • 1
    Searching for "Hope Al" from the given array lifts the requirements to a new level, please check [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance); – Teemu Dec 06 '21 at 17:32
  • @Teemu That sounds really complicated after reading. Is this question too big an ask for an amateur like me? – lmcc Dec 06 '21 at 17:36
  • I can't answer that question, you've to define your skills by yourself. You could create RegExps on fly, and check if the keywords are partially matching the data. But, that's very error prone, and it's easy to get false positives. I'd recommended to use an unique id or something like that. – Teemu Dec 06 '21 at 17:49

2 Answers2

1

The idea is to define what's meant by a match. The simplest and most rigid match is simple string equality. A softer match would tolerate case differences. Softer still would be a tolerance for a first name / last name inversion. (shown in the snippet).

Softest of all would be a match that tolerates small differences in the names (a reordering would not register as a small difference via a levenshtein check), unless we compared distances between individual tokens).

const names = ['Alan Hope', 'Greg Day', 'Alan Peters']

function softMatch(nameA, nameB) {
  if (nameA === nameB) return true;  

  const reverse = name => name.split(' ').reverse().join(' ')
  
  const lcA = nameA.toLowerCase();
  const lcB = nameB.toLowerCase();
  
  if (lcA === lcB) return true; // case insensitive
  if (reverse(lcA) === lcB) return true;  // order and case insensitive
  return false
}

let matches = names.filter(name => softMatch(name, 'peters Alan'))
console.log(matches)

matches = names.filter(name => softMatch(name, 'No Match'))
console.log(matches)

If names are to have more than two subnames, and any ordering is a match, then a match can be implemented as follows...

const names = ['Alan Randolph Hope', 'Greg Herbert Walker Day', 'Alan Jefferson Peters']

function softMatch(nameA, nameB) {
  let subnamesA = nameA.split(' ').map(n => n.toLowerCase())
  let subnamesB = nameB.split(' ').map(n => n.toLowerCase())
  
  // sort lexically and compare
  subnamesA = subnamesA.sort();
  subnamesB = subnamesB.sort();

  return subnamesA.every(function(element, index) {
    return element === subnamesB[index]; 
  });
}

let matches = names.filter(name => softMatch(name, 'peters Alan jefferson'))
console.log(matches)

matches = names.filter(name => softMatch(name, 'No Match'))
console.log(matches)
danh
  • 62,181
  • 10
  • 95
  • 136
  • I have attached a picture of a similar solution from a Youtube video. Is this possible similar to the solution or is your suggestion the only way? – lmcc Dec 06 '21 at 18:07
  • My suggestion and the image you posted agree in the sense that idea is to filter the list with a predicate that defines what a match means. The youtube image appears to represent the sub-names as an array and answers true for any ordering of N names. I'll edit to clarify. – danh Dec 06 '21 at 18:17
  • @LiamMcCormick - edited. In your application, the names list to match against might be an array of columns, so no split needed. Just convert to lower case and sort. (sort, IMO, makes the code more readable than every / some) – danh Dec 06 '21 at 18:32
1

Alternative Solution:

You may also try this sample implementation below to find the matched name even when the search input is reversed or surname/first name is abbreviated (e.g. Alan H. or Al Hope) for a more flexible search method:

const studentNames = ['Alan Randolph Hope', 'Greg Herbert Walker Day', 'Alan Jefferson Peters']

function findMatch(){
 var match = run("Hope Al"); 
 console.log(match);
 console.log(match.length>0);
}

function run(searchString) {
  var res = [];
  let findDuplicates = arr => arr.filter((item, index) => arr.indexOf(item) != index);
  var searches = searchString.split(" ");
  searches.forEach(search=>{
    studentNames.forEach(name =>{
      if(name.toLowerCase().includes(search.toLowerCase())){
        res.push(name);
      }        
    });
  });
  return [...new Set(findDuplicates(res))];
}

Sample demonstration:

  • Search term is Hope Al:

enter image description here

  • Search term is Greg D:

enter image description here

Reference:

SputnikDrunk2
  • 3,398
  • 1
  • 5
  • 17