【问题标题】:Substract shortest string containing all search criteria减去包含所有搜索条件的最短字符串
【发布时间】:2016-11-03 04:34:30
【问题描述】:

我有一个问题要解决,在给定字符串source 和搜索条件集合criteria 的情况下,算法必须返回包含criteria 的所有项的source 的最短子字符串。

==================================

更新

  • 相同的搜索条件可能在多个源字符串中 次。在这种情况下,需要返回子字符串 包含搜索条件的特定实例,使得 它是所有可能的子字符串中最短的。
  • 搜索项中可以包含空格,例如hello world
  • 搜索条件的顺序无关紧要,只要它们都在结果子字符串中

===================================

String source = "aaa wwwww fgffsd ththththt sss sgsgsgsghs bfbfb hhh sdfg kkk dhdhtrherhrhrthrthrt ddfhdetehehe kkk wdwd aaa vcvc hhh zxzx sss nbnbn";
List<String> criteria = new List<string> { "kkk", "aaa", "sss", "hhh" };

上面的输入应该返回以下子字符串:kkk wdwd aaa vcvc hhh zxzx sss

不幸的是,我花了很多时间尝试编写这样的算法,但我无法做到恰到好处。以下是我目前得到的代码:

public struct Extraction
{
    public int Start { get; set; }
    public int End { get; set; }
    public int Length
    {
        get
        {
            var length = this.End - this.Start;
            return length;
        }
    }

    public Extraction(int start, int end)
    {
        this.Start = start;
        this.End = end;
    }
}

public class TextExtractor
{
    private String _source;
    private Dictionary<String, List<Int32>> _criteriaIndexes;
    private Dictionary<String, int> _entryIndex;

    public TextExtractor(String source, List<String> searchCriteria)
    {
        this._source = source;
        this._criteriaIndexes = this.ExtractIndexes(source, searchCriteria);
        this._entryIndex = _criteriaIndexes.ToDictionary(x => x.Key, v => 0);
    }

    public String Extract()
    {
        List<Extraction> possibleExtractions = new List<Extraction>();

        int index = 0;
        int min = int.MaxValue;
        int max = 0;
        bool shouldStop = false;
        while (index < _criteriaIndexes.Count && !shouldStop)
        {
            Boolean compareWithAll = index == _criteriaIndexes.Count - 1;
            if (!compareWithAll)
            {
                var current = _criteriaIndexes.ElementAt(index);
                this.CalculateMinMax(current, ref min, ref max);
                index++;
            }
            else
            {
                var entry = _criteriaIndexes.Last();
                while (_entryIndex[entry.Key] < entry.Value.Count)
                {
                    int a = min;
                    int b = max;
                    this.CalculateMinMax(entry, ref a, ref b);

                    _entryIndex[entry.Key]++;
                    Extraction ext = new Extraction(a, b);
                    possibleExtractions.Add(ext);
                }
                int k = index - 1;

                while (k >= 0)
                {
                    var prev = _criteriaIndexes.ElementAt(k);
                    if (prev.Value.Count - 1 > _entryIndex[prev.Key])
                    {
                        _entryIndex[prev.Key]++;
                        break;
                    }
                    else
                    {
                        k--;
                    }
                }
                shouldStop = _criteriaIndexes.All(x => x.Value.Count - 1 <= _entryIndex[x.Key]);
                _entryIndex[entry.Key] = 0;
                index = 0;
                min = int.MaxValue;
                max = 0;
            }
        }

        Extraction shortest = possibleExtractions.First(x => x.Length.Equals(possibleExtractions.Min(p => p.Length)));
        String result = _source.Substring(shortest.Start, shortest.Length);
        return result;
    }

    private Dictionary<String, List<Int32>> ExtractIndexes(String source, List<String> searchCriteria)
    {
        Dictionary<String, List<Int32>> result = new Dictionary<string, List<int>>();
        foreach (var criteria in searchCriteria)
        {
            Int32 i = 0;
            Int32 startingIndex = 0;
            var indexes = new List<int>();
            while (i > -1)
            {
                i = source.IndexOf(criteria, startingIndex);
                if (i > -1)
                {
                    startingIndex = i + 1;
                    indexes.Add(i);
                }
            }
            if (indexes.Any())
            {
                result.Add(criteria, indexes);
            }

        }
        return result;
    }

    private void CalculateMinMax(KeyValuePair<String, List<int>> current, ref int min, ref int max)
    {
        int j = current.Value[_entryIndex[current.Key]];
        if (j < min)
        {
            min = j;
        }
        int indexPlusWordLength = j + current.Key.Length;
        if (indexPlusWordLength > max)
        {
            max = indexPlusWordLength;
        }
    }
}

如果有人能指出我的算法哪里出错了,我将不胜感激。此外,我觉得这是一个非常幼稚的实现。也许有比尝试索引组合更好的方法来解决这个问题?

谢谢!

【问题讨论】:

  • 你的算法很复杂。代码应该相当简单。 1)在空格周围分割字符串。 2) 从第一个单词开始,直到找到所有四个条件。保存结果 3) 从单词二开始,直到找到所有找到的条件。如果新长度比第一个长度短,则用新结果替换旧结果。 4) 在单词 3 重复,然后是单词 4,然后是单词 5。
  • 是的,我知道我的算法很复杂。这就是我在这里问的原因之一:我想看到解决这个问题的不同方法。我将尝试您描述的方法,但请注意,相同的搜索条件可能会多次出现在源字符串中,您在建议的解决方案中是否考虑过这一点?
  • 另外,搜索条件中是否有可能包含空格?即{ "hello there", "foo", "bar" };
  • 找到的项目在源字符串中的顺序是否必须与它们在条件列表中的顺序相同?即,如果您的标准有{"one", "two", "three"},并且您的字符串是"two three one two five three",那么正确的响应是"two three one" 还是"one two five three"
  • @RufusL 是的,实际上,搜索条件中可以有一个空格。并且搜索项的顺序无关紧要,只要它们都在子字符串中找到即可。

标签: c# string algorithm search


【解决方案1】:

让 Java 内置类来完成这项工作。如何将您的条件转换为正则表达式模式。如果条件是 X 或 Y 或 Z 。 . .,将其转换为“(X)|(Y)|(Z)|...”形式的正则表达式,编译它,然后针对源字符串执行它。

当然,这会返回最左边的匹配项。您可以编写一个非常简单的循环,遍历所有出现、缓存它们并选择最短的——或最左边的最短的——或者,如果两个或更多同样短,那么所有这些。

【讨论】:

  • 一个代码示例将帮助您为这个答案获得一些分数。此外,这是 C#,而不是 Java。 :)
【解决方案2】:

这是一个更简单的算法,可以为您提供最短的子字符串。

void Main()
{
    String source = "aaa wwwww fgffsd ththththt sss ww sgsgsgsghs bfbfb hhh sdfg kkk " +
        "dhdhtrherhrhrthrthrt ddfhdetehehe kkk wdwd aaa vcvc hhh zxzx sss ww nbnbn";
    List<String> criteria = new List<string> { "kkk", "aaa", "sss ww", "hhh" };
    var result = GetAllSubstringContainingCriteria(source, criteria)
        .OrderBy(sub => sub.Length).FirstOrDefault();
    // result is "kkk wdwd aaa vcvc hhh zxzx sss ww"
}

private IEnumerable<string> GetAllSubstringContainingCriteria(
    string source, List<string> criteria)
{
    for (int i = 0; i < source.Length; i++)
    {
        var subString = source.Substring(i);
        if (criteria.Any(crit => subString.StartsWith(crit)))
        {
            var lastWordIndex = 
                GetLastCharacterIndexFromLastCriteriaInSubstring(subString, criteria);
            if (lastWordIndex >= 0)
                yield return string.Join(" ", subString.Substring(0, lastWordIndex));
        }
        else
            continue;
    }
}

private int GetLastCharacterIndexFromLastCriteriaInSubstring(
    string subString, List<string> criteria)
{
    var results = criteria.Select(crit => new { 
            index = subString.IndexOf(crit),
            criteria = crit});

    return results.All(result => result.index >= 0)
        ? results.Select(result => result.index + result.criteria.Length).Max()
        : -1;
}

【讨论】:

  • 阅读您的解决方案,似乎期望单词必须与“标准”列表中列出的顺序相同?我不认为这被列为要求。
  • 你说对了一半,我的算法是错误的,因为它期望第一个标准是第一个单词。我现在已经纠正了这个错误。
  • @KiNeTiC 这确实有效。唯一的问题是它假设搜索项中没有空格,这是不正确的,但我没有将其指定为初始要求的一部分(我的错)。所以 +1 为工作解决方案,但如果您可以修改您的答案以考虑该额外要求。我会把它标记为正确答案。
  • 好的,我已经更新了我的答案。该算法几乎相同,只是我不再将源代码拆分为单词列表..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-20
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
  • 2021-06-12
  • 1970-01-01
  • 2019-05-17
相关资源
最近更新 更多