假设我有一个N个元素列表(list1
)。
然后我有一个M< = N元素的列表,其中包含我要从其他列表中删除的所有元素索引(list2
)。
我想以最有效的方式使用list1
索引相应删除list2
上的元素。
第一种方法是通过for循环:
for (int i = 0; i < list2.Count; i++)
list1.RemoveAt(list2[i]);
这项工作非常好(我当然需要list2
订购)但在最坏的情况下具有O(M * N)复杂度(M次迭代,RemoveAt
为O(n))
另一个解决方案是创建一个临时列表,用 not 删除的元素填充它,然后使用LINQ intersect方法
List<T> tempList = new List<T>();
for (int i = 0; i < list1.Count; i++)
if (list2.Contains(i))
tempList.Add(list1[i]);
list1.Intersect(tempList);
虽然起初我对Intersect
的O(N + M)复杂性感到兴奋,但我最终意识到我需要首先进行N次迭代来填充tempList
,然后任何优势都会丢失(我们假设list2是HashSet
,在第二种情况下,我不关心排序,只是关于O(1)Contains
)
经过一番挖掘后,我仍无法找到一种方法来执行RemoveAll
类似的方法,该方法会根据list1
中存储的值从list2
中删除所有元素。< / p>
是否有机会尽可能提高效率?
PS:对于所有会认为“过早优化是所有邪恶的根源”的人,请考虑我的代码实际上工作正常但是,因为我正在处理一个严格的时间依赖问题,每次迭代节省几ns (我将有大约150k次迭代)可以带来显着的改进。编辑:正如@InBetween正确指出的那样,在第二个解决方案上使用Intersect
实际上是无用的,减少它的复杂性就会降低。
答案 0 :(得分:2)
如果订购了list2
,那么只需使用您优化的第二个解决方案:
var exceptIndex = 0;
var newList = new List<T>();
for (var i = 0; i < list1.Length; i++)
{
if (i != list2[exceptIndex]) newList.Add(list1[i]);
else exceptIndex++
}
return newList;
答案 1 :(得分:1)
List1 = Enumerable.Range(0,List1.Count).Except(List2).Select(i=>List1[i]).ToList()
或
bool[] shouldWeSayGoodbye = new bool[List1.Count];
for(var i=0;i<List2.Count;i++){
shouldWeSayGoodbye[List2[i]]=true;
}
typeof_list1 List3 = new List<typeof_list1>();
for(var i=0;i<List1.Count;i++){
if(!shouldWeSayGoodbye[i]){
List3.Add(List1[i])
}
}
一些测试显示,循环至少击败Linq x4次。
答案 2 :(得分:1)
List<T>.Add
实际上是O(1)
。
这是我想出的,应该去O(n)
:
List<T> resultList = new List<T>(list1.Count); // high capacity!
int curIdx = list1.Count - 1; // start at the end of list1
// assumes list2 is sorted descendingly
list2.Add(-1); // add a final -1 index to make following code nicer
foreach (int targetIdx in list2)
{
while (curIdx > targetIdx)
{
resultList.Add(list1[curIdx]); // both operations are O(1)
curIdx--;
}
curIdx--;
}
// resultList is reversed
澄清:
list1:[10,11,12,13,14]
list2:[1,3] 想要的结果:[10,12,14]
我们希望将list2
排序为desc,并为其添加最后的-1:
list2:[3,1,-1]
最终的结果实际上是反向所需的结果。之后反转列表可以在O(n)
中完成,因此它不会改变整体复杂性,但是您可以进一步优化代码,以便最终列表实际处于正确的顺序(主页工作!)
答案 3 :(得分:0)
您最好的选择可能是坚持使用for
循环。它们将是最快的,假设list2
按相反顺序排序(对于您的示例)。
您可以尝试使用RemoveAll
或许......
list1.RemoveAll(l1item => list2.Contains(list1.IndexOf(l1item)));
缺点是,虽然这看起来更清晰,更直接,但它的基础实际上相当复杂。
public int RemoveAll(Predicate<T> match)
{
if (match == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
int num = 0;
while (num < this._size && !match(this._items[num]))
{
num++;
}
if (num >= this._size)
{
return 0;
}
int i = num + 1;
while (i < this._size)
{
while (i < this._size && match(this._items[i]))
{
i++;
}
if (i < this._size)
{
this._items[num++] = this._items[i++];
}
}
Array.Clear(this._items, num, this._size - num);
int result = this._size - num;
this._size = num;
this._version++;
return result;
}
另一方面,查看您当前使用的RemoveAt
:
public void RemoveAt(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
this._size--;
if (index < this._size)
{
Array.Copy(this._items, index + 1, this._items, index, this._size - index);
}
this._items[this._size] = default(T);
this._version++;
}
每次删除某个项目时Array.Copy
执行RemoveAll
,而for (int i = 0; i < list1.Count; i++)
{
bool exists = false;
for (int j = 0; j < list2.Count; j++)
if (i == list2[j])
{
exists = true;
break;
}
if (!exists) newList.Add(list1[i]);
}
通过项目进行处理,并将已编入索引的项目移至&#34;已删除&#34;项目
您可以对这两者进行一些简单的基准测试,看看哪个更好。
使用以下内容可能会给您带来最佳效果:
<input id="search" type="submit" value="Search Google Images">
<input id="keyword" type="text" value="" placeholder="Type text here">
<script>
$(document).ready(function(){
$("#search").click(function(){
window.open("https://www.google.de/search?q="+document.getElementById('keyword').value+"&biw=1678&bih=790&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjNzbLf6OfQAhVD6RQKHZp5BRsQ_AUIBigB&dpr=1.09")
});
});
</script>
这不应该依赖于列表顺序。