【问题标题】:Finding List A in List B?在列表 B 中找到列表 A?
【发布时间】:2013-06-18 01:27:04
【问题描述】:

我有一个要在列表 B 中找到的通用点 NET 列表 A - 如何在列表 B 中找到列表 A?我需要列表 A 在列表 B 中开始位置的索引。

【问题讨论】:

  • 听起来像 Knuth–Morris–Pratt 的修改版本可以在这里应用 en.wikipedia.org/wiki/…
  • @BrokenGlass:或者只是一个简单的“子字符串”搜索。
  • 你想为 String 列表还是 SomeObject 列表实现这个?
  • 附加信息:List A 和 List B 都被声明为 List;我在想可能有办法用 LINQ 做到这一点?在列表 B 中找到列表 A?

标签: .net vb.net linq


【解决方案1】:

有一个非常幼稚的方法是O(|A| * |B|)。基本上,这是朴素的子串搜索算法1

for(int i = 0; i < B.Count - A.Count; i++) {
     bool matchPossible = true;
     for(int j = 0; matchPossible && j < A.Count; j++) {
         if (!B[i + j].Equals(A[j])) { // should check for B[i + j] == null
              matchPossible = false;
              break;
         }
     }
     if(matchPossible) {
         return i;
     }
 }     
 return -1;

我省略了一些你应该做的明显错误检查,以便你可以专注于方法。我评论了其中一项明显的检查。这将为您提供B 中的索引,其中可以找到A

除非基准测试表明该方法正在拖累您,否则我会坚持这样做。如果是的话,你需要看看像 Knuth-Morris-Pratt 这样更复杂的东西。

List A 和 List B 都被声明为List&lt;string&gt;;我在想可能有办法用 LINQ 做到这一点?在列表B 中查找列表A

当然。

for(int i = 0; i < B.Count - A.Count; i++) {
    if(B.SequenceEqual(A.Skip(i).Take(B.Count))) {
        return i;
    }
}
return -1;

请注意,这与我上面给出的算法基本相同,只是表达得更清楚一点。但是,这种方法有一个缺点。我不知道Enumerable.Skip 是否足够聪明,可以在索引器可用时使用它。如果不使用索引器,此版本的性能可能低于原始版本。这就是我在最初的方法中没有使用它的原因。

另外,您必须翻译成 VB.NET,抱歉;我手边没有编译器,而且我说流利的 C#(所以不需要编译器来检查我的语法),但不是 VB。

1:出于某种原因,我突然回想起这种方法在 K&R 的小 C 书中有所涉及?任何人都可以验证;我不知道我的副本现在在哪里?2

2:找到了。是的,第 4.1 节。函数是strindex

【讨论】:

  • 感谢 Jason - 对于您的上述算法,我接受了;我只需要将其转换为 VB.NET。我还将在下面的@SysDragon 上运行测试。
【解决方案2】:

这是this answer 的通用实现,它允许任何IList,并且可以传入可选的IEqualityComparer。这不是最快的搜索方式,但它是进行更高级答案的良好起点。

static class GenericSearcher
{

    static readonly int[] Empty = new int[0];

    public static int[] Locate<T>(this IList<T> self, IList<T> candidate)
    {
        return Locate(self, candidate, EqualityComparer<T>.Default);
    }

    public static int[] Locate<T>(this IList<T> self, IList<T> candidate, IEqualityComparer<T> comparer)
    {
        if (IsEmptyLocate(self, candidate))
            return Empty;

        var list = new List<int>();

        for (int i = 0; i < self.Count; i++)
        {
            if (!IsMatch(self, i, candidate, comparer))
                continue;

            list.Add(i);
        }

        return list.Count == 0 ? Empty : list.ToArray();
    }

    static bool IsMatch<T>(IList<T> array, int position, IList<T> candidate, IEqualityComparer<T> comparer)
    {
        if (candidate.Count > (array.Count - position))
            return false;

        for (int i = 0; i < candidate.Count; i++)
            if (comparer.Equals(array[position + i],candidate[i]) == false)
                return false;

        return true;
    }

    static bool IsEmptyLocate<T>(ICollection<T> array, ICollection<T> candidate)
    {
        return array == null
            || candidate == null
            || array.Count == 0
            || candidate.Count == 0
            || candidate.Count > array.Count;
    }

}

已使用 VB 代码更新(感谢 ajakblackgoat

Module GenericSearcher

    Private ReadOnly Empty As Integer() = New Integer(-1) {}

    <System.Runtime.CompilerServices.Extension> _
    Public Function Locate(Of T)(self As IList(Of T), candidate As IList(Of T)) As Integer()
        Return Locate(self, candidate, EqualityComparer(Of T).[Default])
    End Function

    <System.Runtime.CompilerServices.Extension> _
    Public Function Locate(Of T)(self As IList(Of T), candidate As IList(Of T), comparer As IEqualityComparer(Of T)) As Integer()
        If IsEmptyLocate(self, candidate) Then
            Return Empty
        End If

        Dim list = New List(Of Integer)()

        For i As Integer = 0 To self.Count - 1
            If Not IsMatch(self, i, candidate, comparer) Then
                Continue For
            End If

            list.Add(i)
        Next

        Return If(list.Count = 0, Empty, list.ToArray())
    End Function

    Private Function IsMatch(Of T)(array As IList(Of T), position As Integer, candidate As IList(Of T), comparer As IEqualityComparer(Of T)) As Boolean
        If candidate.Count > (array.Count - position) Then
            Return False
        End If

        For i As Integer = 0 To candidate.Count - 1
            If Not comparer.Equals(array(position + i), candidate(i)) Then
                Return False
            End If
        Next

        Return True
    End Function

    Private Function IsEmptyLocate(Of T)(array As ICollection(Of T), candidate As ICollection(Of T)) As Boolean
        Return array Is Nothing OrElse candidate Is Nothing OrElse array.Count = 0 OrElse candidate.Count = 0 OrElse candidate.Count > array.Count
    End Function

End Module

【讨论】:

    【解决方案3】:

    试试这个接收ListAListB的函数:

    Dim index As Integer = listB.IndexOf(listA(0))
    Dim iCont As Integer
    Dim bMatch As Boolean
    
    While index >= 0
        bMatch = True
        iCont = 1
    
        'Check if lists match on all the items in list A
        While bMatch
            index += 1
            bMatch = (index < listB.Count) AndAlso (listA(iCont) = listB(index))
    
            iCont += 1
            If iCont >= ListA.Count Then Exit While
        End While         
    
        If bMatch Then Exit While
        index = listB.IndexOf(listA(0), index)
    End While
    
    return bMatch
    

    【讨论】:

    • 这里显示的 VB 代码是否与上面 Jason 的 C# 代码基本一致?在我看来,除了你的算法是在 VB 中之外,它们几乎是相同的算法?
    • 你想说什么?它们不是同一个算法。有很大的不同,但是是的,它们有一些共同点。我什至没有看到他的答案(不是 vb.net)。基本上,这两个答案都共享检查两个列表的相似性的想法。我的是跳到元素,杰森的答案是遍历所有元素。
    • 谢谢:我接受了你的回答和杰森的回答;我计划测试两者。我提出问题的原因是我几乎所有的工作都是在 C# 中完成的,但是这个项目是在 VB.NET 中的——我只是不像 C# 那样熟悉 VB。我将检查您和 Jason 的上述情况,并可能运行测试以查看它们的性能。再次感谢您的回复!
    【解决方案4】:

    这是一个相当简单的方法。它应该适用于任何具有相等比较器的类型。我用字符串和整数列表测试了它。此函数仅迭代 ListB 的第一个元素。因此,它只会步进与 ListA 中与 ListB 中的第一个元素相等的元素到开始相等序列的元素的次数一样多。

    Private Function FindList(Of T)(ListA As IList(Of T), ListB As List(Of T)) As Integer
        Dim FoundIndex As Integer = ListA.IndexOf(ListB(0))
        Dim FoundlList As Boolean = False
        While FoundIndex <> -1
            If ListA.Skip(FoundIndex).Take(ListB.Count).SequenceEqual(ListB) Then
                Return FoundIndex
            End If
            FoundIndex = FindIndex(ListA, FoundIndex, ListB(0))
        End While
        Return FoundIndex
    End Function
    Private Function FindIndex(Of T)(List As IList(Of T), Start As Integer, FindItem As T) As Integer
        Dim TempList = List.Skip(Start + 1).ToList
        Return TempList.IndexOf(FindItem) + (List.Count - TempList.Count)
    End Function
    

    【讨论】:

      猜你喜欢
      • 2019-08-25
      • 2016-03-17
      • 1970-01-01
      • 1970-01-01
      • 2016-07-14
      • 1970-01-01
      • 2016-11-08
      • 2015-08-28
      • 1970-01-01
      相关资源
      最近更新 更多