瀏覽代碼

HUE-8918 [autocomplete] Extract stringDistance to a separate module

Johan Ahlen 6 年之前
父節點
當前提交
dd712803fa

+ 2 - 62
desktop/core/src/desktop/js/parse/sql/generic/sqlParseSupport.js

@@ -15,6 +15,7 @@
 // limitations under the License.
 
 import { SqlFunctions } from 'sql/sqlFunctions';
+import stringDistance from 'sql/stringDistance';
 
 const identifierEquals = (a, b) =>
   a &&
@@ -46,66 +47,6 @@ if (!String.prototype.endsWith) {
   };
 }
 
-/**
- * Calculates the Optimal String Alignment distance between two strings. Returns 0 when the strings are equal and the
- * distance when not, distances is less than or equal to the length of the longest string.
- *
- * @param strA
- * @param strB
- * @param [ignoreCase]
- * @returns {number} The similarity
- */
-const stringDistance = function(strA, strB, ignoreCase) {
-  if (ignoreCase) {
-    strA = strA.toLowerCase();
-    strB = strB.toLowerCase();
-  }
-
-  // TODO: Consider other algorithms for performance
-  const strALength = strA.length;
-  const strBLength = strB.length;
-  if (strALength === 0) {
-    return strBLength;
-  }
-  if (strBLength === 0) {
-    return strALength;
-  }
-
-  const distances = new Array(strALength);
-
-  let cost, deletion, insertion, substitution, transposition;
-  for (let i = 0; i <= strALength; i++) {
-    distances[i] = new Array(strBLength);
-    distances[i][0] = i;
-    for (let j = 1; j <= strBLength; j++) {
-      if (!i) {
-        distances[0][j] = j;
-      } else {
-        cost = strA[i - 1] === strB[j - 1] ? 0 : 1;
-        deletion = distances[i - 1][j] + 1;
-        insertion = distances[i][j - 1] + 1;
-        substitution = distances[i - 1][j - 1] + cost;
-        if (deletion <= insertion && deletion <= substitution) {
-          distances[i][j] = deletion;
-        } else if (insertion <= deletion && insertion <= substitution) {
-          distances[i][j] = insertion;
-        } else {
-          distances[i][j] = substitution;
-        }
-
-        if (i > 1 && j > 1 && strA[i] === strB[j - 1] && strA[i - 1] === strB[j]) {
-          transposition = distances[i - 2][j - 2] + cost;
-          if (transposition < distances[i][j]) {
-            distances[i][j] = transposition;
-          }
-        }
-      }
-    }
-  }
-
-  return distances[strALength][strBLength];
-};
-
 const equalIgnoreCase = (a, b) => a && b && a.toLowerCase() === b.toLowerCase();
 
 const SIMPLE_TABLE_REF_SUGGESTIONS = [
@@ -2276,6 +2217,5 @@ const initSyntaxParser = function(parser) {
 
 export default {
   initSqlParser: initSqlParser,
-  initSyntaxParser: initSyntaxParser,
-  stringDistance: stringDistance
+  initSyntaxParser: initSyntaxParser
 };

+ 0 - 115
desktop/core/src/desktop/js/parse/sql/hive/spec/sqlParseSupportSpec.js

@@ -1,115 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import SqlParseSupport from '../sqlParseSupport';
-
-describe('sqlParseSupport.js', () => {
-  const expectDistance = function(strA, strB, distance, ignoreCase) {
-    const lr = SqlParseSupport.stringDistance(strA, strB, ignoreCase);
-    const rl = SqlParseSupport.stringDistance(strB, strA, ignoreCase);
-    expect(lr).toEqual(rl);
-    expect(lr).toEqual(distance);
-  };
-
-  it('should calculate the distance between "" and "" correctly', () => {
-    expectDistance('', '', 0, true);
-  });
-
-  it('should calculate the distance between "abc" and "" correctly', () => {
-    expectDistance('abc', '', 3, true);
-  });
-
-  it('should calculate the distance between "a" and "b" correctly', () => {
-    expectDistance('a', 'b', 1, true);
-  });
-
-  it('should calculate the distance between "abc" and "abc" correctly', () => {
-    expectDistance('abc', 'abc', 0, true);
-  });
-
-  it('should calculate the distance between "abcd" and "abc" correctly', () => {
-    expectDistance('abcd', 'abc', 1, true);
-  });
-
-  it('should calculate the distance between "abd" and "abc" correctly', () => {
-    expectDistance('abd', 'abc', 1, true);
-  });
-
-  it('should calculate the distance between "ca" and "abc" correctly', () => {
-    expectDistance('ca', 'abc', 3, true);
-  });
-
-  it('should calculate the distance between "abC" and "abc" whe not ignoring case correctly', () => {
-    expectDistance('abC', 'abc', 1, false);
-  });
-
-  it('should calculate the distance between "abC" and "abc" when ignoring case correctly', () => {
-    expectDistance('abC', 'abc', 0, true);
-  });
-
-  it('should calculate the distance between "abe" and "abc" correctly', () => {
-    expectDistance('abe', 'abc', 1, true);
-  });
-
-  it('should calculate the distance between "ace" and "abc" correctly', () => {
-    expectDistance('ace', 'abc', 2, true);
-  });
-
-  it('should calculate the distance between "12345" and "23451" correctly', () => {
-    expectDistance('12345', '23451', 2, true);
-  });
-
-  it('should calculate the distance between "abcde" and "12345" correctly', () => {
-    expectDistance('abcde', '12345', 5, true);
-  });
-
-  it('should calculate the distance between "12345" and "abcdefgh" correctly', () => {
-    expectDistance('12345', 'abcdefgh', 8, true);
-  });
-
-  it('should calculate the distance between "abc1def" and "abcdef" correctly', () => {
-    expectDistance('abc1def', 'abcdef', 1, true);
-  });
-
-  it('should calculate the distance between "bacdef" and "abcdef" correctly', () => {
-    expectDistance('bacdef', 'abcdef', 2, true);
-  });
-
-  xit('should be quick', () => {
-    const strA = 'abcdefgh012345678ijklmnop012345678';
-    const strB = 'ijklmnop012345678abcdefgh012345678';
-    let start, end;
-    const durations = new Array(10000 - 1000);
-    for (let i = 0; i < 10000; i++) {
-      if (i > 1000) {
-        start = performance.now();
-      }
-      SqlParseSupport.stringDistance(strA, strB, true);
-      if (i > 1000) {
-        end = performance.now();
-        durations.push(end - start);
-      }
-    }
-    let sum = 0;
-    durations.forEach(duration => {
-      sum += duration;
-    });
-    // eslint-disable-next-line no-restricted-syntax
-    console.log('it took ' + sum / durations.length + ' ms on average.');
-    // ~ 0.037 ms on average
-    expect(true).toBeTruthy();
-  });
-});

+ 1 - 61
desktop/core/src/desktop/js/parse/sql/hive/sqlParseSupport.js

@@ -15,6 +15,7 @@
 // limitations under the License.
 
 import { SqlFunctions } from 'sql/sqlFunctions';
+import stringDistance from 'sql/stringDistance';
 
 const identifierEquals = (a, b) =>
   a &&
@@ -46,66 +47,6 @@ if (!String.prototype.endsWith) {
   };
 }
 
-/**
- * Calculates the Optimal String Alignment distance between two strings. Returns 0 when the strings are equal and the
- * distance when not, distances is less than or equal to the length of the longest string.
- *
- * @param strA
- * @param strB
- * @param [ignoreCase]
- * @returns {number} The similarity
- */
-const stringDistance = function(strA, strB, ignoreCase) {
-  if (ignoreCase) {
-    strA = strA.toLowerCase();
-    strB = strB.toLowerCase();
-  }
-
-  // TODO: Consider other algorithms for performance
-  const strALength = strA.length;
-  const strBLength = strB.length;
-  if (strALength === 0) {
-    return strBLength;
-  }
-  if (strBLength === 0) {
-    return strALength;
-  }
-
-  const distances = new Array(strALength);
-
-  let cost, deletion, insertion, substitution, transposition;
-  for (let i = 0; i <= strALength; i++) {
-    distances[i] = new Array(strBLength);
-    distances[i][0] = i;
-    for (let j = 1; j <= strBLength; j++) {
-      if (!i) {
-        distances[0][j] = j;
-      } else {
-        cost = strA[i - 1] === strB[j - 1] ? 0 : 1;
-        deletion = distances[i - 1][j] + 1;
-        insertion = distances[i][j - 1] + 1;
-        substitution = distances[i - 1][j - 1] + cost;
-        if (deletion <= insertion && deletion <= substitution) {
-          distances[i][j] = deletion;
-        } else if (insertion <= deletion && insertion <= substitution) {
-          distances[i][j] = insertion;
-        } else {
-          distances[i][j] = substitution;
-        }
-
-        if (i > 1 && j > 1 && strA[i] === strB[j - 1] && strA[i - 1] === strB[j]) {
-          transposition = distances[i - 2][j - 2] + cost;
-          if (transposition < distances[i][j]) {
-            distances[i][j] = transposition;
-          }
-        }
-      }
-    }
-  }
-
-  return distances[strALength][strBLength];
-};
-
 const equalIgnoreCase = (a, b) => a && b && a.toLowerCase() === b.toLowerCase();
 
 const SIMPLE_TABLE_REF_SUGGESTIONS = [
@@ -2518,6 +2459,5 @@ const initGlobalSearchParser = function(parser) {
 export default {
   initSqlParser: initSqlParser,
   initSyntaxParser: initSyntaxParser,
-  stringDistance: stringDistance,
   initGlobalSearchParser: initGlobalSearchParser
 };

+ 0 - 115
desktop/core/src/desktop/js/parse/sql/impala/spec/sqlParseSupportSpec.js

@@ -1,115 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import SqlParseSupport from '../sqlParseSupport';
-
-describe('sqlParseSupport.js', () => {
-  const expectDistance = function(strA, strB, distance, ignoreCase) {
-    const lr = SqlParseSupport.stringDistance(strA, strB, ignoreCase);
-    const rl = SqlParseSupport.stringDistance(strB, strA, ignoreCase);
-    expect(lr).toEqual(rl);
-    expect(lr).toEqual(distance);
-  };
-
-  it('should calculate the distance between "" and "" correctly', () => {
-    expectDistance('', '', 0, true);
-  });
-
-  it('should calculate the distance between "abc" and "" correctly', () => {
-    expectDistance('abc', '', 3, true);
-  });
-
-  it('should calculate the distance between "a" and "b" correctly', () => {
-    expectDistance('a', 'b', 1, true);
-  });
-
-  it('should calculate the distance between "abc" and "abc" correctly', () => {
-    expectDistance('abc', 'abc', 0, true);
-  });
-
-  it('should calculate the distance between "abcd" and "abc" correctly', () => {
-    expectDistance('abcd', 'abc', 1, true);
-  });
-
-  it('should calculate the distance between "abd" and "abc" correctly', () => {
-    expectDistance('abd', 'abc', 1, true);
-  });
-
-  it('should calculate the distance between "ca" and "abc" correctly', () => {
-    expectDistance('ca', 'abc', 3, true);
-  });
-
-  it('should calculate the distance between "abC" and "abc" whe not ignoring case correctly', () => {
-    expectDistance('abC', 'abc', 1, false);
-  });
-
-  it('should calculate the distance between "abC" and "abc" when ignoring case correctly', () => {
-    expectDistance('abC', 'abc', 0, true);
-  });
-
-  it('should calculate the distance between "abe" and "abc" correctly', () => {
-    expectDistance('abe', 'abc', 1, true);
-  });
-
-  it('should calculate the distance between "ace" and "abc" correctly', () => {
-    expectDistance('ace', 'abc', 2, true);
-  });
-
-  it('should calculate the distance between "12345" and "23451" correctly', () => {
-    expectDistance('12345', '23451', 2, true);
-  });
-
-  it('should calculate the distance between "abcde" and "12345" correctly', () => {
-    expectDistance('abcde', '12345', 5, true);
-  });
-
-  it('should calculate the distance between "12345" and "abcdefgh" correctly', () => {
-    expectDistance('12345', 'abcdefgh', 8, true);
-  });
-
-  it('should calculate the distance between "abc1def" and "abcdef" correctly', () => {
-    expectDistance('abc1def', 'abcdef', 1, true);
-  });
-
-  it('should calculate the distance between "bacdef" and "abcdef" correctly', () => {
-    expectDistance('bacdef', 'abcdef', 2, true);
-  });
-
-  xit('should be quick', () => {
-    const strA = 'abcdefgh012345678ijklmnop012345678';
-    const strB = 'ijklmnop012345678abcdefgh012345678';
-    let start, end;
-    const durations = new Array(10000 - 1000);
-    for (let i = 0; i < 10000; i++) {
-      if (i > 1000) {
-        start = performance.now();
-      }
-      SqlParseSupport.stringDistance(strA, strB, true);
-      if (i > 1000) {
-        end = performance.now();
-        durations.push(end - start);
-      }
-    }
-    let sum = 0;
-    durations.forEach(duration => {
-      sum += duration;
-    });
-    // eslint-disable-next-line no-restricted-syntax
-    console.log('it took ' + sum / durations.length + ' ms on average.');
-    // ~ 0.037 ms on average
-    expect(true).toBeTruthy();
-  });
-});

+ 1 - 61
desktop/core/src/desktop/js/parse/sql/impala/sqlParseSupport.js

@@ -15,6 +15,7 @@
 // limitations under the License.
 
 import { SqlFunctions } from 'sql/sqlFunctions';
+import stringDistance from 'sql/stringDistance';
 
 const identifierEquals = (a, b) =>
   a &&
@@ -46,66 +47,6 @@ if (!String.prototype.endsWith) {
   };
 }
 
-/**
- * Calculates the Optimal String Alignment distance between two strings. Returns 0 when the strings are equal and the
- * distance when not, distances is less than or equal to the length of the longest string.
- *
- * @param strA
- * @param strB
- * @param [ignoreCase]
- * @returns {number} The similarity
- */
-const stringDistance = function(strA, strB, ignoreCase) {
-  if (ignoreCase) {
-    strA = strA.toLowerCase();
-    strB = strB.toLowerCase();
-  }
-
-  // TODO: Consider other algorithms for performance
-  const strALength = strA.length;
-  const strBLength = strB.length;
-  if (strALength === 0) {
-    return strBLength;
-  }
-  if (strBLength === 0) {
-    return strALength;
-  }
-
-  const distances = new Array(strALength);
-
-  let cost, deletion, insertion, substitution, transposition;
-  for (let i = 0; i <= strALength; i++) {
-    distances[i] = new Array(strBLength);
-    distances[i][0] = i;
-    for (let j = 1; j <= strBLength; j++) {
-      if (!i) {
-        distances[0][j] = j;
-      } else {
-        cost = strA[i - 1] === strB[j - 1] ? 0 : 1;
-        deletion = distances[i - 1][j] + 1;
-        insertion = distances[i][j - 1] + 1;
-        substitution = distances[i - 1][j - 1] + cost;
-        if (deletion <= insertion && deletion <= substitution) {
-          distances[i][j] = deletion;
-        } else if (insertion <= deletion && insertion <= substitution) {
-          distances[i][j] = insertion;
-        } else {
-          distances[i][j] = substitution;
-        }
-
-        if (i > 1 && j > 1 && strA[i] === strB[j - 1] && strA[i - 1] === strB[j]) {
-          transposition = distances[i - 2][j - 2] + cost;
-          if (transposition < distances[i][j]) {
-            distances[i][j] = transposition;
-          }
-        }
-      }
-    }
-  }
-
-  return distances[strALength][strBLength];
-};
-
 const equalIgnoreCase = (a, b) => a && b && a.toLowerCase() === b.toLowerCase();
 
 const SIMPLE_TABLE_REF_SUGGESTIONS = [
@@ -2600,6 +2541,5 @@ const initGlobalSearchParser = function(parser) {
 export default {
   initSqlParser: initSqlParser,
   initSyntaxParser: initSyntaxParser,
-  stringDistance: stringDistance,
   initGlobalSearchParser: initGlobalSearchParser
 };

+ 2 - 2
desktop/core/src/desktop/js/sql/aceLocationHandler.js

@@ -21,9 +21,9 @@ import dataCatalog from 'catalog/dataCatalog';
 import hueDebug from 'utils/hueDebug';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
-import SqlParseSupport from 'parse/sqlParseSupport';
 import sqlStatementsParser from 'parse/sqlStatementsParser';
 import sqlUtils from 'sql/sqlUtils';
+import stringDistance from 'sql/stringDistance';
 
 // TODO: depends on Ace, sqlStatementsParser
 
@@ -1107,7 +1107,7 @@ class AceLocationHandler {
               const weightedExpected = $.map(possibleValues, val => {
                 return {
                   text: isLowerCase ? val.name.toLowerCase() : val.name,
-                  distance: SqlParseSupport.stringDistance(token.value, val.name)
+                  distance: stringDistance(token.value, val.name)
                 };
               });
               weightedExpected.sort((a, b) => {

+ 5 - 5
desktop/core/src/desktop/js/parse/sql/generic/spec/sqlParseSupportSpec.js → desktop/core/src/desktop/js/sql/spec/stringDistanceSpec.js

@@ -14,12 +14,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import SqlParseSupport from '../sqlParseSupport';
+import stringDistance from '../stringDistance';
 
-describe('sqlParseSupport.js', () => {
+describe('stringDistance.js', () => {
   const expectDistance = function(strA, strB, distance, ignoreCase) {
-    const lr = SqlParseSupport.stringDistance(strA, strB, ignoreCase);
-    const rl = SqlParseSupport.stringDistance(strB, strA, ignoreCase);
+    const lr = stringDistance(strA, strB, ignoreCase);
+    const rl = stringDistance(strB, strA, ignoreCase);
     expect(lr).toEqual(rl);
     expect(lr).toEqual(distance);
   };
@@ -97,7 +97,7 @@ describe('sqlParseSupport.js', () => {
       if (i > 1000) {
         start = performance.now();
       }
-      SqlParseSupport.stringDistance(strA, strB, true);
+      stringDistance(strA, strB, true);
       if (i > 1000) {
         end = performance.now();
         durations.push(end - start);

+ 77 - 0
desktop/core/src/desktop/js/sql/stringDistance.js

@@ -0,0 +1,77 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * Calculates the Optimal String Alignment distance between two strings. Returns 0 when the strings are equal and the
+ * distance when not, distances is less than or equal to the length of the longest string.
+ *
+ * @param strA
+ * @param strB
+ * @param [ignoreCase]
+ * @returns {number} The similarity
+ */
+const stringDistance = function(strA, strB, ignoreCase) {
+  if (ignoreCase) {
+    strA = strA.toLowerCase();
+    strB = strB.toLowerCase();
+  }
+
+  // TODO: Consider other algorithms for performance
+  const strALength = strA.length;
+  const strBLength = strB.length;
+  if (strALength === 0) {
+    return strBLength;
+  }
+  if (strBLength === 0) {
+    return strALength;
+  }
+
+  const distances = new Array(strALength);
+
+  let cost, deletion, insertion, substitution, transposition;
+  for (let i = 0; i <= strALength; i++) {
+    distances[i] = new Array(strBLength);
+    distances[i][0] = i;
+    for (let j = 1; j <= strBLength; j++) {
+      if (!i) {
+        distances[0][j] = j;
+      } else {
+        cost = strA[i - 1] === strB[j - 1] ? 0 : 1;
+        deletion = distances[i - 1][j] + 1;
+        insertion = distances[i][j - 1] + 1;
+        substitution = distances[i - 1][j - 1] + cost;
+        if (deletion <= insertion && deletion <= substitution) {
+          distances[i][j] = deletion;
+        } else if (insertion <= deletion && insertion <= substitution) {
+          distances[i][j] = insertion;
+        } else {
+          distances[i][j] = substitution;
+        }
+
+        if (i > 1 && j > 1 && strA[i] === strB[j - 1] && strA[i - 1] === strB[j]) {
+          transposition = distances[i - 2][j - 2] + cost;
+          if (transposition < distances[i][j]) {
+            distances[i][j] = transposition;
+          }
+        }
+      }
+    }
+  }
+
+  return distances[strALength][strBLength];
+};
+
+export default stringDistance;