Three Color Triangle - javascript

here is the question (https://www.codewars.com/kata/5a331ea7ee1aae8f24000175)
I've been searching for 2 days about this. I saw an essay (https://www.ijpam.eu/contents/2013-85-1/6/6.pdf). In codewars discussion, they say we can solve the question without using mat rules, if we design the code for complexity O(n).I did try that too but it doesnt work. I've tried my best but it didnt pass. Here is my code.
I did read this (Three colors triangles)
I wonder, is there any way to solve this without completely using Math ?
function triangle(row) {
let nextRow = []
let result = row.split("")
for(var j = 0; j < row.length-1; j++) {
nextRow= []
for(var i = 0; i < result.length - 1; i++) {
nextRow.push(nextColor(result[i],result[i+1]))
}
result = nextRow.slice(0)
}
return result.join("")
}
function nextColor(s1,s2) {
let colors = {"R": 1, "B": 2, "G": 3};
if(colors[s1] + colors[s2] == 6) return "G"
else if(colors[s1] + colors[s2] == 5) return "R"
else if(colors[s1] + colors[s2] == 4) return "B"
else if(colors[s1] + colors[s2] == 3) return "G"
else if(colors[s1] + colors[s2] == 2) return "R"
}

This solution solves it using < O(n2) with = O(n2) as a max. It uses two loops. One a while loop that ends so long as there is no new row to process. And an inner loop that iterates the colors of the current array.
This is not a solution to increase efficiency. It is a solution to show a non-efficient way with clarity on how the rules would work. Technically you could replace my use of "strings" with arrays, to increase efficiency, but that is not the point to illustrate on how an algorithm could work, albeit, inefficiently.
All other comments are in code comments:
function reduceTriangle( firstRow ) {
// from rules: You will be given the first row of the triangle as a string --> firstRow
let triangle = [];
// seed the triangle so we can loop it until the process is done
triangle.push( firstRow );
// lets loop this
let onRow = 0;
while (onRow < triangle.length) {
// lets determine the next generated row
// the rules given for adjacent colors are:
// Colour here: G G B G R G B R
// Becomes colour here: G R B G
// We'll also assume that order of the two-pattern doesn't matter: G B -> R, too! (from example)
let newRow = "";
for (let c = 0; c < triangle[onRow].length - 1; c++) {
let twoPattern = triangle[onRow].substring(c, c + 2); // GG, BG, etc.
// console.log(twoPattern);
let onePattern = false; // hmmm?
if (twoPattern == "RR" || twoPattern == "GG" || twoPattern == "BB") {
onePattern = twoPattern.substring(0, 1); // R || G || B
}
else {
if (twoPattern == "BG" || twoPattern == "GB") {
onePattern = "R"; // frome rules
}
else if (twoPattern == "RG" || twoPattern == "GR") {
onePattern = "B"; // frome rules
}
if (twoPattern == "BR" || twoPattern == "RB") {
onePattern = "G"; // frome rules
}
}
// hmmm cont'd:
if (onePattern !== false) {
newRow += onePattern;
}
}
if (newRow.length > 0) {
triangle.push( newRow.toString() ); // toString so we get a deep copy to append to triangle
}
// lets move to the next row, if none added, then the loop will end next cycle
onRow++;
}
return triangle;
}
console.log( reduceTriangle( "RRGBRGBB" ) );
console.log( reduceTriangle( "RBRGBRBGGRRRBGBBBGG" ) );

Related

Search coordinates in a binary not working correctly

I need to insert a lot of coordinates in a binary tree and sometimes merge them. I'm testing the program for a while and have noted that the search function doesn't work sometimes.
In this example, I created two binary trees, merged them and searched for some coordinate, but the coordinates that are inserted in the tree, were not found:
First tree:
0 2
9 8
7 0
6 0
Second tree:
3 2
8 5
4 1
5 6
Merged trees:
3 2
8 5
4 1
5 6
0 2
9 8
7 0
6 0
VALUES 8 and 5 NOT FOUND
Can someone help me with this problem?
Here is the code:
function binarytree()
{
this.root = null;
this.add = function()
{
var node = {
x : j,
y : i,
left : null,
right : null
};
var current;
if (this.root == null) this.root = node;
else {
current = this.root;
while (1)
{
if (i < current.y || j < current.x) {
if (current.left == null) {
current.left = node;
break;
}
else current = current.left;
}
else if (i > current.y || j > current.x) {
if (current.right == null) {
current.right = node;
break;
}
else current = current.right;
}
else break;
}
}
}
this.search = function(tree, i, j) {
var found = false;
current = tree.root;
while (!found && current) {
if (i < current.y || j < current.x) current = current.left;
else if (i > current.y || j > current.x) current = current.right;
else found = true;
}
return found;
}
this.print = function(no)
{
if (no)
{
this.print(no.left);
this.print(no.right);
console.log(no.x, no.y);
}
}
}
function merge(tree, tree2) {
if (tree2.x < tree.x || tree2.y < tree.y) {
if (tree.left) {
this.merge(tree.left, tree2);
} else {
tree.left = tree2;
}
} else {
if (tree.right) {
this.merge(tree.right, tree2);
} else {
tree.right = tree2;
}
}
}
var i, j;
var tree = new binarytree();
var tree2 = new binarytree();
for (x = 0; x < 4; x++)
{
i = Math.floor(Math.random() * 10);
j = Math.floor(Math.random() * 10);
tree.add();
}
for (x = 0; x < 4; x++)
{
i = Math.floor(Math.random() * 10);
j = Math.floor(Math.random() * 10);
tree2.add();
}
console.log("First tree:");
tree.print(tree.root);
console.log("Second tree:");
tree2.print(tree2.root);
merge(tree.root,tree2.root);
console.log("Merged trees:");
tree.print(tree.root);
if (tree.search(tree, i, j) == true) console.log("FOUND VALUES " + j + " AND " + i);
else console.log("VALUES " + j + " AND " + i + " NOT FOUND");
As you already mentioned in your deleted question, you need to use KD trees for this, as a normal binary search tree is intended for 1-dimensional values, not 2-dimensional values.
There are several issues in your code:
The way you split points into 2 categories is not a transitive operation. Let's say you have two points in one tree: (0, 0) and (10, -5), then the second point will be stored in the left property of the root. This is because -5 is less than 0. Now we have a second tree with two points: (4, 4) and also (10, -5). The tree will have the same structure as the first for the same reasons. Your merge function will put the second tree in the right property of the first tree's root. This is because (4, 4) is considered "right" of (0, 0). Now notice how this is inconsistent: now we have a (10, -5) sitting both in the left and the right subtree of the merged tree! This happens because the way you compare points is not a transitive comparison.
The above point is the major problem, but also notice that if (i < current.y || j < current.x) will on average be true for 75% of the cases. This is a second reason why this comparison method is not the right one.
The way to compare 2D-points in a KD tree is to alternate comparisons with either the X-coordinate or the Y-coordinate. So at the top-level of the tree you would compare Y-coordinates to decide whether to go left or right, and on the next level you would compare X-coordinates. Then again one level deeper you would compare Y-coordinates again, ...etc.
Some other remarks:
Use class syntax
Create a constructor also for the node instances
Don't print in methods, instead define a generator to produce all values, and a toString method and print that string in your main program.
Avoid using global variables: your add method should take i and j as arguments, and you should declare variables always (you didn't use var current in search, nor for x and y in the main code): this is crucial if you want to write reliable code. Consider using "use strict" which will alert you about such problems.
add and search will use a similar way to navigate through the tree, so put the code that they have in common in a separate function, which we could call locate.
As far as I know, there is no way to merge KD trees the way you have imagined it, where you can decide to put the "rest" of the second tree under a leaf node of the first. So I would suggest to just iterate the nodes of the second tree and insert them one by one in the first.
Here is the suggested code:
"use strict"; // To help avoid all kinds of problems
class Node { // Use a class for creating node instances
constructor(i, j) {
this.x = i;
this.y = j;
this.left = null;
this.right = null;
}
* inorder() { // Generator to visit all nodes
if (this.left) yield * this.left.inorder();
yield [this.x, this.y];
if (this.right) yield * this.right.inorder();
}
}
class BinaryTree {
constructor() {
this.root = null;
}
locate(i, j) { // A common function to avoid code repetition
if (this.root == null) return [null, "root"];
let current = this.root;
let axis = "x";
let otherAxis = "y";
while (true) {
// alternate the axis:
[axis, otherAxis, i, j] = [otherAxis, axis, j, i];
if (i < current[axis]) {
if (current.left == null) return [current, "left"];
current = current.left;
} else if (i > current[axis] || j != current[otherAxis]) {
if (current.right == null) return [current, "right"];
current = current.right;
} else { // point is already in the tree
return [current, "equal"];
}
}
}
add(i, j) { // this method should have parameters
if (this.root == null) {
this.root = new Node(i, j); // Use constructor for creating node
} else {
const [current, side] = this.locate(i, j);
if (side !== "equal") {
current[side] = new Node(i, j);
}
}
}
search(i, j) {
const [_, side] = this.locate(i, j);
return side === "equal";
}
* inorder() {
if (this.root) yield * this.root.inorder();
}
mergeWith(otherTree) {
// Insert all the other nodes one by one:
for (let point of otherTree.inorder()) {
this.add(...point);
}
}
toString() { // Don't print, but produce string
return JSON.stringify(Array.from(this.inorder()));
}
}
const tree = new BinaryTree();
for (const point of [[0, 2], [9, 8], [7, 0], [6, 0]]) tree.add(...point);
const tree2 = new BinaryTree();
for (const point of [[3, 2], [8, 5], [4, 1], [5, 6]]) tree2.add(...point);
console.log("First tree:");
console.log(tree.toString());
console.log("Second tree:");
console.log(tree2.toString());
tree.mergeWith(tree2);
console.log("Merged trees:");
console.log(tree.toString());
// Check that all points are found:
for (const point of tree.inorder()) {
console.log(...point, tree.search(...point));
}

Rearranging a string to be a palindrome

I'm trying to solve the problem of: Given an array of strings with only lower case letters, make a function that returns an array of those same strings, but each string has its letters rearranged such that it becomes a palindrome (if not possible then return -1). I'm a bit stuck on how I should be rearranging the letters.
let arr = ["hello", "racecra"];
I created a function to first check if a word is a palindrome :
function isPalindrome(arr) {
let obj = {};
for (var x = 0; x < str.length; x++) {
if (obj[arr[x]]) {
obj[arr[x]] += 1;
} else {
obj[arr[x]] = 1;
}
}
let countOdd = 0;
let countEven = 0;
for (let x of Object.values(obj)) {
if (x % 2 == 0) {
countEven += 1;
} else {
countOdd += 1;
}
}
return countOdd == 1 ? true : false
}
then I plan to loop through the words
let emptyArr = [];
for (var x = 0; x < arr.length; x++) {
if (isPalindrome(arr[x]) {
// not sure what to do here. I know the word is a palindrome but not sure how to sort the order of the word in the palindrome form.
} else {
emptyArr.push(-1);
}
}
return emptyArr;
Look closely: you don't need your words to be palindromes, you need them to be rearrangeable as palindromes ("palindrome-candidates"). Now, a word is a palindrome-candidate if all of its letters but one can be counted by an even number (2, 4, 6 etc.)
For example, this...
hollo
... is NOT a palindrome, but can become one, as there's 2 'o', 2 'l' and just one 'h' in it. To rearrange, you just move 'h' in the middle, then just place 'o' and 'l' before and after it:
l -> o -> h <- o <- l
So start with splitting each of your words by characters, then either count those characters or just sort them (as #Barmar suggested). If they satisfy the condition, rearrange the letters following the approach given; if not, return null (or any other special value clearly distinguishable from the rest) immediately.
Here's one way to do it:
function rearrangeAsPalindrome(word) {
if (word.length === 1) return word; // easy win first
const charCounter = word.split('').reduce((counter, ch) => ({
...counter,
[ch]: (counter[ch] || 0) + 1
}), {});
const parts = ['', '', '']; // left, middle, right
const entries = Object.entries(charCounter);
for (let i = 0; i < entries.length; ++i) {
const [char, counter] = entries[i];
if (counter % 2) { // odd
if (parts[1] !== '') return null;
// one odd is already here, eject! eject!
parts[1] = char.repeat(counter);
}
else { // even
const half = counter / 2;
parts[0] = char.repeat(half) + parts[0];
parts[2] += char.repeat(half);
}
}
return parts.join('');
}
console.log(rearrangeAsPalindrome('racarrrac')); // crraaarrc
console.log(rearrangeAsPalindrome('aabbcc')); // cbaabc
console.log(rearrangeAsPalindrome('hollo')); // lohol
console.log(rearrangeAsPalindrome('hello')); // null
This function returns null (and does it early) when it realizes the word given cannot be rearranged as a palindrome - or an actual palindrome if it is possible.
This can help
"How to generate distinct palindromes from a string in JavaScript"
https://medium.com/#bibinjaimon/how-to-generate-distinct-palindromes-from-a-string-in-javascript-6763940f5138

Javascript Draw pattern

I need some function to return pattern like this :
= * = * =
* = * = *
= * = * =
* = * = *
= * = * =
i try this but i cant get it:
function cetakGambar(angka){
let result = ''
for(let i=0; i<=angka; i++){
for(let j=0; j<=i; j++){
result += '= '
}
for(let k=0; k == i; k++){
result += ' *'
}
result += '\n'
}
return result
}
console.log(cetakGambar(5))
what looping i need to get that pattern
You were on the right track with the two nested loops. Here's an example small modification to solve the problem:
function cetakGambar(angka){
let result = ''
for(let i = 0; i < angka; i++){
for(let j = 0; j < angka; ++j) {
result += ((i + j) % 2 == 0) ? '= ' : '* ';
}
result += '\n';
}
return result
}
For each i a row is generated by looping over j. For each j we append either an = or * , depending on if after adding i and j the result is divisible by two (to create the alternating pattern). After each line a \n (newline) is appended.
Here's an ES6 solution w/o explicit iteration. It is not particularly concise and probably not too useful in this case but fun (IMHO), so I wanted to share it.
It uses Array.from to apply a generator function yielding the next symbol (* or =) for each cell and concats cells with spaces and rows with newlines.
// Utility to apply function <fn> <n> times.
function times(n, fn) {
return Array.from({ length: n }, fn);
}
// Generator function yielding the next symbol to be drawn.
//
function cetakGambar(angka) {
let generator = (function*() {
let [i, s1, s2] = [0, "*", "="];
while (true) {
// Make sure to start a new line with a different symbol for
// even rows in case <angka> is even
if (i++ % angka === 0 && angka % 2 === 0) {
[s1, s2] = [s2, s1];
}
// limit the generator to the number of values we actually need
if (i === angka * angka + 1) return;
yield i % 2 === 0 ? s1 : s2;
}
})();
return times(
angka,
() => times(angka, () => generator.next().value).join(" ") // join symbols w/ spaces ...
).join("\n"); // ... and lines w/ newline
}
console.log(cetakGambar(5));
Codesandbox here: https://codesandbox.io/s/wispy-http-mfxgo?file=/src/index.js
Different approach using one loop
function gen(row,col){
var i = 0;
var out="";
do {
if(i%col==0) out+="\n"
out += (i % 2) == 0 ? ' = ' : ' * ';
i++;
}
while (i < row*col);
return out;
}
console.log(gen(5,5));

Levenshtein distance from index 0

I've been working through "The Algorithm Design Manual" section 8.2.1 Edit Distance by Recursion. In this section Skiena writes, "We can define a recursive algorithm using the observation that the last character in the string must either be matched, substituted, inserted, or deleted." That got me wondering, why the last character? This is true for any character based on the problem definition alone. The actual Levenshtein distance algorithm makes recursive calls from the back of the strings. Why? There's no reason you couldn't do the opposite, right? Is it just a simpler, more elegant syntax?
I'm flipping the algorithm around, so it iterates from the front of the string. My attempt is below. I know my implementation doesn't work completely (ex: minDistance("industry", "interest") returns 5 instead of 6). I've spent a couple hours trying to figure out what I'm doing wrong, but I'm not seeing it. Any help would be much appreciated.
var matchChar = (c,d) => c === d ? 0 : 1;
var minDistance = function(word1, word2) {
var stringCompare = function(s, t, i, j) {
if(i === s.length) return Math.max(t.length-s.length-1,0)
if(j === t.length) return Math.max(s.length-t.length-1,0)
if(cache[i][j] !== undefined) {
return cache[i][j]
}
let match = stringCompare(s,t,i+1,j+1) + matchChar(s[i], t[j]);
let insert = stringCompare(s,t,i,j+1) + 1;
let del = stringCompare(s,t,i+1,j) + 1;
let lowestCost = Math.min(match, insert, del)
cache[i][j] = lowestCost
return lowestCost
};
let s = word1.split('')
s.push(' ')
s = s.join('')
let t = word2.split('')
t.push(' ')
t = t.join('')
var cache = []
for(let i = 0; i < s.length; i++) {
cache.push([])
for(let j = 0; j < t.length; j++) {
cache[i].push(undefined)
}
}
return stringCompare(s, t, 0, 0)
}
The lines
if(i === s.length) return Math.max(t.length-s.length-1,0)
if(j === t.length) return Math.max(s.length-t.length-1,0)
look wrong to me. I think they should be
if(i === s.length) return t.length-j
if(j === t.length) return s.length-i

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