<p>Here at work, we often need to find a string from the list of strings that is the closest match to some other input string. Currently, we are using Needleman-Wunsch algorithm. The algorithm often returns a lot of false-positives (if we set the minimum-score too low), sometimes it doesn't find a match when it should (when the minimum-score is too high) and, most of the times, we need to check the results by hand. We thought we should try other alternatives.</p>

<p>Do you have any experiences with the algorithms?
Do you know how the algorithms compare to one another?</p>

<p>I'd really appreciate some advice.</p>

<p>PS: We're coding in C#, but you shouldn't care about it - I'm asking about the algorithms in general.</p>

<hr>

<p>Oh, I'm sorry I forgot to mention that.</p>

<p>No, we're not using it to match duplicate data. We have a list of strings that we are looking for - we call it search-list. And then we need to process texts from various sources (like RSS feeds, web-sites, forums, etc.) - we extract parts of those texts (there are entire sets of rules for that, but that's irrelevant) and we need to match those against the search-list. If the string matches one of the strings in search-list - we need to do some further processing of the thing (which is also irrelevant).</p>

<p>We can not perform the normal comparison, because the strings extracted from the outside sources, most of the times, include some extra words etc.</p>

<p>Anyway, it's not for duplicate detection.</p>

[{'answer_id': 49268, 'author': 'Biri', 'author_id': 968, 'author_profile': 'https://Stackoverflow.com/users/968', 'pm_score': 2, 'selected': False, 'text': '<p>We are using the <a href="http://en.wikipedia.org/wiki/Levenshtein_distance" rel="nofollow noreferrer">Levenshtein distance</a> method to check for duplicate customers in our database. It works quite well.</p>\n'}, {'answer_id': 49278, 'author': 'Grey Panther', 'author_id': 1265, 'author_profile': 'https://Stackoverflow.com/users/1265', 'pm_score': 3, 'selected': False, 'text': '<p>Related to the Levenstein distance: you might wish to normalize it by dividing the result with the length of the longer string, so that you always get a number between 0 and 1 and so that you can compare the distance of pair of strings in a meaningful way (the expression L(A, B) > L(A, C) - for example - is meaningless unless you normalize the distance).</p>\n'}, {'answer_id': 50110, 'author': 'Thomas Kammeyer', 'author_id': 4410, 'author_profile': 'https://Stackoverflow.com/users/4410', 'pm_score': 6, 'selected': True, 'text': '<p>OK, Needleman-Wunsch(NW) is a classic end-to-end ("global") aligner from the bioinformatics literature.  It was long ago available as "align" and "align0" in the FASTA package.  The difference was that the "0" version wasn\'t as biased about avoiding end-gapping, which often allowed favoring high-quality internal matches easier.  Smith-Waterman, I suspect you\'re aware, is a local aligner and is the original basis of BLAST.  FASTA had it\'s own local aligner as well that was slightly different.  All of these are essentially heuristic methods for estimating Levenshtein distance relevant to a scoring metric for individual character pairs (in bioinformatics, often given by Dayhoff/"PAM", Henikoff&amp;Henikoff, or other matrices and usually replaced with something simpler and more reasonably reflective of replacements in linguistic word morphology when applied to natural language).</p>\n\n<p>Let\'s not be precious about labels:  Levenshtein distance, as referenced in practice at least, is basically edit distance and you have to estimate it because it\'s not feasible to compute it generally, and it\'s expensive to compute exactly even in interesting special cases:  the water gets deep quick there, and thus we have heuristic methods of long and good repute.</p>\n\n<p>Now as to your own problem:  several years ago, I had to check the accuracy of short DNA reads against reference sequence known to be correct and I came up with something I called "anchored alignments".</p>\n\n<p>The idea is to take your reference string set and "digest" it by finding all locations where a given N-character substring occurs.  Choose N so that the table you build is not too big but also so that substrings of length N are not too common.  For small alphabets like DNA bases, it\'s possible to come up with a perfect hash on strings of N characters and make a table and chain the matches in a linked list from each bin.  The list entries must identify the sequence and start position of the substring that maps to the bin in whose list they occur.  These are "anchors" in the list of strings to be searched at which an NW alignment is likely to be useful.</p>\n\n<p>When processing a query string, you take the N characters starting at some offset K in the query string, hash them, look up their bin, and if the list for that bin is nonempty then you go through all the list records and perform alignments between the query string and the search string referenced in the record.  When doing these alignments, you line up the query string and the search string <em>at</em> the anchor and extract a substring of the search string that is the same length as the query string and which contains that anchor at the same offset, K.</p>\n\n<p>If you choose a long enough anchor length N, and a reasonable set of values of offset K (they can be spread across the query string or be restricted to low offsets) you should get a subset of possible alignments and often will get clearer winners.  Typically you will want to use the less end-biased align0-like NW aligner.</p>\n\n<p>This method tries to boost NW a bit by restricting it\'s input and this has a performance gain because you do less alignments and they are more often between similar sequences.  Another good thing to do with your NW aligner is to allow it to give up after some amount or length of gapping occurs to cut costs, especially if you know you\'re not going to see or be interested in middling-quality matches.</p>\n\n<p>Finally, this method was used on a system with small alphabets, with K restricted to the first 100 or so positions in the query string and with search strings much larger than the queries (the DNA reads were around 1000 bases and the search strings were on the order of 10000, so I was looking for approximate substring matches justified by an estimate of edit distance specifically).  Adapting this methodology to natural language will require some careful thought:  you lose on alphabet size but you gain if your query strings and search strings are of similar length.</p>\n\n<p>Either way, allowing more than one anchor from different ends of the query string to be used simultaneously might be helpful in further filtering data fed to NW.  If you do this, be prepared to possibly send overlapping strings each containing one of the two anchors to the aligner and then reconcile the alignments... or possibly further modify NW to emphasize keeping your anchors mostly intact during an alignment using penalty modification during the algorithm\'s execution.</p>\n\n<p>Hope this is helpful or at least interesting.</p>\n'}, {'answer_id': 53724, 'author': 'Yuval F', 'author_id': 1702, 'author_profile': 'https://Stackoverflow.com/users/1702', 'pm_score': 2, 'selected': False, 'text': '<p>Alternative algorithms to look at are <a href="http://www.tgries.de/agrep/" rel="noreferrer" title="agrep">agrep</a> (<a href="http://en.wikipedia.org/wiki/Agrep" rel="noreferrer" title="Wikipedia Explanation for agrep">Wikipedia entry on agrep</a>),\n<a href="http://www.bioinfo.se/kurser/swell/blasta-fasta.shtml" rel="noreferrer" title="FASTA and BLAST">FASTA and BLAST</a> biological sequence matching algorithms. These are special cases of <a href="http://en.wikipedia.org/wiki/Approximate_string_matching" rel="noreferrer">approximate string matching</a>, also in the <a href="http://www.cs.sunysb.edu/~algorith/files/approximate-pattern-matching.shtml" rel="noreferrer">Stony Brook algorithm repositry</a>. If you can specify the ways the strings differ from each other, you could probably focus on a tailored algorithm. For example, aspell uses some variant of "soundslike" (soundex-metaphone) distance in combination with a "keyboard" distance to accomodate bad spellers and bad typers alike.</p>\n'}, {'answer_id': 15047137, 'author': 'alex', 'author_id': 165130, 'author_profile': 'https://Stackoverflow.com/users/165130', 'pm_score': 1, 'selected': False, 'text': '<p>Use <a href="http://en.wikipedia.org/wiki/FM-index" rel="nofollow">FM Index</a> with Backtracking, similar to the one in <a href="http://bowtie-bio.sourceforge.net/index.shtml" rel="nofollow">Bowtie</a> fuzzy aligner</p>\n'}, {'answer_id': 16992601, 'author': 'R. Shilling', 'author_id': 1427103, 'author_profile': 'https://Stackoverflow.com/users/1427103', 'pm_score': 1, 'selected': False, 'text': "<p>In order to minimize mismatches due to slight variations or errors in spelling, I've used the Metaphone algorithm, then Levenshtein distance (scaled to 0-100 as a percentage match) on the Metaphone encodings for a measure of closeness. That seems to have worked fairly well. </p>\n"}, {'answer_id': 22522502, 'author': 'mortonjt', 'author_id': 1167475, 'author_profile': 'https://Stackoverflow.com/users/1167475', 'pm_score': 0, 'selected': False, 'text': '<p>To expand on Cd-MaN\'s answer, it sounds like you\'re facing a normalization problem.  It isn\'t obvious how to handle scores between alignments with varying lengths.</p>\n\n<p>Given what you are interested in, you may want to obtain p-values for your alignment.  If you are using Needleman-Wunsch, you can obtain these p-values using Karlin-Altschul statistics <a href="http://www.ncbi.nlm.nih.gov/BLAST/tutorial/Altschul-1.html" rel="nofollow">http://www.ncbi.nlm.nih.gov/BLAST/tutorial/Altschul-1.html</a></p>\n\n<p>BLAST will can local alignment and evaluate them using these statistics.  If you are concerned about speed, this would be a good tool to use.</p>\n\n<p>Another option is to use HMMER.  HMMER uses Profile Hidden Markov Models to align sequences.  Personally, I think this is a more powerful approach since it also provides positional information. <a href="http://hmmer.janelia.org/" rel="nofollow">http://hmmer.janelia.org/</a></p>\n'}]
['https://Stackoverflow.com/questions/49263', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/1353085/']