【问题标题】:List, not lose the reference列出,不丢失参考
【发布时间】:2011-05-04 11:02:25
【问题描述】:

面试回来。我与您分享,欢迎提供良好而准确的答案。

目的,你有一个静态方法,这个方法接收一个IList<int>你有 取回可以除以 3 的值并编写代码。

约束: 原始列表(在主列表中)在堆栈上有引用,在堆上有值, 结果必须在与原始列表相同的空间(在堆上)中返回(这是一个 void 方法)。此处显示的解决方案不正确,因为在该方法中有一个新指针 在堆栈+堆上是在方法域中创建的。解决方案?

奖励:如何更改代码以不仅接收 int,而且接收 float、double、....

static void Main(string[] args)
    {
        IList<int> list = new List<int>() { 9, 3, 10, 6, 14, 16, 20};
        CanBeDivedByThree(list);
    }

    static void CanBeDivedByThree(IList<int> list)
    {
        list = (from p in list
                where p % 3 == 0
                orderby p descending
                select p).ToList<int>();
    }

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    这是没有意义的,因为IList 的内部存储不受您的控制。添加(或可能删除)项目可能会重新分配内部数据结构。

    这尤其没有意义,因为您的示例中的列表包含 value 类型,当您访问它们时无论如何都会复制这些类型。

    最后但并非最不重要的一点是,使用托管语言基本上是您不必担心内存 (al) 位置的全部意义所在。这样的东西是平台的implementation details

    回答您的额外问题:没有简单的方法可以实现这一目标。有人可能会认为使用具有类型约束的泛型可以解决这里的问题(类似于static void CanBeDivedByThree&lt;T&gt;(IList&lt;T&gt; list) where T : struct),但问题是 C#(还没有?)支持泛型算术。 C# 没有可以采用“T”和“int”类型的通用参数的模运算符。

    【讨论】:

    • 细节与否是面试问题的一部分,即使我同意你的观点。
    • @Kris-I:恕我直言,最好通过解释为什么它们没有意义来回答这些问题。
    • @0xA3:对于通用列表,过滤用户提供的通用谓词是合理的。
    • @0xA3:我的意思类似于@CodeInChaos 的方法:void RemoveAll&lt;T&gt;(IList&lt;T&gt; list, Predicate&lt;T&gt; match)
    • @Vlad:非常感谢,有道理!
    【解决方案2】:
    list.RemoveAll(n => n % 3 == 0);
    

    for (int i = list.Count - 1; i >= 0; --i)
    {
        if (list[i] % 3 != 0)
            list.RemoveAt(i);
    }
    

    第一种方法仅适用于List&lt;T&gt;

    可以将其设为模板方法,但余数运算对浮点数没有多大意义。

    【讨论】:

    • 你的起始索引差了一个
    【解决方案3】:

    不幸的是,只有 List 而不是 IList 实现了 RemoveAll。所以我先把它实现为扩展方法。

    public static int RemoveAll<T>(this IList<T> list, Predicate<T> match)
    {
      if (match == null)
        throw new ArgumentNullException("match");
    
      int destIndex=0;
      int srcIndex;
      for(srcIndex=0;srcIndex<list.Count;srcIndex++)
      {
        if(!match(list[srcIndex]))
        {
          //if(srcIndex!=destIndex)//Small optimization, can be left out
            list[destIndex]=list[srcIndex];
          destIndex++;
        }
      }
      for(int removeIndex=list.Count-1;removeIndex>=destIndex;removeIndex--)
      {
        list.RemoveAt(removeIndex);
      }
      return srcIndex-destIndex;
    }
    

    那么你可以使用:

    list.RemoveAll(n => n % 3 != 0);
    

    然后您可以将重载用于其他类型。不幸的是,您不能(轻松)使其成为泛型,因为泛型不适用于运算符重载。

    【讨论】:

    • 为了清楚起见,您可以使用RemoveAt(由IList&lt;&gt; 支持)。
    • 我猜错了吗? - 实际上,您应该只保留可被 3 整除的条目,而不是删除它们。
    • @Vlad:RemoveAt 不会导致 O(n^2) 算法吗?弗拉德解决方案中的@BrokenGlass 复制/粘贴错误
    • @CodeInChaos:会的。在这个任务中,我更喜欢清晰而不是效率(记住过早的优化并让优化器完成工作)。
    • @CodeInChaos:如果您想进一步优化,也许可以这样做:您可以将整个循环分成2个循环:循环#1跳过直到找到第一个不匹配的项目(无需比较src/dest 索引),循环 #2(从循环 #1 结束的地方开始)总是复制(因为 src/dest 索引保证不相等)。
    【解决方案4】:

    其他人已经涵盖了列表部分 - 这只是为了奖励。

    您不能使用 C# 泛型以静态类型的方式执行此操作,但如果您使用的是 C# 4,您可以使用动态类型执行此操作。例如:

    using System;
    using System.Collections.Generic;
    
    class Test
    {
        static void Main()
        {
            ShowDivisibleBy3(new List<int> { 1, 3, 6, 7, 9 });
            ShowDivisibleBy3(new List<decimal> { 1.5m, 3.3m, 6.0m, 7m, 9.00m });
        }
    
        static void ShowDivisibleBy3<T>(IEnumerable<T> source)
        {
            foreach (dynamic item in source)
            {
                if (item % 3 == 0)
                {
                    Console.WriteLine(item);
                }
            }
        }
    }
    

    【讨论】:

    • 主要问题是不会丢失原始堆部分的引用
    • @Kris-I:正如在其他地方所表达的,不能保证将使用 IList&lt;T&gt; 的什么实现。哎呀,它可能是一个懒惰生成的。如果它是一个明确的List&lt;T&gt;,那就不同了。
    • dynamic 的“hack”很好 :-) 我同意明确的 List&lt;T&gt; 会有所不同;但是我们只知道List&lt;T&gt; 的内部存储是一个数组。我们不知道如何管理此存储以及如何以及何时重新分配存储。即使我们知道(例如来自 Reflector)补丁或新的框架版本可能会改变它。
    • @0xA3:没错,所以我的评论。
    猜你喜欢
    • 2012-04-23
    • 2016-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-08
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    相关资源
    最近更新 更多