C# 如何最有效地(快速)匹配两个列表?

C# 如何最有效地(快速)匹配两个列表?,c#,.net,performance,optimization,search,C#,.net,Performance,Optimization,Search,我有两个项目、来源和目标的列表。源列表中的项在目标列表中将有0到n个匹配项,但不会有重复的匹配项 考虑到两个列表都已排序,您将如何在性能方面最有效地进行匹配 例如: source = {"1", "2", "A", "B", ...} target = {"1 - new music", "1 / classic", "1 | pop", "2 edit", "2 no edit", "A - sing", "B (listen)", ...} 基本上,匹配是简单的前缀匹配,但是假设您有一个名

我有两个项目、来源和目标的
列表。源列表中的项在目标列表中将有0到n个匹配项,但不会有重复的匹配项

考虑到两个列表都已排序,您将如何在性能方面最有效地进行匹配

例如:

source = {"1", "2", "A", "B", ...}
target = {"1 - new music", "1 / classic", "1 | pop", "2 edit", "2 no edit", "A - sing", "B (listen)", ...}
基本上,匹配是简单的前缀匹配,但是假设您有一个名为
MatchName
的方法。如果要进行更多优化搜索,可以使用新函数
NameMatch
仅比较两个字符串并返回布尔值


最终,源[0]将具有源[0]。在这种情况下,匹配将包含目标[0、1和2]。我认为最好的方法是准备一个索引。像这样(Javascript)


在这种情况下,实际上并不需要排序良好的列表。

经过编辑、重写、未测试的列表应该具有O(源+目标)性能。 用法可以是MatchMaker.Match(源,目标).ToList()


我反对过早优化。

我不确定这是否值得尝试优化。您可以用它实现某种二进制搜索,但其有效性相当有限。我们在谈论多少元素

目标中没有不匹配的元素 假设列表已排序,且
目标
中不存在无法与
匹配的元素:

static List<string>[] FindMatches(string[] source, string[] target)
{
    // Initialize array to hold results
    List<string>[] matches = new List<string>[source.Length];
    for (int i = 0; i < matches.Length; i++)
        matches[i] = new List<string>();

    int s = 0;
    for (int t = 0; t < target.Length; t++)
    {
        while (!MatchName(source[s], target[t]))
        {
            s++;
            if (s >= source.Length)
                return matches;
        }

        matches[s].Add(target[t]);
    }

    return matches;
}
这两者在其他方面基本相同。如您所见,在目标元素中循环一次,当您不再找到匹配项时,将索引前进到源数组

如果源元素的数量有限,那么进行一次更智能的搜索可能是值得的。如果源元素的数量也很大,那么假定的好处就会减少


然后,在调试模式下,在我的机器上,第一个算法需要0.18秒,目标元素100万个。第二个更快(0.03秒),但这是因为正在进行的比较更简单。可能您必须将所有内容与第一个空格字符进行比较,使其速度大大降低。

很明显,一旦超过当前源前缀,您就会停止在目标列表上循环。在这种情况下,您最好使用前缀方法,而不是匹配的方法,这样您就可以知道当前前缀是什么,并在经过它时停止搜索目标。

在对项目进行排序时,您可以在列表中循环:

string[] source = {"1", "2", "A", "B" };
string[] target = { "1 - new music", "1 / classic", "1 | pop", "2 edit", "2 no edit", "A - sing", "B (listen)" };

List<string>[] matches = new List<string>[source.Length];
int targetIdx = 0;
for (int sourceIdx = 0; sourceIdx < source.Length; sourceIdx++) {
   matches[sourceIdx] = new List<string>();
   while (targetIdx < target.Length && NameMatch(source[sourceIdx], target[targetIdx])) {
      matches[sourceIdx].Add(target[targetIdx]);
      targetIdx++;
   }
}
string[]source={“1”、“2”、“A”、“B”};
string[]target={“1-新音乐”,“1/经典”,“1 |流行音乐”,“2编辑”,“2不编辑”,“A-唱”,“B(听)”};
List[]matches=新列表[source.Length];
int targetIdx=0;
for(int-sourceIdx=0;sourceIdx
这里有一个答案,它只在两个列表中循环一次,使用逻辑将两个列表都作为优化排序。正如大多数人所说,我不会太担心优化,因为这些答案中的任何一个都可能足够快,我会选择最具可读性和可维护性的解决方案

话虽如此,我需要和我的咖啡做点什么,给你。下面的优点之一是,它允许目标列表中的内容与源列表中的内容不匹配,尽管我不确定您是否需要该功能

class Program
{
    public class Source
    {
        private readonly string key;
        public string Key { get { return key;}}

        private readonly List<string> matches = new List<string>();
        public List<string> Matches { get { return matches;} }

        public Source(string key)
        {
            this.key = key;
        }
    }

    static void Main(string[] args)
    {
        var sources = new List<Source> {new Source("A"), new Source("C"), new Source("D")};
        var targets = new List<string> { "A1", "A2", "B1", "C1", "C2", "C3", "D1", "D2", "D3", "E1" };

        var ixSource = 0;
        var currentSource = sources[ixSource++];

        foreach (var target in targets)
        {
            var compare = CompareSourceAndTarget(currentSource, target);

            if (compare > 0)
                continue;

            // Try and increment the source till we have one that matches 
            if (compare < 0)
            {
                while ((ixSource < sources.Count) && (compare < 0))
                {
                    currentSource = sources[ixSource++];
                    compare = CompareSourceAndTarget(currentSource, target);
                }
            }

            if (compare == 0)
            {
                currentSource.Matches.Add(target);
            }

            // no more sources to match against
            if ((ixSource > sources.Count))
                break;
        }

        foreach (var source in sources)
        {
            Console.WriteLine("source {0} had matches {1}", source.Key, String.Join(" ", source.Matches.ToArray()));
        }
    }

    private static int CompareSourceAndTarget(Source source, string target)
    {
        return String.Compare(source.Key, target.Substring(0, source.Key.Length), StringComparison.OrdinalIgnoreCase);
    }
}
类程序
{
公共类源
{
私有只读字符串密钥;
公共字符串密钥{get{return Key;}}
私有只读列表匹配项=新列表();
公共列表匹配{get{return Matches;}}
公共源(字符串键)
{
this.key=key;
}
}
静态void Main(字符串[]参数)
{
var sources=新列表{新源(“A”)、新源(“C”)、新源(“D”)};
var目标=新列表{“A1”、“A2”、“B1”、“C1”、“C2”、“C3”、“D1”、“D2”、“D3”、“E1”};
var ixSource=0;
var currentSource=源[ixSource++];
foreach(目标中的var目标)
{
var compare=比较源和目标(currentSource,target);
如果(比较>0)
继续;
//尝试增加源,直到有一个匹配的源
如果(比较<0)
{
而((ixSourcesources.Count))
打破
}
foreach(源中的var源)
{
WriteLine(“source{0}有匹配项{1}”、source.Key、String.Join(“、source.matches.ToArray());
}
}
私有静态int CompareSourceAndTarget(源源、字符串目标)
{
返回String.Compare(source.Key,target.Substring(0,source.Key.Length),StringComparison.OrdinalIgnoreCase);
}
}

既然它们是排序的,那么它不就是一个基本的O(N)合并循环吗

ia = ib = 0;
while(ia < na && ib < nb){
  if (A[ia] < B[ib]){
    // A[ia] is unmatched
    ia++;
  }
  else if (B[ib] < A[ia]){
    // B[ib] is unmatched
    ib++;
  }
  else {
    // A[ia] matches B[ib]
    ia++;
    ib++;
  }
}
while(ia < na){
  // A[ia] is unmatched
  ia++;
}
while(ib < nb){
  // B[ib] is unmatched
  ib++;
}
ia=ib=0;
而(ia
这与您的另一个问题本质上不完全相同吗:我想在这一个问题中,您可以根据元素排序的知识添加更多优化。它们相似,但
static List<string>[] FindMatches(string[] source, string[] target)
{
    // Initialize array to hold results
    List<string>[] matches = new List<string>[source.Length];
    for (int i = 0; i < matches.Length; i++)
        matches[i] = new List<string>();

    int s = 0;
    for (int t = 0; t < target.Length; t++)
    {
        int m = CompareName(source[s], target[t]);
        if (m == 0)
        {
            matches[s].Add(target[t]);
        }
        else if (m > 0)
        {
            s++;
            if (s >= source.Length)
                return matches;
            t--;
        }
    }

    return matches;
}

static int CompareName(string source, string target)
{
    // Whatever comparison you need here, this one is really basic :)
    return target[0] - source[0];
}
string[] source = {"1", "2", "A", "B" };
string[] target = { "1 - new music", "1 / classic", "1 | pop", "2 edit", "2 no edit", "A - sing", "B (listen)" };

List<string>[] matches = new List<string>[source.Length];
int targetIdx = 0;
for (int sourceIdx = 0; sourceIdx < source.Length; sourceIdx++) {
   matches[sourceIdx] = new List<string>();
   while (targetIdx < target.Length && NameMatch(source[sourceIdx], target[targetIdx])) {
      matches[sourceIdx].Add(target[targetIdx]);
      targetIdx++;
   }
}
class Program
{
    public class Source
    {
        private readonly string key;
        public string Key { get { return key;}}

        private readonly List<string> matches = new List<string>();
        public List<string> Matches { get { return matches;} }

        public Source(string key)
        {
            this.key = key;
        }
    }

    static void Main(string[] args)
    {
        var sources = new List<Source> {new Source("A"), new Source("C"), new Source("D")};
        var targets = new List<string> { "A1", "A2", "B1", "C1", "C2", "C3", "D1", "D2", "D3", "E1" };

        var ixSource = 0;
        var currentSource = sources[ixSource++];

        foreach (var target in targets)
        {
            var compare = CompareSourceAndTarget(currentSource, target);

            if (compare > 0)
                continue;

            // Try and increment the source till we have one that matches 
            if (compare < 0)
            {
                while ((ixSource < sources.Count) && (compare < 0))
                {
                    currentSource = sources[ixSource++];
                    compare = CompareSourceAndTarget(currentSource, target);
                }
            }

            if (compare == 0)
            {
                currentSource.Matches.Add(target);
            }

            // no more sources to match against
            if ((ixSource > sources.Count))
                break;
        }

        foreach (var source in sources)
        {
            Console.WriteLine("source {0} had matches {1}", source.Key, String.Join(" ", source.Matches.ToArray()));
        }
    }

    private static int CompareSourceAndTarget(Source source, string target)
    {
        return String.Compare(source.Key, target.Substring(0, source.Key.Length), StringComparison.OrdinalIgnoreCase);
    }
}
ia = ib = 0;
while(ia < na && ib < nb){
  if (A[ia] < B[ib]){
    // A[ia] is unmatched
    ia++;
  }
  else if (B[ib] < A[ia]){
    // B[ib] is unmatched
    ib++;
  }
  else {
    // A[ia] matches B[ib]
    ia++;
    ib++;
  }
}
while(ia < na){
  // A[ia] is unmatched
  ia++;
}
while(ib < nb){
  // B[ib] is unmatched
  ib++;
}