我有2 lists<string>
项,来源和目标。源列表中的项目在目标列表中将具有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
只比较2个字符串并返回一个bool。
在最后,源[0]将包含源[0] .Matches包含目标[0,1和2]。
答案 0 :(得分:3)
我不确定这是否值得尝试优化到目前为止。你可以用它实现某种二进制搜索,但它的有效性会相当有限。我们谈论了多少元素?
假设列表已排序,target
中没有与source
无法匹配的元素:
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;
}
如果target
中存在的元素可能在source
中没有匹配,则上述内容将中断(如果元素不在目标的末尾)。要解决这个问题,最好采用不同的实现进行比较。我们需要它返回'小于','相等'或'大于',而不是布尔值,就像在排序中使用Comparer一样:
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];
}
这两者基本上是相同的。如您所见,您循环遍历目标元素一次,当您不再找到匹配项时将索引推进到源数组。
如果源元素的数量有限,则可能值得进行更智能的搜索。如果源元素的数量也很大,那么假设的好处就会减少。
然后,在调试模式下,第一个算法在我的机器上 0.18秒, 100万目标元素。第二个更快( 0.03秒),但这是因为正在进行更简单的比较。可能你必须将所有内容与第一个空白字符进行比较,使其显着变慢。
答案 1 :(得分:1)
编辑,重写,未经测试,应具有O(源+目标)性能。 用法可以是MatchMaker.Match(source,target).ToList();
public static class MatchMaker
{
public class Source
{
char Term { get; set; }
IEnumerable<string> Results { get; set; }
}
public static IEnumerable<Source> Match(IEnumerable<string> source, IEnumerable<string> target)
{
int currentIndex = 0;
var matches = from term in source
select new Source
{
Term = term[0],
Result = from result in target.FromIndex(currentIndex)
.TakeWhile((r, i) => {
currentIndex = i;
return r[0] == term[0];
})
select result
};
}
public static IEnumerable<T> FromIndex<T>(this IList<T> subject, int index)
{
while (index < subject.Count) {
yield return subject[index++];
}
}
}
一个简单的LinQ,可能不是最快的,但最清楚的是:
var matches = from result in target
from term in source
where result[0] == term[0]
select new {
Term: term,
Result: result
};
我反对过早优化。
答案 2 :(得分:1)
在对项目进行排序时,您可以遍历列表:
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++;
}
}
答案 3 :(得分:1)
这是一个答案,它只使用两个列表作为优化排序的逻辑遍历两个列表。像大多数人所说的那样,我不会过于担心优化问题,因为对于任何这些答案来说,它可能足够快,我会选择最易读和可维护的解决方案。
话虽这么说,我需要和我的咖啡有关,所以你走了。下面的优点之一是它允许目标列表中的内容在源列表中没有匹配,但我不确定您是否需要该功能。
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);
}
}
答案 4 :(得分:1)
由于它们是排序的,它不只是一个基本的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++;
}
答案 5 :(得分:0)
我认为最好的方法是准备索引。像这样(Javascript)
index = [];
index["1"] = [0,1,2];
index["2"] = [3,4];
在这种情况下,并不真正需要排序良好的列表。
答案 6 :(得分:0)
当你超过当前的源前缀时,你显然会停止遍历目标列表。在这种情况下,使用前缀方法比使用匹配方法更好,这样您就可以知道当前前缀是什么,并且如果超过它就停止搜索目标。