【问题标题】:Looping list null elements循环列表空元素
【发布时间】:2017-01-18 15:37:59
【问题描述】:

我有一个包含类(人物)实例的列表。随着时间的推移,有些人会消失(在列表中为空),有些人会添加。然后我想遍历不为空的元素列表。

下面的代码完成了这项工作,但我觉得它写得很糟糕。

  1. 我有一个包含许多空元素的列表。这甚至是一个问题吗?使用他当前的代码,它是易于管理的。但是,如果我将createPerson(10); 更改为createPerson(300);,并将for (int i = 1; i <= 100; i++) 更改为for (int i = 1; i <= 1000; i++),我的列表将有约6300 个元素的计数,其中6000 个为空。

  2. 逐个元素地遍历列表并检查if (person[i] != null) 似乎很愚蠢。还有其他方法可以做到这一点吗?这里应该使用 LINQ 吗?

我在想也许最好删除 null 元素,并将带有数据的元素移动到带有 null 的元素。然后我需要使用person.Id(唯一的增量ID)而不是索引号来识别元素。类似于:

var item = person.First(i => i.Id == Id);

对于这个问题有什么推荐的方法吗?

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace listID
{
    class Program
    {
        static List<People> person = new List<People>();
        static Random rnd = new Random();

        static void Main(string[] args)
        {
            // Generates 10 people of random ages.
            createPerson(10);

            // Advances 100 years.
            for (int i = 1; i <= 100; i++)
            {
                circleOfLife();
            }

            writeList();
            Console.ReadKey();
        }

        /// <summary>
        /// Writes out the current elements in the list
        /// </summary>
        static void writeList()
        {
            for (int i = 0; i < person.Count; i++)
            {
                if (person[i] != null)
                    Console.WriteLine("List index: " + i + " -  " + person[i].age + " years old.");
            }
        }


        /// <summary>
        /// Creates people of random age between 0 and 100.
        /// </summary>
        /// <param name="q">Amount of people to create</param>
        static void createPerson(int q)
        {
            for (int i = 1; i <= q; i++)
            {
                People newPerson = new People();
                newPerson.age = rnd.Next(100);
                person.Add(newPerson);
            }
        }

        /// <summary>
        /// Increases age of person by a year. If person reaches age of 100, they get removed, and another one of random age is added.
        /// </summary>
        static void circleOfLife()
        {
            for (int i = 0; i < person.Count; i++)
            {
                if(person[i] != null)
                {
                    person[i].increaseAge();
                    if (person[i].age > 99)
                    {
                        person[i] = null;
                        createPerson(1);
                    }
                }
            }
        }

    }

    class People
    {
        public int age;

        private static int m_Counter = 0;
        public int Id { get; set; }
        public People()
        {
            this.Id = System.Threading.Interlocked.Increment(ref m_Counter); // Gives unique incrememntal ID number to each elelment.
        }

        public void increaseAge()
        {
            age++;
        }

    }
}

输出:

List index: 13 -  93 years old.
List index: 17 -  26 years old.
List index: 18 -  95 years old.
List index: 19 -  45 years old.
List index: 20 -  34 years old.
List index: 21 -  92 years old.
List index: 22 -  58 years old.
List index: 23 -  44 years old.
List index: 24 -  67 years old.

非常感谢您的帮助。我还在学习,所以示例代码会非常有用。

【问题讨论】:

  • 您以“我在想也许...”开头的段落是处理此问题的正确方法。
  • 是的,这对我来说很有意义。但是,我希望有一个代码示例显示如何在使用 circleOfLife(); 之前和之后调用相同的实例。就像如果一个实例person[x] 具有属性person[x].Id = y 被分配一个新索引(z),我需要找到包含属性person.Id = y 的元素。最好的方法是什么?

标签: c# linq list class


【解决方案1】:

我建议使用 RemoveAll 从列表中删除元素。

static void circleOfLife()
{
    for (int i = 0; i < person.Count; i++)
    {
        if(person[i] != null)
        {
            person[i].increaseAge();
        }
    }
    //Select all id of person having Age > 99
    var personIds = person.Where(p => p.Age > 99).Select(p => p.Id);
    person.RemoveAll(p => p.Age > 99); //or person.RemoveAll(p => personIds.Contains(p.Id));
    createPerson(personIds.Count());
}

它将选择所有年龄> 99的人,然后将其从列表中删除。

【讨论】:

  • No RemoveAll 是List 的一个方法,它接受一个谓词。与 Linq 无关
  • 清除不需要的元素。但是,如果我想在这种情况下引用特定实例,我将不得不使用 person.Id 来跟踪它们,对吗? (因为索引会发生变化)最好的方法是什么? Linq,还是使用 FindIndex?
【解决方案2】:

你也可以使用Lookup:

var lookup = person.Where(p => p != null && p.Age <= 99).ToLookup(p => p.Id);    
var item = lookup[42].FirstOrDefault(); // null if no person with Id 42

【讨论】:

    【解决方案3】:

    您为什么不替换 人而不是分配给null 并添加?

     static void circleOfLife() {
       for (int i = 0; i < persons.Count; ++i) {
         persons[i].increaseAge();
    
         // if a person is too old  
         if (persons[i].age > 99) {
           // ...generate a new person and put it on old person's place
           persons[i] = new People() {
             age = rnd.Next(100)
           }; 
         }
       } 
     }
    

    在这种情况下,您将摆脱讨厌的null 检查。

    【讨论】:

      【解决方案4】:

      您的列表是否应包含 null 值的人员对象 Age &gt; 99 取决于您想在域模型中表达什么。

      但我无法想象为什么要在列表中保留 null 值的充分理由。空值的存在传达了哪些附加信息。关于“这些人是谁”的信息丢失了,因为 id 与它的实例一起消失了。如果您想跟踪一直存在的人数,那么一个简单的计数器就足够了。所以这些null 值只会导致列表不断增加,并随着时间的推移降低性能。

      如果您想跟踪 ID 为“太旧”的人员实例,那么您可以使用两个包含“活着的人”的列表和一个存储历史实例的列表。

      一些有用的 LINQ 表达式作为示例:

      var livingPeople = people.Where(p => p != null && p.Age <= 99);
      people.RemoveAll(p => p == null); // removes all null values
      var numberOfPassedPeople = people.Count(p => p == null);
      

      【讨论】:

        【解决方案5】:

        您可以使用 LINQ Where() 扩展方法并只获取非空的,例如

        var item = person.Where(p => p != null);
        

        【讨论】:

          猜你喜欢
          • 2018-07-11
          • 2015-02-14
          • 1970-01-01
          • 2012-08-31
          • 2019-09-03
          • 2015-03-16
          • 2018-09-05
          • 2019-05-06
          • 2016-07-21
          相关资源
          最近更新 更多