JS function switch between either greater than or less than - javascript

I have a function that is quite long and at the moment I need a duplicate of the function where the only difference is it asks greater than rather than less than. So the only difference is > or <.
Is there any (non messy) way to make this kind of function just one function instead of the two here?
function(value, modifier) {
let result;
if (value > modifier) result = 10;
return result;
}
function(value, modifier) {
let result;
if (value < modifier) result = 10;
return result;
}
So basically I need a conditional greater than/less than sign.
EDIT: To be clear, in an ideal world I'd like to do this:
myFunction(10, 5, >)
EDIT: Adding one part of my actual function to make things clearer. I was hoping there might be real simple way to do it but it seems like perhaps not so maybe part of the actual function might help things:
function getEdges(colour) {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData(x,y);
if (data[0] < colour) {
edge.push(y);
break;
}
}
}
return edge;
}
And there is another almost identical function like this where the only difference is the line if (data[0] > colour) { is greater than rather than less than.

If the only difference between the two functions is the comparison, then you can just extract that and make it a parameter
function getEdges(colour, comparator) {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData();
if (comparator(data[0], colour)) {
edge.push(y);
break;
}
}
}
return edge;
}
//call with less than
getEdges(someColour, (a, b) => a < b)
//call with greater than
getEdges(someColour, (a, b) => a > b)
You can also keep your logic in one function and derive two more from it. This way you don't need to maintain multiple code blocks and you still get two explicit calls:
Using partial application with .bind:
function getEdges(comparator, colour) {
// ^ ^ the two parameters are swapped
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData();
if (comparator(data[0], colour)) {
edge.push(y);
break;
}
}
}
return edge;
}
//partial application
const getEdgesLessThan = getEdges.bind(null, (a, b) => a < b);
const getEdgesGreaterThan = getEdges.bind(null, (a, b) => a > b);
getEdgesLessThan(someColour)
getEdgesGreaterThan(someColour)
Using a curried function:
function getEdges(comparator) {
// ^---------
return function(colour) {// | taking two parameters
// ^ ----------
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData();
if (comparator(data[0], colour)) {
edge.push(y);
break;
}
}
}
return edge;
}
}
//currying
const getEdgesLessThan = getEdges((a, b) => a < b);
const getEdgesGreaterThan = getEdges((a, b) => a > b);
getEdgesLessThan(someColour)
getEdgesGreaterThan(someColour)

How about passing the condition function as a third parameter?
function getEdges(colour, condition) {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData(x,y);
if (condition(data[0], colour)) {
edge.push(y);
break;
}
}
}
return edge;
}
Call the function and pass required condition.
for lesser than: getEdges(color, (data, color) => color < data);
for greater than: getEdges(color, (data, color) => color > data);

EDIT: To be clear, in an ideal world I'd like to do this:
myFunction(10, 5, >)
You can get something very similar:
const lt = (x, y) => x < y;
const gt = (x, y) => x > y;
function foo(value, modifier, compare) {
let result;
if (compare(value, modifier)) result = 10;
return result;
}
console.log(foo(2, 3, lt)); // 10
console.log(foo(3, 2, gt)); // 10
Using your second example:
const lt = (x, y) => x < y;
const gt = (x, y) => x > y;
const width = 3;
const height = 3;
const getData = (x, y) => [height * x + y];
console.log(getEdges(3, lt)); // [2]
console.log(getEdges(3, gt)); // [2,2]
function getEdges(colour, compare) {
const edge = [];
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData(x, y);
if (compare(data[0], colour)) {
edge.push(y);
break;
}
}
}
return edge;
}
Hope that helps.

You could keep the operator and just swap the terms according to your needs:
function getEdges(colour, operator) {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData();
const [a, b] = operator == '<' ? [data[0], colour] : [colour, data[0]];
if (a < b) {
edge.push(y);
break;
}
}
}
return edge;
}
EDIT:
Ok, so if you want keep things simple and in separated functions, with minimal code changes you could implement it like this:
function getEdges(colour, operator = '<') {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData();
const [a, b] = operator == '<' ? [data[0], colour] : [colour, data[0]];
if (a < b) {
edge.push(y);
break;
}
}
}
return edge;
}
The signature getEdges(colour, operator = '<') makes the operator parameter optional. If you don't pass it to the function, it'll assume a default value of '<' so that you won't to have to change anything in your existing code. Then, you could make a second function that will reuse the original one, just with a different parameter:
function getEdgesGreaterThan(colour) {
return getEdges(colour, '>');
}
And there you have it! Hope it helps!

Based on the conversation in the comments, the purpose is to re-use the getEdges function for when a greater than or lower than comparison is needed. I've added a second parameter to indicate this, with it set to false as the default case. The if statement is dual-purpose in the sense that it makes a greater than comparison when the isGreater flag is set to true and a less than comparison when the isGreater flag is set to false. The rest of the logic is re-used verbatim with no duplication.
function getEdgesInner(colour, isGreater = false) {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData(x,y);
if ((isGreater && data[0] > colour) || (!isGreater && data[0] < colour))
edge.push(y);
break;
}
}
}
return edge;
}
/* Public Api */
function getGreaterEdges(colour) { return getEdges(colour) }
function getLesserEdges(colour) { return getEdges(colour, true) }

You can try these ways. just like you wanted in your original sample! With short, stylish and beauty ideas:
mathematical trick:
function func(value, modifier, sign) {
let result, fc=sign=="<"?1:-1;
if (value*fc< modifier*fc) result = 10;
return result;
}
A useful JS feature:
function func(value, modifier, sign) {//this is slower probably
let result;
if (eval(value+sign+modifier)) result = 10;
return result;
}
Usage (both top ways):
console.log(func(1, 2, "<"));
Passing a delegate function (for compare):
function func(value, modifier, compF) {
let result;
if (compF(value, modifier)) result = 10;
return result;
}
Usage :
console.log(func(1, 2, function(v, m){return v<m;}/*or equivalent lambda function*/));

function(value, modifier, type) {
let result;
if(type == ">") {
if (value > modifier) result = 10;
} else {
if (value < modifier) result = 10;
}
return result;
}
and pass type as a string "<" or ">"

You should add 3rd argument which takes the condition, for example, string "gt" for Grater Than(>) and "lt" for Less Than(<)
function(value, modifier, cond){
if(cond === "gt") return value > modifier ? 10 : undefined;
else if(cond === "lt") return value < modifier ? 10 : undefined;
}
EDIT: I came up with a better solution, suitable for your updated question.
let check = (value, modifier, cond)=>eval(value + cond + modifier) ? 10 : undefined
here you pass "<" or ">" for cond parameter.
EDIT 2:
function getEdges(colour,cond) {
for (let x = 0; x < width; x++) {
for (let y = height - 1; y >= 0; y--) {
const data = getData(x,y);
let shoudPush = eval(data[0] + cond + colour) ? true : false
if(shouldPush) {
edge.push(y);
break;
}
}
}
return edge;
}

EDIT: To be clear, in an ideal world I'd like to do this:
myFunction(10, 5, >)
You can almost do that with using eval. Although I'm a little confused about your result since it potentially returns undefined.
var myFunction = function(value, modifier, operation) {
var evalResult = eval(value + operation + modifier)
// I like ternary expression but could also be written as an if/else statement
return evalResult ? 10 : undefined
}
myFunction(10, 5, '<') // outputs undefined
myFunction(10, 5, '>') // outputs 10
Just be aware that eval can be dangerous.
https://medium.com/#eric_lum/the-dangerous-world-of-javascripts-eval-and-encoded-strings-96fd902af2bd
EDIT
Hey I made a codepen in response to your edited answer.
https://codepen.io/springborg/pen/ydayYa
I think this is what most closely matches your proposed syntax. Although I would probably advise against using eval unless you are absolutely certain no vulnerability will be exposed.
My recommendation is using Aditya Bhave answer :)
https://stackoverflow.com/a/56649540/1303205

If reusability is your ultimate goal, here's a curried example of how one might do this (somewhat functional-programming-stylez).
This approach allows greater reuse of the code. You might want to compare things at two points in your code, and return different values according to context.
let functionalComparer = comp => modifier => result => value => {
return comp(value, modifier) ? result: undefined
}
// curry the crap out of it!
let gt = (x,y) => x > y
let gtComp = functionalComparer(gt)
let gt100 = gtComp(100)
let whenGt100ReturnString = gt100("more than 100!")
console.log(`
with value 99: ${whenGt100ReturnString(99)},
with value 101: ${whenGt100ReturnString(101)}
`)
let lt = (x,y) => x < y
let whenLt50ReturnFoo = functionalComparer(lt)(50)("FOO")
console.log(`
with value 49: ${whenLt50ReturnFoo(49)},
with value 51: ${whenLt50ReturnFoo(51)}
`)

Related

How to limit a number between several numbers (get the most nearest small number)? [duplicate]

Example: I have an array like this: [0,22,56,74,89] and I want to find the closest number downward to a different number. Let's say that the number is 72, and in this case, the closest number down in the array is 56, so we return that. If the number is 100, then it's bigger than the biggest number in the array, so we return the biggest number. If the number is 22, then it's an exact match, just return that. The given number can never go under 0, and the array is always sorted.
I did see this question but it returns the closest number to whichever is closer either upward or downward. I must have the closest one downward returned, no matter what.
How do I start? What logic should I use?
Preferably without too much looping, since my code is run every second, and it's CPU intensive enough already.
You can use a binary search for that value. Adapted from this answer:
function index(arr, compare) { // binary search, with custom compare function
var l = 0,
r = arr.length - 1;
while (l <= r) {
var m = l + ((r - l) >> 1);
var comp = compare(arr[m]);
if (comp < 0) // arr[m] comes before the element
l = m + 1;
else if (comp > 0) // arr[m] comes after the element
r = m - 1;
else // arr[m] equals the element
return m;
}
return l-1; // return the index of the next left item
// usually you would just return -1 in case nothing is found
}
var arr = [0,22,56,74,89];
var i=index(arr, function(x){return x-72;}); // compare against 72
console.log(arr[i]);
Btw: Here is a quick performance test (adapting the one from #Simon) which clearly shows the advantages of binary search.
var theArray = [0,22,56,74,89];
var goal = 56;
var closest = null;
$.each(theArray, function(){
if (this <= goal && (closest == null || (goal - this) < (goal - closest))) {
closest = this;
}
});
alert(closest);
jsFiddle http://jsfiddle.net/UCUJY/1/
Array.prototype.getClosestDown = function(find) {
function getMedian(low, high) {
return (low + ((high - low) >> 1));
}
var low = 0, high = this.length - 1, i;
while (low <= high) {
i = getMedian(low,high);
if (this[i] == find) {
return this[i];
}
if (this[i] > find) {
high = i - 1;
}
else {
low = i + 1;
}
}
return this[Math.max(0, low-1)];
}
alert([0,22,56,74,89].getClosestDown(75));
Here's a solution without jQuery for more effiency. Works if the array is always sorted, which can easily be covered anyway:
var test = 72,
arr = [0,56,22,89,74].sort(); // just sort it generally if not sure about input, not really time consuming
function getClosestDown(test, arr) {
var num = result = 0;
for(var i = 0; i < arr.length; i++) {
num = arr[i];
if(num <= test) { result = num; }
}
return result;
}
Logic: Start from the smallest number and just set result as long as the current number is smaller than or equal the testing unit.
Note: Just made a little performance test out of curiosity :). Trimmed my code down to the essential part without declaring a function.
Here's an ES6 version using reduce, which OP references. Inspired by this answer get closest number out of array
lookup array is always sorted so this works.
const nearestBelow = (input, lookup) => lookup.reduce((prev, curr) => input >= curr ? curr : prev);
const counts = [0,22,56,74,89];
const goal = 72;
nearestBelow(goal, counts); // result is 56.
Not as fast as binary search (by a long way) but better than both loop and jQuery grep https://jsperf.com/test-a-closest-number-function/7
As we know the array is sorted, I'd push everything that asserts as less than our given value into a temporary array then return a pop of that.
var getClosest = function (num, array) {
var temp = [],
count = 0,
length = a.length;
for (count; count < length; count += 1) {
if (a[count] <= num) {
temp.push(a[count]);
} else {
break;
}
}
return temp.pop();
}
getClosest(23, [0,22,56,74,89]);
Here is edited from #Simon.
it compare closest number before and after it.
var test = 24,
arr = [76,56,22,89,74].sort(); // just sort it generally if not sure about input, not really time consuming
function getClosest(test, arr) {
var num = result = 0;
var flag = 0;
for(var i = 0; i < arr.length; i++) {
num = arr[i];
if(num < test) {
result = num;
flag = 1;
}else if (num == test) {
result = num;
break;
}else if (flag == 1) {
if ((num - test) < (Math.abs(arr[i-1] - test))){
result = num;
}
break;
}else{
break;
}
}
return result;
}

averagePair problem using multiple pointers as a solution

I'm trying to solve the following problem :
What I've come up with so far:
function averagePair(arr,tar){
if (arr.length < 2){
return false
}
let x = 0
for (var y = 1; y < arr.length; y++){
if ((arr[x] + arr[y]) / 2 == tar){
return true
}
else {
x++;
}
}
return false
}
I know this solution isn't correct, can someone explain why? It works for some cases but not all
There's a solution with O(1) additional space complexity and O(n) time complexity.
Since an array is sorted, it makes sense to have two indices: one going from begin to end (say y), another from end to begin of an array (say x).
Here's the code:
function averagePair(arr,tar){
// That's now included in for-loop condition
// if (arr.length < 2) {
// return false;
// }
let x = arr.length - 1;
for (var y = 0; y < x; y++) {
// Division may lose precision, so it's better to compare
// arr[x] + arr[y] > 2*tar
// than
// (arr[x] + arr[y]) / 2 > tar
while (y < x && arr[x] + arr[y] > 2*tar) {
x--;
}
if (x != y && arr[x] + arr[y] == 2*tar) {
return true;
}
}
return false;
}
It's kinda two-pointers technique: we'll decrease x until a[x] + a[y] > 2*tar for current loop iteration because we need to find the closest match. At the next for-loop iteration a[y] is greater or equal than the previous one, so it makes no sense to check if a[z] + a[y] == 2*tar for any z > x. We'll do this until indices aren't equal, which means there's no match.
You're only comparing adjacent elements, eg [0] vs [1], and [1] vs [2]. You also need to compare [0] vs [2] and so on. The simplest tweak would be to use a nested loop:
for (let x = 0; x < arr.length; x++) {
for (let y = 0; y < arr.length; y++) {
if (x !== y) {
// test arr[x] against arr[y]
But it'd be more elegant and less computationally complex (O(n) instead of O(n ^ 2)) to use a Set to keep track of what's been found so far:
const nums = new Set();
for (const num of arr) {
if (nums.has(tar - num)) {
return true;
} else {
nums.add(num);
}
}
function averagePair(arr,tar){
const nums = new Set();
for (const num of arr) {
if (nums.has(tar - num)) {
return true;
} else {
nums.add(num);
}
}
return false;
}
console.log(averagePair([-2, 3, 2], 0));
console.log(averagePair([-2, 3, 3], 0));

More concise alternative to JavaScript conditional ternary operator?

There are many cases where using a conditional ternary operator allows for preferring const over let:
let scaleFactor = 1;
if (prev.scale < 1 && current.scale < 1) {
scaleFactor = 5;
}
With ternary and const:
const scaleFactor =
prev.scale < 1 && current.scale < 1 ? 5 : 1;
I see and use this pattern a lot. Is there a more concise way to write this that I've been missing?
Update:
An example of alternative to ternary that is both shorter and more readable
const t = tX > 0
? 0
: tX < width - width * scale
? width - width * scale
: tX
const t = Math.max(Math.min(tX, 0), width - width * scale)
Although I completely agree with some of the people commenting on your question that short != concise, I do think your question is valid. For many cases where you have the pattern if set value to X else to Y, you can do an expression that involves the boolean conditions as factors. Some options:
if (C)
value = X
else
value = Y
can be converted to
value = C * X + !C * Y;
Same thing can be written as:
value = X + !C * (Y - X)
I am a game developer, and it's quite often that I need something like:
// 1 if the right arrow was pressed, -1 for the left, 0 otherwise
var changeInMovementX = hasRightArrowBeenPressed - hasLeftArrowBeenPressed;
// same for up and down
var changeInMovementY = hasTopArrowBeenPressed - hasDownArrowBeenPressed;
// move 1 pixel in the given directions
player.move(changeInMovementX, changeInMovementX);
For your specific example, you might consider something like:
const shouldIncreaseScale = prev.scale < 1 && current.scale < 1;
const scaleIncrease = 4;
const scaleFactor = 1 + shouldIncreaseScale * scaleIncrease;
In short, language feature like switch expression doesn't yet exist in Javascript. But there are a few things you can do depending on your liking
IIFE with switch statement that returns
const a = 5
const c1 = (() => {
switch (a) {
case 1: {
return 2
}
case 2: {
return 3
}
default: {
return 4
}
}
})()
console.log(c1)
IIFE with if statement
const c2 = (() => {
if (a === 1) {
return 2
} else if (a < 10 && b > 10) {
return 3
} else {
return 4
}
})()
console.log(c2)
custom helper
// define this once
const conditionalHelper = (cases, defaultValue) => {
for (let i = 0; i < cases.length; i++) {
const [predicate, value] = cases[i]
if (predicate()) {
return value
}
}
return defaultValue
}
const c3 = conditionalHelper(
[
[() => a === 1, 2],
[() => a < 10 && b > 10, 3],
],
4
)
console.log(c3)

Happy numbers - recursion

I have an issue with a recursive algorithm, that solves the problem of finding the happy numbers.
Here is the code:
function TestingFunction(number){
sumNumberContainer = new Array(0);
CheckIfNumberIsHappy(number);
}
function CheckIfNumberIsHappy(number){
var sumOfTheNumbers = 0;
for (var i = 0; i < number.length; i++) {
sumOfTheNumbers += Math.pow(parseInt(number[i]), 2);
}
console.log(sumOfTheNumbers);
if(sumOfTheNumbers == 1){
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
//return true;
} else {
sumNumberContainer.push(sumOfTheNumbers);
if(sumNumberContainer.length > 1){
for (var i = 0; i < sumNumberContainer.length - 1; i++) {
for (var j = i + 1; j < sumNumberContainer.length; j++) {
if(sumNumberContainer[i] == sumNumberContainer[j]){
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
//return false;
}
}
}
}
CheckIfNumberIsHappy(sumOfTheNumbers.toString());
}
}
Algorithm is working ALMOST fine. I've tested it out by calling function with different numbers, and console was displaying correct results. The problem is that I almost can't get any value from the function. There are only few cases in which I can get any value: If the number is build out of ,,0", and ,,1", for example 1000.
Because of that, I figured out, that I have problem with returning any value when the function is calling itself again.
Now I ended up with 2 results:
Returning the
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
which is giving an infinity looped number. For example when the number is happy, the function is printing in the console number one again and again...
Returning the
//return true
or
//return false
which gives me an undefined value
I'm a little bit in check by this problem, and I'm begging you guys for help.
I would take a step back and reexamine your problem with recursion in mind. The first thing you should think about with recursion is your edge cases — when can you just return a value without recursing. For happy numbers, that's the easy case where the sum of squares === 1 and the harder case where there's a cycle. So test for those and return appropriately. Only after that do you need to recurse. It can then be pretty simple:
function sumSq(num) {
/* simple helper for sums of squares */
return num.toString().split('').reduce((a, c) => c * c + a, 0)
}
function isHappy(n, seen = []) {
/* seen array keeps track of previous values so we can detect cycle */
let ss = sumSq(n)
// two edge cases -- just return
if (ss === 1) return true
if (seen.includes(ss)) return false
// not an edge case, save the value to seen, and recurse.
seen.push(ss)
return isHappy(ss, seen)
}
console.log(isHappy(23))
console.log(isHappy(22))
console.log(isHappy(7839))
Here's a simplified approach to the problem
const digits = x =>
x < 10
? [ x ]
: [ ...digits (x / 10 >> 0), x % 10 ]
const sumSquares = xs =>
xs.reduce ((acc, x) => acc + x * x, 0)
const isHappy = (x, seen = new Set) =>
x === 1
? true
: seen.has (x)
? false
: isHappy ( sumSquares (digits (x))
, seen.add (x)
)
for (let n = 1; n < 100; n = n + 1)
if (isHappy (n))
console.log ("happy", n)
// happy 1
// happy 7
// happy 10
// ...
// happy 97
The program above could be improved by using a technique called memoization
Your code is almost correct. You just forgot to return the result of the recursive call:
function TestingFunction(number){
sumNumberContainer = new Array(0);
if (CheckIfNumberIsHappy(number))
console.log(number);
}
function CheckIfNumberIsHappy(number){
var sumOfTheNumbers = 0;
for (var i = 0; i < number.length; i++) {
sumOfTheNumbers += Math.pow(parseInt(number[i]), 2);
}
if(sumOfTheNumbers == 1){
return true;
} else {
sumNumberContainer.push(sumOfTheNumbers);
if(sumNumberContainer.length > 1){
for (var i = 0; i < sumNumberContainer.length - 1; i++) {
for (var j = i + 1; j < sumNumberContainer.length; j++) {
if(sumNumberContainer[i] == sumNumberContainer[j]){
return false;
}
}
}
}
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
}
}
for (let i=0; i<100; ++i)
TestingFunction(i.toString()); // 1 7 10 13 ... 91 94 97
I've got the solution, which was given to me in the comments, by the user: Mark_M.
I just had to use my previous
return true / return false
also I had to return the recursive statement in the function, and return the value of the CheckIfTheNumberIsHappy function, which was called in TestingFunction.
The working code:
function TestingFunction(number){
sumNumberContainer = new Array(0);
return CheckIfNumberIsHappy(number);
}
function CheckIfNumberIsHappy(number){
var sumOfTheNumbers = 0;
for (var i = 0; i < number.length; i++) {
sumOfTheNumbers += Math.pow(parseInt(number[i]), 2);
}
console.log(sumOfTheNumbers);
if(sumOfTheNumbers == 1){
return true;
} else {
sumNumberContainer.push(sumOfTheNumbers);
if(sumNumberContainer.length > 1){
for (var i = 0; i < sumNumberContainer.length - 1; i++) {
for (var j = i + 1; j < sumNumberContainer.length; j++) {
if(sumNumberContainer[i] == sumNumberContainer[j]){
return false;
}
}
}
}
return CheckIfNumberIsHappy(sumOfTheNumbers.toString());
}
}
Thanks for the great support :)

Compare Strings Javascript Return %of Likely

I am looking for a JavaScript function that can compare two strings and return the likeliness that they are alike. I have looked at soundex but that's not really great for multi-word strings or non-names. I am looking for a function like:
function compare(strA,strB){
}
compare("Apples","apple") = Some X Percentage.
The function would work with all types of strings, including numbers, multi-word values, and names. Perhaps there's a simple algorithm I could use?
Ultimately none of these served my purpose so I used this:
function compare(c, u) {
var incept = false;
var ca = c.split(",");
u = clean(u);
//ca = correct answer array (Collection of all correct answer)
//caa = a single correct answer word array (collection of words of a single correct answer)
//u = array of user answer words cleaned using custom clean function
for (var z = 0; z < ca.length; z++) {
caa = $.trim(ca[z]).split(" ");
var pc = 0;
for (var x = 0; x < caa.length; x++) {
for (var y = 0; y < u.length; y++) {
if (soundex(u[y]) != null && soundex(caa[x]) != null) {
if (soundex(u[y]) == soundex(caa[x])) {
pc = pc + 1;
}
}
else {
if (u[y].indexOf(caa[x]) > -1) {
pc = pc + 1;
}
}
}
}
if ((pc / caa.length) > 0.5) {
return true;
}
}
return false;
}
// create object listing the SOUNDEX values for each letter
// -1 indicates that the letter is not coded, but is used for coding
// 0 indicates that the letter is omitted for modern census archives
// but acts like -1 for older census archives
// 1 is for BFPV
// 2 is for CGJKQSXZ
// 3 is for DT
// 4 is for L
// 5 is for MN my home state
// 6 is for R
function makesoundex() {
this.a = -1
this.b = 1
this.c = 2
this.d = 3
this.e = -1
this.f = 1
this.g = 2
this.h = 0
this.i = -1
this.j = 2
this.k = 2
this.l = 4
this.m = 5
this.n = 5
this.o = -1
this.p = 1
this.q = 2
this.r = 6
this.s = 2
this.t = 3
this.u = -1
this.v = 1
this.w = 0
this.x = 2
this.y = -1
this.z = 2
}
var sndx = new makesoundex()
// check to see that the input is valid
function isSurname(name) {
if (name == "" || name == null) {
return false
} else {
for (var i = 0; i < name.length; i++) {
var letter = name.charAt(i)
if (!(letter >= 'a' && letter <= 'z' || letter >= 'A' && letter <= 'Z')) {
return false
}
}
}
return true
}
// Collapse out directly adjacent sounds
// 1. Assume that surname.length>=1
// 2. Assume that surname contains only lowercase letters
function collapse(surname) {
if (surname.length == 1) {
return surname
}
var right = collapse(surname.substring(1, surname.length))
if (sndx[surname.charAt(0)] == sndx[right.charAt(0)]) {
return surname.charAt(0) + right.substring(1, right.length)
}
return surname.charAt(0) + right
}
// Collapse out directly adjacent sounds using the new National Archives method
// 1. Assume that surname.length>=1
// 2. Assume that surname contains only lowercase letters
// 3. H and W are completely ignored
function omit(surname) {
if (surname.length == 1) {
return surname
}
var right = omit(surname.substring(1, surname.length))
if (!sndx[right.charAt(0)]) {
return surname.charAt(0) + right.substring(1, right.length)
}
return surname.charAt(0) + right
}
// Output the coded sequence
function output_sequence(seq) {
var output = seq.charAt(0).toUpperCase() // Retain first letter
output += "-" // Separate letter with a dash
var stage2 = seq.substring(1, seq.length)
var count = 0
for (var i = 0; i < stage2.length && count < 3; i++) {
if (sndx[stage2.charAt(i)] > 0) {
output += sndx[stage2.charAt(i)]
count++
}
}
for (; count < 3; count++) {
output += "0"
}
return output
}
// Compute the SOUNDEX code for the surname
function soundex(value) {
if (!isSurname(value)) {
return null
}
var stage1 = collapse(value.toLowerCase())
//form.result.value=output_sequence(stage1);
var stage1 = omit(value.toLowerCase())
var stage2 = collapse(stage1)
return output_sequence(stage2);
}
function clean(u) {
var u = u.replace(/\,/g, "");
u = u.toLowerCase().split(" ");
var cw = ["ARRAY OF WORDS TO BE EXCLUDED FROM COMPARISON"];
var n = [];
for (var y = 0; y < u.length; y++) {
var test = false;
for (var z = 0; z < cw.length; z++) {
if (u[y] != "" && u[y] != cw[z]) {
test = true;
break;
}
}
if (test) {
//Don't use & or $ in comparison
var val = u[y].replace("$", "").replace("&", "");
n.push(val);
}
}
return n;
}
Here's an answer based on Levenshtein distance https://en.wikipedia.org/wiki/Levenshtein_distance
function similarity(s1, s2) {
var longer = s1;
var shorter = s2;
if (s1.length < s2.length) {
longer = s2;
shorter = s1;
}
var longerLength = longer.length;
if (longerLength == 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
}
For calculating edit distance
function editDistance(s1, s2) {
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
var costs = new Array();
for (var i = 0; i <= s1.length; i++) {
var lastValue = i;
for (var j = 0; j <= s2.length; j++) {
if (i == 0)
costs[j] = j;
else {
if (j > 0) {
var newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue),
costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
}
if (i > 0)
costs[s2.length] = lastValue;
}
return costs[s2.length];
}
Usage
similarity('Stack Overflow','Stack Ovrflw')
returns 0.8571428571428571
You can play with it below:
function checkSimilarity(){
var str1 = document.getElementById("lhsInput").value;
var str2 = document.getElementById("rhsInput").value;
document.getElementById("output").innerHTML = similarity(str1, str2);
}
function similarity(s1, s2) {
var longer = s1;
var shorter = s2;
if (s1.length < s2.length) {
longer = s2;
shorter = s1;
}
var longerLength = longer.length;
if (longerLength == 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
}
function editDistance(s1, s2) {
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
var costs = new Array();
for (var i = 0; i <= s1.length; i++) {
var lastValue = i;
for (var j = 0; j <= s2.length; j++) {
if (i == 0)
costs[j] = j;
else {
if (j > 0) {
var newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue),
costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
}
if (i > 0)
costs[s2.length] = lastValue;
}
return costs[s2.length];
}
<div><label for="lhsInput">String 1:</label> <input type="text" id="lhsInput" oninput="checkSimilarity()" /></div>
<div><label for="rhsInput">String 2:</label> <input type="text" id="rhsInput" oninput="checkSimilarity()" /></div>
<div>Match: <span id="output">No Input</span></div>
Using this library for string similarity worked like a charm for me!
Here's the Example -
var similarity = stringSimilarity.compareTwoStrings("Apples","apple"); // => 0.88
Here is a very simple function that does a comparison and returns a percentage based on equivalency. While it has not been tested for all possible scenarios, it may help you get started.
function similar(a,b) {
var equivalency = 0;
var minLength = (a.length > b.length) ? b.length : a.length;
var maxLength = (a.length < b.length) ? b.length : a.length;
for(var i = 0; i < minLength; i++) {
if(a[i] == b[i]) {
equivalency++;
}
}
var weight = equivalency / maxLength;
return (weight * 100) + "%";
}
alert(similar("test","tes")); // 75%
alert(similar("test","test")); // 100%
alert(similar("test","testt")); // 80%
alert(similar("test","tess")); // 75%
To Find degree of similarity between two strings; we can use more than one or two methods but I am mostly inclined towards the usage of 'Dice's Coefficient' . which is better! well in my knowledge than using 'Levenshtein distance'
Using this 'string-similarity' package from npm you will be able to work on what I said above.
some easy usage examples are
var stringSimilarity = require('string-similarity');
var similarity = stringSimilarity.compareTwoStrings('healed', 'sealed');
var matches = stringSimilarity.findBestMatch('healed', ['edward', 'sealed', 'theatre']);
for more please visit the link given above. Thankyou.
Just one I quickly wrote that might be good enough for your purposes:
function Compare(strA,strB){
for(var result = 0, i = strA.length; i--;){
if(typeof strB[i] == 'undefined' || strA[i] == strB[i]);
else if(strA[i].toLowerCase() == strB[i].toLowerCase())
result++;
else
result += 4;
}
return 1 - (result + 4*Math.abs(strA.length - strB.length))/(2*(strA.length+strB.length));
}
This weighs characters that are the same but different case 1 quarter as heavily as characters that are completely different or missing. It returns a number between 0 and 1, 1 meaning the strings are identical. 0 meaning they have no similarities. Examples:
Compare("Apple", "Apple") // 1
Compare("Apples", "Apple") // 0.8181818181818181
Compare("Apples", "apple") // 0.7727272727272727
Compare("a", "A") // 0.75
Compare("Apples", "appppp") // 0.45833333333333337
Compare("a", "b") // 0
How about function similar_text from PHP.js library?
It is based on a PHP function with the same name.
function similar_text (first, second) {
// Calculates the similarity between two strings
// discuss at: http://phpjs.org/functions/similar_text
if (first === null || second === null || typeof first === 'undefined' || typeof second === 'undefined') {
return 0;
}
first += '';
second += '';
var pos1 = 0,
pos2 = 0,
max = 0,
firstLength = first.length,
secondLength = second.length,
p, q, l, sum;
max = 0;
for (p = 0; p < firstLength; p++) {
for (q = 0; q < secondLength; q++) {
for (l = 0;
(p + l < firstLength) && (q + l < secondLength) && (first.charAt(p + l) === second.charAt(q + l)); l++);
if (l > max) {
max = l;
pos1 = p;
pos2 = q;
}
}
}
sum = max;
if (sum) {
if (pos1 && pos2) {
sum += this.similar_text(first.substr(0, pos2), second.substr(0, pos2));
}
if ((pos1 + max < firstLength) && (pos2 + max < secondLength)) {
sum += this.similar_text(first.substr(pos1 + max, firstLength - pos1 - max), second.substr(pos2 + max, secondLength - pos2 - max));
}
}
return sum;
}
fuzzyset - A fuzzy string set for javascript.
fuzzyset is a data structure that performs something akin to fulltext search against data to determine likely mispellings and approximate string matching. Note that this is a javascript port of a python library.
To some extent, I like the ideas of Dice's coefficient embedded in the string-similarity module. But I feel that considering the bigrams only and not taking into account their multiplicities is missing some important data. Below is a version that also handles multiplicities, and I think is a simpler implementation overall. I don't try to use their API, offering only a function which compares two strings after some manipulation (removing non-alphanumeric characters, lower-casing everything, and compressing but not removing whitespace), built atop one which compares them without that manipulation. It would be easy enough to wrap this back in their API, but I see little need.
const stringSimilarity = (a, b) =>
_stringSimilarity (prep (a), prep (b))
const _stringSimilarity = (a, b) => {
const bg1 = bigrams (a)
const bg2 = bigrams (b)
const c1 = count (bg1)
const c2 = count (bg2)
const combined = uniq ([... bg1, ... bg2])
.reduce ((t, k) => t + (Math .min (c1 [k] || 0, c2 [k] || 0)), 0)
return 2 * combined / (bg1 .length + bg2 .length)
}
const prep = (str) => // TODO: unicode support?
str .toLowerCase () .replace (/[^\w\s]/g, ' ') .replace (/\s+/g, ' ')
const bigrams = (str) =>
[...str] .slice (0, -1) .map ((c, i) => c + str [i + 1])
const count = (xs) =>
xs .reduce ((a, x) => ((a [x] = (a [x] || 0) + 1), a), {})
const uniq = (xs) =>
[... new Set (xs)]
console .log (stringSimilarity (
'foobar',
'Foobar'
)) //=> 1
console .log (stringSimilarity (
"healed",
"sealed"
))//=> 0.8
console .log (stringSimilarity (
"Olive-green table for sale, in extremely good condition.",
"For sale: table in very good condition, olive green in colour."
)) //=> 0.7787610619469026
console .log (stringSimilarity (
"Olive-green table for sale, in extremely good condition.",
"For sale: green Subaru Impreza, 210,000 miles"
)) //=> 0.38636363636363635
console .log (stringSimilarity (
"Olive-green table for sale, in extremely good condition.",
"Wanted: mountain bike with at least 21 gears."
)) //=> 0.1702127659574468
console .log (stringSimilarity (
"The rain in Spain falls mainly on the plain.",
"The run in Spun falls munly on the plun.",
)) //=> 0.7560975609756098
console .log (stringSimilarity (
"Fa la la la la, la la la la",
"Fa la la la la, la la",
)) //=> 0.8636363636363636
console .log (stringSimilarity (
"car crash",
"carcrash",
)) //=> 0.8
console .log (stringSimilarity (
"Now is the time for all good men to come to the aid of their party.",
"Huh?",
)) //=> 0
.as-console-wrapper {max-height: 100% !important; top: 0}
Some of the test cases are from string-similarity, others are my own. They show some significant differences from that package, but nothing untoward. The only one I would call out is the difference between "car crash" and "carcrash", which string-similarity sees as identical and I report with a similarity of 0.8. My version finds more similarity in all the olive-green test-cases than does string-similarity, but as these are in any case fairly arbitrary numbers, I'm not sure how much difference it makes; they certainly position them in the same relative order.
string-similarity lib vs Top answer (by #overloard1234) performance comparation you can find below
Based on #Tushar Walzade's advice to use string-similarity library, you can find, that for example
stringSimilatityLib.findBestMatch('KIA','Kia').bestMatch.rating
will return 0.0
So, looks like better to compare it in lowerCase.
Better base usage (for arrays) :
findBestMatch(str, strArr) {
const lowerCaseArr = strArr.map(element => element.toLowerCase());//creating lower case array
const match = stringSimilatityLib.findBestMatch(str.toLowerCase(), lowerCaseArr).bestMatch; //trying to find bestMatch
if (match.rating > 0) {
const foundIndex = lowerCaseArr.findIndex(x => x === match.target); //finding the index of found best case
return strArr[foundIndex]; //returning initial value from array
}
return null;
},
Performance
Also, i compared top answer here (made by #overloard1234) and string-similarity lib (v4.0.4).
The results you can find here : https://jsbench.me/szkzojoskq/1
Result : string-similarity is ~ twice faster
Just for fun : v2.0 of string-similarity library slower, than latest 4.0.4 about 2.2 times. So update it, if you are still using < 3.0 :)
const str1 = " pARTH PARmar r ";
const str2 = " parmar r par ";
function calculateSimilarity(str1 = "", str2 = "") {
let longer = str1.trim();
let shorter = str2.trim();
let a1 = longer.toLowerCase().split(" ");
let b1 = shorter.toLowerCase().split(" ");
let result = a1.every((aa, i) => aa[0] === b1[i][0]);
if (longer.length < shorter.length) [longer,shorter] = [shorter,longer];
var arr = [];
let count = 0;
for(var i = 0;i<longer.length;i++){
if(shorter && shorter.includes(longer[i])) {
shorter = shorter.replace(longer[i],"")
count++
};
}
return {
score : (count*100)/longer.length,
result
}
}
console.log(calculateSimilarity(str1, str2));
I used #overlord1234 function, but corrected ь: '', cuz English words don't have this letter, and next need return a[char] ?? char instead of return a[char] || char

Categories

Resources