Subset sum with number reuse allowed - javascript

I have a list of positive integers e.g. 15, 29, 110, and a target e.g. 44. I'm trying to find all possible combinations which sum to the target but importantly, the numbers in the set can be used multiple times e.g.
Target = 44
Result = 1x15, 1x29
Target = 307
Result = 2x110, 3x29
I found a dynamic programming solution which works when the combination is no more than one of each number. So Target 44 works but not my 307 example (returns Not Found).
How can the multiples or number reuse be done?
function subset(people, min, max)
{
var subsets = [];
subsets[0] = '';
for (var person in people)
{
for (var s = min-1; s >= 0; --s)
{
if (s in subsets)
{
var sum = s + people[person];
if (!(sum in subsets))
{
subsets[sum] = subsets[s] + ' ' + person;
if (sum >= min && sum <= max)
{
return subsets[sum];
}
}
}
}
}
return 'Not found';
}
var p = {
optionA:15,
optionB:29,
optionC:110
};
var qty = 307;
console.log(subset(p, qty, qty));

Try this recursive solution:
function subset(people, min, max) {
const pairs = Object.entries(people),
results = [],
getSum = multiplications => multiplications.reduce((sum, multiplicator, position) =>
sum + pairs[position][1] * multiplicator, 0),
formatResult = result => result.map(multiplications =>
multiplications.reduce((res, multiplicator, position) =>
(multiplicator > 0 ? res.push(`${multiplicator}x${pairs[position][1]}`) :
res, res), []));
function findSums(multiplications, position) {
let s;
while((s = getSum(multiplications)) <= max) {
if (s >= min) {
results.push([...multiplications]);
}
if (position < pairs.length - 1) {
const m = [...multiplications],
nextPosition = position + 1;
m[nextPosition]++;
findSums(m, nextPosition);
}
multiplications[position]++;
}
}
findSums(pairs.map(_ => 0), 0);
return results.length > 0 ? formatResult(results) : "Not found";
}
var p = {
optionA:15,
optionB:29,
optionC:110
};
var qty = 307;
console.log(subset(p, qty, qty));

Change the second loop in such way:
for (var s = 0; s <= wantedSum - people[person] ; s++)
Using this approach you fill all entries of subsets[] array\list where index is multiple of people[person] (instead of single entry). For example, with value 3 you fill 3,6,9,12... entries.

Related

Randomising arrays to form an highly entropic grid

I am attempting to make a 5x5 grid using arrays with the following limitations
Should not exceed more than 4 check marks per grid
Should not have 2 consecutive check marks
This is what I have come up with so far, I would appreciate if someone could help me figure out how would I achieve the latter condition
let emoji = {
0: '✅',
1: '❓',
}
let grid = []
let checkmarks = 0
for (let i = 0; i < 5; i++) {
let row = []
for (let j = 0; j < 5; j++) {
let random = crypto.randomInt(0, 1000) % 2
if (random == 0) {
if(checkmarks < 4) {
row.push(emoji[0])
checkmarks++
}
else {
row.push(emoji[1])
}
} else {
row.push(emoji[1])
}
}
grid.push(row)
}
I am attempting to make it as random as possible.
I'm posting this answer because the accepted answer doesn't seem to produce a consistent result. I agree with most of the approach, but result just wasn't always returning 4 checkmarks (because it seems to reset after each iteration, which can increase the maximum number of loops needed).
But ultimately, the idea is to fill the 5x5 array with the ❓ character first, randomly select a location, verify the surrounding blocks are not ✅, and then place a ✅ if these conditions are met. If not, I instead just select a new position but keep the existing results until the needed number of ✅ have been set.
let grid = [],
rows = 5,
cols = 5,
maxChecks = 4,
totalChecks = 0,
emoji = {
0: '✅',
1: '❓',
};
const _RandomChecks = () => {
grid = [];
totalChecks = 0;
for(let i = 0; i < rows; i++) {
grid[i] = [];
for(let j = 0; j < cols; j++) {
grid[i] = [...grid[i], emoji[1]];
}
}
while(totalChecks < maxChecks) {
let rndRow = parseInt(crypto.randomUUID().replace(/[^0-9]/g, "").substr(-8)) % rows,
rndCol = parseInt(crypto.randomUUID().replace(/[^0-9]/g, "").substr(-8)) % cols,
valid = (grid[rndRow][rndCol] == emoji[1]) ? true : false;
if(grid[rndRow-1]?.[rndCol] && valid) valid = (grid[rndRow-1]?.[rndCol] == emoji[1]) ? true : false;
if(grid[rndRow+1]?.[rndCol] && valid) valid = (grid[rndRow+1]?.[rndCol] == emoji[1]) ? true : false;
if(grid[rndRow][rndCol-1] && valid) valid = (grid[rndRow][rndCol-1] == emoji[1]) ? true : false;
if(grid[rndRow][rndCol+1] && valid) valid = (grid[rndRow][rndCol+1] == emoji[1]) ? true : false;
if(valid) {
grid[rndRow][rndCol] = emoji[0];
totalChecks++;
}
}
console.log(grid.map(row => row.join('')).join('\n'));
}
_RandomChecks();
Instead of randomly determining if a cell should be a checkmark I would rather randomly find cells that should be a checkmark.
Your current solution decreases the chance of getting a checkmark with each cell.
Created some example code for you:
const emojis = ['✅', '❓']
const size = 5
const checkmarks = []
for (let i = 0; i < 4; i += 1) {
while (true) {
// get random x and y
const x = Math.random() * size | 0
const y = Math.random() * size | 0
// check if x and y are far enough from existing checkmarks
const areNeighbours = checkmarks.some(c => {
if (c.x === x) {
return Math.abs(c.y - y) <= 1
}
if (c.y === y) {
return Math.abs(c.x - x) <= 1
}
return false
})
if (!areNeighbours) {
checkmarks.push({
x,
y
})
break
}
}
}
const grid = []
for (let y = 0; y < size; y += 1) {
grid.push([])
for (let x = 0; x < size; x += 1) {
const checkmark = checkmarks.find(c => c.x === x && c.y === y)
grid[y][x] = checkmark ? emojis[0] : emojis[1]
}
}
console.log(grid.map(row => row.join('')).join('\n'))
Imagine a 5x5 board initially filled by ❓.
Next you toss 4 coins at once, each coin will landed in one cell, head or tail.
If head, place a ✅ in the cell.
Now check if non-consecutive ✅ condition is met. If not start over.
Solution:
const emojis = ['✅', '❓'];
function randomInt(min, max) {
return min + Math.floor(Math.random() * (max - min));
}
function tossCoins(checkmarkLimit, size) {
const positions = Array.from({ length: checkmarkLimit }, () => {
const pos = randomInt(0, size * size);
const tail = Math.random() > 0.5;
if (tail) return null;
const x = pos % 5;
const y = (pos - x) / 5;
return [x, y];
})
return positions.filter(Boolean);
}
function checkNonConsecutive(positions) {
for (let i = 0; i < positions.length; i++) {
const p = positions[i];
for (let j = 0; j < positions.length; j++) {
if (i == j) continue;
const o = positions[j];
const distance = Math.abs(p[0] - o[0]) + Math.abs(p[1] - o[1])
if (distance <= 1) {
return false;
}
}
}
return true;
}
function main() {
const checkmarkLimit = 4;
const size = 5;
const grid = Array.from({ length: size }, () => Array.from({ length: size }, () => emojis[1]));
let positions = tossCoins(checkmarkLimit, size);
while (!checkNonConsecutive(positions)) {
positions = tossCoins(checkmarkLimit, size);
}
positions.forEach(([x, y]) => {
grid[y][x] = emojis[0];
});
return grid;
}
for (let n=0; n < 10; n++) {
console.log('round: ' + n);
console.log(main().map(row => row.join('')).join('\n'));
}

generate random numbers in specific range and using specific numbers?

How to generate random numbers in specific range and using specific numbers?
Example
given numbers [7,8];
given range [100-900];
output must be one of them 777, 787, 788, 878, 877, 888 etc...
Help me
const randomGenerateNumber = (maxRange:number, minRange:number, numbers:number[]) => {
//...what should i do in there??? Help me? Any Idea?
}
I think you don't want random numbers. It seems that you want a set of numbers based on some rules. Random means something else.
If I understand well your question you want to generate all possible numbers containing only a set of digits from a range of numbers. Is this an accurate description?
If so, this is similar with what you want: Generate random numbers only with specific digits
Edit:
You are right, so you want only one number.
In javascript you could do something like this:
I edited the algorithm to take into account min and max in probably the most lazy way. I didn't take into account cases where numbers can't be generated, it will return undefined.
There are so many ways to do this. Your algorithm can work too and maybe more efficient but it seems to have an issue with 0, it will generate numbers with 0 even if it's not in the digits array.
function randomGenerateNumber(minRange, maxRange, digits){
noTries = 0;
while(noTries++ < 100000)
{
var num = 0;
//get a random number from your range
len = Math.floor(Math.random() * (maxRange - minRange) + minRange);
//get the lenght of that random number
len = len.toString().length;
//generate a number with that length using only your set of digits
while(len--)
{
num = num * 10 + digits[Math.floor(Math.random() * digits.length)];
}
if(num >= minRange && num<= maxRange)
{
return num;
break;
}
}
}
//your testing cases
console.log(randomGenerateNumber(100,900,[7,8]))
console.log(randomGenerateNumber(299,300,[1,2,3,4,5,6,7,8,9]));
i did it. Is there any improvement. Little bit messy.
const getRandomNumber = (min: number, max: number, numbers: number[]): number => {
if (numbers.length === 9) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
let result = '';
//split maxDigits 100 => [1, 0, 0]
const maxDigits = max
.toString()
.split('')
.map(i => parseInt(i, 10));
//split minDigits 100 => [1, 0, 0]
const minDigits = min
.toString()
.split('')
.map(i => parseInt(i, 10));
//length of random number [minDigit, maxDigit] inclusive
const randomDigit = Math.floor(Math.random() * (maxDigits.length - minDigits.length + 1) + minDigits.length);
let alreadyHigh = false;
let alreadyLow = false;
let equal = true;
//4 conditions
//1. minDigits.length === maxDigits.length
//2. randomDigit === minDigits.length
//3. randomDigit === maxDigits.length
//4. randomDigit > minDigits.length && randomDigit < maxDigits.length
for (let i = 0; i < randomDigit; i++) {
const numbersToUse = i === 0 ? numbers : [...numbers, 0];
let availableNumbers = [];
if (minDigits.length === maxDigits.length) {
if (equal) {
for (let k = 0; k < numbersToUse.length; k++) {
if (minDigits[i] > maxDigits[i]) {
if (numbersToUse[k] >= 0 && numbersToUse[k] <= maxDigits[i]) {
availableNumbers.push(numbersToUse[k]);
}
} else if (numbersToUse[k] >= minDigits[i] && numbersToUse[k] <= maxDigits[i]) {
availableNumbers.push(numbersToUse[k]);
} else {
availableNumbers.push(maxDigits[i]);
}
}
} else {
if (!alreadyHigh) {
for (let k = 0; k < numbersToUse.length; k++) {
if (numbersToUse[k] >= minDigits[i]) {
availableNumbers.push(numbersToUse[k]);
}
}
} else {
availableNumbers = numbersToUse;
}
}
} else if (randomDigit === minDigits.length) {
if (!alreadyHigh) {
for (let k = 0; k < numbersToUse.length; k++) {
if (numbersToUse[k] >= minDigits[i]) {
availableNumbers.push(numbersToUse[k]);
}
}
} else {
availableNumbers = numbersToUse;
}
} else if (randomDigit === maxDigits.length) {
if (!alreadyLow) {
for (let k = 0; k < numbersToUse.length; k++) {
if (numbersToUse[k] <= maxDigits[i]) {
availableNumbers.push(numbersToUse[k]);
}
}
} else {
availableNumbers = numbersToUse;
}
} else {
availableNumbers = numbersToUse;
}
availableNumbers = [...new Set(availableNumbers)];
const randomIndex = Math.floor(Math.random() * availableNumbers.length);
result += `${availableNumbers[randomIndex]}`;
alreadyHigh = !alreadyHigh ? availableNumbers[randomIndex] > minDigits[i] : true;
alreadyLow = !alreadyLow ? availableNumbers[randomIndex] < maxDigits[i] : true;
equal = equal ? availableNumbers[randomIndex] === maxDigits[i] : false;
}
return parseInt(result, 10);
};

How to reduce an array of numbers with similar values

I want to create a method that takes a list of numbers and can reduce the array by a supplied variance.
Variance Explained:
The variance should match a value below and above. So for example, the variance of 100 equated against the value: 5460 should match any value between 5410 - 5510. (50 below the equated value and 50 above)
For instance if I have the array:
[ 1576420754, 1576420756, 1576593554, 1581172759, 1581172764 ]
I want to created a method filterSimilarValues(array, variance = 100)
Which would give me the following output:
[ 1576420756, 1576593554, 1581172764 ]
I have tried a couple things, like
const filterSimalarValues = (array, variance = 100) => {
let filteredArray = [];
for (let i = 0; i < array.length; i++) {
const number = array[i];
if (number >= number - (variance / 2) && number <= number + (variance / 2)) {
return;
}
filteredArray.push(number);
}
return filteredArray;
};
Use Array.filter() to retain only numbers that the absolute difference (Math.abs()) between the them and the next number is greater or equal variance / 2. The last item (i === array.length - 1) is included by default.
const filterSimalarValues = (array, variance = 100) =>
array.filter((n, i) =>
i === array.length - 1 ||
Math.abs(n - array[i + 1]) >= variance / 2
)
const data = [1576420754, 1576420756, 1576593554, 1581172759, 1581172764]
const result = filterSimalarValues(data)
console.log(result)
I would try something like:
const filterSimalarValues = (array, variance = 100) =>
array.reduce((acc, curr) => {
const variant = acc.find(
item => curr > item - variance / 2 && curr < item + variance / 2,
);
if (!variant) {
acc.push(curr);
} else {
acc[acc.indexOf(variant)] = curr;
}
return acc;
}, []);
Also, in my opinion you should be more specific with your question, there are a lot of possible (edge) cases not covered.
As per the explanation provided and my understanding of variance, you want to limit the numbers between the variance range. I am assuming you have a minimum of 2 elements but you can add checks for that case.
const filterSimalarValues = (array, variance = 100) => {
const filteredArray = [];
filteredArray.push(array[0]);//bench mark to check
for (let i = 1; i < array.length; i++) {
const number_lower_bound = array[i] - variance/2;
const number_upper_bound = array[i] + variance/2;
var bound_exist = false;
for(var bound = number_lower_bound;bound<=number_upper_bound;bound++){
if (filteredArray.includes(bound)) {
bound_exist = true;
break;
}
}
if(!bound_exist){
filteredArray.push(array[i])
}
}
}
return filteredArray;
};
This is what I have come up with, it works, but there might be a better solution so I am not accepting my own answer.
const filterSimilarValues = (array, variance = 100) => {
let filteredArray = [];
array.forEach(number => {
const start = number - (variance / 2);
const end = number + (variance / 2);
const range = Array(end - start + 1).fill().map((_, idx) => start + idx);
const found = range.some(r => filteredArray.includes(r))
if (!found) {
filteredArray.push(number);
}
});
return filteredArray;
};

Javascript find closest number in array without going under

I have an array of numbers, for example [300, 500, 700, 1000, 2000, 3000] and I want to find the closest number, without going under the number given.
For instance, searching for 2200 would return 3000 (NOT 2000).
However, if I search for 3200 as there is nothing higher in the array, it should return 3000 as there are no other choices.
I can get the closest number that is under the value using:
if (sizeToUse == null || Math.abs(this - monitorWidth) < Math.abs(sizeToUse - monitorWidth)) {
sizeToUse = this;
}
However, I can't get the whole thing to work. My full code is:
$(function() {
var monitorWidth = window.screen.availWidth,
sizeToUse = null,
upscaleImages = false;
$('.responsive-img').each(function(){
var sizeData = $(this).attr('data-available-sizes');
sizeData = sizeData.replace(' ', '');
var sizesAvailable = sizeData.split(',');
sizesAvailable.sort(function(a, b){return b-a});
$.each(sizesAvailable, function(){
if(upscaleImages){
if (sizeToUse == null || Math.abs(this - monitorWidth) < Math.abs(sizeToUse - monitorWidth)) {
sizeToUse = this;
}
}
else{
//We don't want to upscale images so we need to find the next highest image available
}
});
console.log('Size to use ' + sizeToUse + ' monitor width ' + monitorWidth);
});
});
You can use this code :
function closest(arr, closestTo){
var closest = Math.max.apply(null, arr); //Get the highest number in arr in case it match nothing.
for(var i = 0; i < arr.length; i++){ //Loop the array
if(arr[i] >= closestTo && arr[i] < closest) closest = arr[i]; //Check if it's higher than your number, but lower than your closest value
}
return closest; // return the value
}
var x = closest(yourArr, 2200);
Fiddle : http://jsfiddle.net/ngZ32/
Another approach is to find the first candidate greater than or equal to the one you want, and take the result, returning the last element if there are no matches:
function closestNumberOver(x, arr) {
return arr.find(d => d >= x) || arr[arr.length - 1]
}
var list = [300, 500, 700, 1000, 2000, 3000];
function findBestMatch(toMatch) {
// Assumes the array is sorted.
var bestMatch = null;
var max = Number.MIN_VALUE;
var item;
for (var i = 0; i < list.length; i++) {
item = list[i];
if (item > toMatch) {
bestMatch = item;
break;
}
max = Math.max(max, item);
}
// Compare to null, just in case bestMatch is 0 itself.
if (bestMatch !== null) {
return bestMatch;
}
return max;
}
alert(findBestMatch(2200));
alert(findBestMatch(3200));
sizesAvailable.sort(function(a, b){return a-b}); // DESCENDING sort
if(upscaleImages) // do th eif once, not every time through the loop
{
$.each(sizesAvailable, function()
{
if (this > monitorWidth)
sizeToUse = this;
}
if (sizeToUse == null) sizeToUse = sizesAvailable[0];
}
else
{
$.each(sizesAvailable, function()
{
//We don't want to upscale images so....
}
}
});

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