【问题标题】:How can I get all nested items from a collection?如何从集合中获取所有嵌套项?
【发布时间】:2016-07-03 15:04:55
【问题描述】:

我有一些物品。一个项目可以有另一个项目,另一个项目可以有另一个项目。以此类推。

我不知道嵌套项可以有多少级项。嵌套项的级别可以在运行时定义。

class Person
{
    Person person;
    public Person(Person _nestedPerson)
    {
        person = _nestedPerson;
    }

    public bool IsSelectedPerson { get; set; }
    public string Name { get; set; }
}

以及如何嵌套项目(Person):

IList<Person> list = new List<Person>();            
for (int startIndex = 0; startIndex < 5; startIndex++)
{
   list.Add(new Person(new Person(new Person(new Person(null) { Name="Bill", 
        IsSelectedPerson=true})) { Name = "Jessy", IsSelectedPerson = false }) 
        { Name = "Bond", IsSelectedPerson =true});//3 nested persons
   list.Add(new Person(new Person(null) { Name = "Kendell", 
        IsSelectedPerson = true }) { Name="Rosy", IsSelectedPerson=true});//2 nested persons
   //The next time it can be just one person without nested item(person). I do not know how many items(persons) will be nested
   //list.Add(new Person(null) { Name="Rosy", IsSelectedPerson=true});
}

我的目标是获取IsSelectedPerson=true 的人(Person) 的所有 个对象(没有重复)?

我玩过Select()

var ee = list.Select(x=>x.IsSelectedFacet==true);//comparison should be done here

但这不是我想要的,它只需要bool 值。

更新:

我的预期结果应该是拥有一个具有唯一名称的 Person 对象。不管有多少同名的对象。我只想拿一个对象。抱歉误导。它应该是这样的:

【问题讨论】:

  • 您对嵌套深度有任何限制吗?
  • FacetStorage.Where(p =&gt; p.IsSelectedFacet);
  • @Valentin 不,嵌套深度没有限制
  • @Jodrell 是的,我试过coll.Where(p =&gt; p.IsSelectedPerson);,但这个查询只接受上层对象,而不是嵌套对象。
  • @StepUp,首先你需要“扁平化”嵌套的人。有几种方法可以做到这一点,我更喜欢一种不涉及分配任意长列表的 linq 方式。

标签: c# linq


【解决方案1】:

你可以创建一个辅助方法来解开所有嵌套对象

    IEnumerable<Person> UnwrapPerson(Person p)
    {
        List<Person> list = new List<Person>();
        list.Add(p);
        if (p.person != null)
            list.AddRange(UnwrapPerson(p.person));

        return list;
    }

或者如果 Person 类只有一个嵌套对象 (Person person;),您可以使用 yield 构造而不是递归

    static IEnumerable<Person> UnwrapPerson(Person p)
    {
        yield return p;
        while (p.person != null)
        {
            p = p.person;
            yield return p;
        }
    }

为了删除所有重复的人,例如同名,你应该实现IEqualityComparer&lt;Person&gt;,然后使用Distinct方法。

class Comparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return string.Equals(x.Name, y.Name);
    }

    public int GetHashCode(Person obj)
    {
        string name = obj.Name;
        int hash = 7;
        for (int i = 0; i < name.Length; i++)
        {
            hash = hash * 31 + name[i];
        }

        return hash;
    }
}

所以最终查询应该类似于:

 list.SelectMany(p => UnwrapPerson(p))
     .Where(x => x.IsSelectedPerson == true)
     .Distinct(new Comparer())

【讨论】:

  • @StepUp 我已经添加了重复删除。
  • 我试过你的解决方案,但是有重复。
  • @StepUp 重复是什么意思?同名?还是带有嵌套对象的对象?您正在运行最后一个查询吗?
  • 使用相同的Name。是的,我已经尝试过你的最后一个查询。
  • @StepUp 我在我的机器上运行此代码,它运行良好。你能分享一下你在做什么吗?
【解决方案2】:

我会使用某种递归访问模式来访问所有嵌套的Persons

class Person
{
   public static List<Person> selectedPersons;
   Person person;
   public Person(Person _nestedPerson)
   {
       if(selectedPersons == null)
         selectedPersons = new List<Person>();
       person = _nestedPerson;
   }

   public bool IsSelectedPerson { get; set; }
   public string Name { get; set; }

   public void Visit()
   {
       if(this.IsSelectedPerson)
         selectedPersons.Add(this);
       if(this.person != null)
         this.person.Visit();
   }
}

【讨论】:

  • 感谢您的回复,但是,我无法更改我的模型类。
  • @StepUp 也许扩展方法可以做到?并将列表作为参数传递给它
【解决方案3】:

这是生成项目列表的另一种方法:

IEnumerable<Person> GetIsSelectedPerson(Person p)
{
    Person temp = p;
    while (temp != null)
    {
        if (temp.IsSelectedPerson)
        {
            yield return temp;
        }
        temp = temp.person;
    }           
}

用法:

IEnumberable<Person> Result = GetIsSelectedPerson(rootPerson)

【讨论】:

  • 我想不通,如何遍历嵌套的人?你能提供完整的代码吗?
  • 好的,但是我怎样才能接受嵌套的人呢?对不起我的假问题:)。请提供完整代码
  • Person temp 正在遍历您的嵌套Persons 并返回项目以防IsSelectedPerson - 这是完整代码
  • @StepUp 在我看来,您应该对list 使用SelectMany 方法。 list.SelectMany(x=&gt; GetIsSelectedPerson(x))
  • 很多重复的对象。我只想要单个对象。可以不重复吗?提前致谢
【解决方案4】:

这样做是为了让人们变平,

Func<Person, IEnumerable<Person>> flattener = null;
flattener = p => new[] { p }
    .Concat(
        p.person == null 
            ? Enumerable.Empty<Person>()
            : (new [] { p.Person }).SelectMany(child => flattener(child)));

所以你可以这样做,

flattener(person).Where(p => p.IsSelectedPerson);

跟随你们,你们可能想要的是,

flattener(person)
   .Where(p => p.IsSelectedPerson)
   .Select(p => p.Name)
   .Distinct();

【讨论】:

  • 我正在尝试您的解决方案,但是 p 没有 Person 字段。我的意思是:.Concat(p.Personp 只有IsSelectedPersonName
  • @StepUp,试试小写p,嵌套在Person中的Person是否可以公开访问?
  • 谢谢,我已经编辑到public。但是,有很多重复。我怎样才能删除它?
  • Distinct 会有所帮助,但如果您希望不同的实例相同,则需要 EqualsGetHashCode
  • 抱歉误导,我的意思是我只想获得具有不同 Name 属性的对象。
【解决方案5】:

由于你不知道链中人的级别,最好是使用递归。两个简单的解决方案(假设您在 Person 类上添加方法)

  1. 创建一个接收列表的方法,这样就可以在递归调用中填写: 列表完成列表 = 新列表(); list[0].GetCompleteList(completeList); list[1].GetCompleteList(completeList);

    public void GetCompleteList(List<Person> personsList)
    {
        personsList.Add(this);
        if (person != null)
        {
            person.GetCompleteList(personsList);
        }
    }
    
  2. 相同,无参数

    List<Person> completeList = new List<Person>();
    completeList.AddRange(list[0].GetCompleteList());
    completeList.AddRange(list[1].GetCompleteList());
    
    // Another way: with linq
    var myPersons  list.SelectMany(m => m.GetCompleteList());
    
    public List<Person> GetCompleteList()
     {
         List<Person> returnList = new List<Person>();
         returnList.Add(this);
         if (person != null)
         {
             returnList.AddRange(person.GetCompleteList());
         }
         return returnList;
     }
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-20
    • 2016-05-16
    • 2014-11-02
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多