首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >最长公共子串

最长公共子串
EN

Code Review用户
提问于 2013-02-03 17:54:40
回答 2查看 9.3K关注 0票数 8

我编写了一个程序来查找几个字符串中最长的公共子序列。我使用了一个简单的算法和实现。

其动机是解决http://rosalind.info/problems/lcs/的Rosalind问题。您也可以在那里找到示例输入。Rosalind问题将字符串与DNA联系在一起,但我认为我的代码可以作为一个通用字符串操作来处理。

如果有多个子字符串,这个问题会要求提供任何常见的子字符串,但是我找到了所有的子字符串。

字符串集合的公共子串是集合中每个成员的子串。如果集合的较长的公共子字符串不存在,则公共子字符串是最长公共子串。例如,CG是ACGTACGT和AACCGGTATA的公共子字符串,而GTA是最长的公共子字符串。请注意,可能存在多个最长的公共子字符串。给定:一个DNA串集合(每个长度最多为1 kbp;)。返回:集合中最长的公共子字符串。(如果存在多个解决方案,则可以返回任何单一解决方案。)样本数据集GATTACA ATACA样本输出AC

如何改进这些代码?有什么明显的问题吗?

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace Finding_a_Shared_Motif
{
    class Program
    {
        private static void Main()
        {
            var input = File.ReadAllLines("rosalind_lcs.txt").ToList();

            var t = new Stopwatch();
            t.Restart();
            var lcs = LongestCommonSubstring(input);
            t.Stop();

            File.WriteAllLines("output.txt", lcs);
            Console.WriteLine("Finished in {0} msec.", t.ElapsedMilliseconds);
            Console.ReadLine();
        }

        public static IEnumerable<string> LongestCommonSubstring(List<string> strings)
        {
            var lcs = LongestCommonSubstring(strings[0], strings[1]);

            for (var i = 2; i < strings.Count(); i++)
            {
                var new_lcs = new BestStrings();

                foreach (var s in lcs) new_lcs.Add(LongestCommonSubstring(s, strings[i]));

                lcs = new_lcs;
            }

            return lcs;
        }

        private static BestStrings LongestCommonSubstring(string s1, string s2)
        {
            var lcs = new BestStrings();

            for (var i = 1 - s2.Length; i < s1.Length; i++)
            {
                var substrings = BestSubstringWithAlignment(s1, s2, i);

                if (substrings.Length == 0) continue;

                lcs.Add(substrings);
            }

            return lcs;
        }

        private static BestStrings BestSubstringWithAlignment(string s1, string s2, int offset)
        {
            var substrings = new BestStrings();

            var substring = "";
            for (var i = Math.Max(0, offset); i < s1.Length && i < s2.Length + offset; i++)
            {
                var c1 = s1[i];

                var c2 = s2[i - offset];

                if (c1 == c2)
                {
                    substring = substring + c1;
                }
                else
                {
                    substrings.Add(substring);
                    substring = "";
                }
            }
            substrings.Add(substring);

            return substrings;
        }

        sealed class BestStrings : Collection<string>
        {
            public int Length
            {
                get { return base[0].Length; }
            }

            public BestStrings()
            {
                base.Add("");
            }

            public new void Add(string s)
            {
                if (s.Length == 0 || s.Length < Length || Contains(s)) return;

                if (s.Length > Length) Clear();
                base.Add(s);
            }

            public void Add(IEnumerable<string> collection)
            {
                foreach (var s in collection) Add(s);
            }
        }
    }
}
EN

回答 2

Code Review用户

发布于 2013-02-05 06:00:42

首先是一些风格。

简单命名的变量根本无助于可读性。c1c2s1s2是个坏主意。我知道这只是一个挑战,而不是生产代码,但保持一致的风格是一个好习惯。

其次,我首先使用string.Contains()方法。它将发现是否存在指定的子字符串,并且应该有助于清理一些代码。

在这个思路中,我决定从第一个字符串中所有可能的子字符串开始,然后搜索所有字符串的列表。

代码语言:javascript
复制
public static IEnumerable<string> LongestCommonSubstrings(List<string> strings)
{
    var firstString = strings.FirstOrDefault();

    var allSubstrings = new List<string>();
    for(int substringLength = firstString.Length -1; substringLength >0; substringLength--)
    {
        for(int offset = 0; (substringLength + offset) < firstString.Length; offset++)
        {
            string currentSubstring = firstString.Substring(offset,substringLength);
            if (!System.String.IsNullOrWhiteSpace(currentSubstring) && !allSubstrings.Contains(currentSubstring))
            {
                allSubstrings.Add(currentSubstring);
            }
        }
    }

    return allSubstrings.OrderBy(subStr => subStr).ThenByDescending(subStr => subStr.Length).Where(subStr => strings.All(currentString => currentString.Contains(subStr)));
}

这还将允许我们在linq上执行一个.FirstOrDefault(),并获得最大的(由于orderby()调用)。

(为了测试,我使用了一个静态字符串列表,而不是一个文件:)

代码语言:javascript
复制
public static void Run()
{
    var input = new List<string>{
        "GATTACA",
        "TAGACCA",
        "ATACA",
    };

    var t = new Stopwatch();
    t.Restart();
    var lcs = LongestCommonSubstrings(input);
    t.Stop();

    Console.WriteLine("All Common substrings: \r\n{0}", string.Join("\r\n", lcs));
    Console.WriteLine("Finished in {0} msec.", t.ElapsedMilliseconds);
    Console.ReadLine();
}

免责声明:尚未完全测试上述代码。可能会伤害你的大脑/电脑。

票数 6
EN

Code Review用户

发布于 2013-02-07 19:42:13

我不确定你是否对更好的算法感兴趣,但如果是的话,这里有一些

在这个特定的实现中,我将考虑更改处理子字符串的方式。您目前正在执行大量的字符串连接,每次分配一个新的string对象时,这会很慢。因为您只是在跟踪子字符串,所以可以存储每个匹配的源字符串、启动索引和长度。这将为您节省大量内存,并且运行得更快。

如果您真正依赖于拥有单独的字符串对象,那么至少要知道匹配的范围,然后执行一个substring()来提取它,而不是一次只构建一个字符。

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/21252

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档