C# How to remove specific nodes from list - c#

I have the following class
public class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Content { get; set; }
public bool IsLastItem { get; set; }
}
Let say I have the following model and I want to remove items which IsLastItem = false and don't have child. In this scenario item4 and item7 should remove from list.
I get the list of my model from database and I simulated it in the code block like this
var items = new List<Item>
{
new Item
{
Id = 1,
ParentId = 0,
Content = "item1",
IsLastItem = false
},
new Item
{
Id = 2,
ParentId = 1,
Content = "item2",
IsLastItem = false
},
new Item
{
Id = 3,
ParentId = 1,
Content = "item3",
IsLastItem = true
},
new Item
{
Id = 4,
ParentId = 1,
Content = "item4",
IsLastItem = false
},
new Item
{
Id = 5,
ParentId = 2,
Content = "item5",
IsLastItem = false
},
new Item
{
Id = 6,
ParentId = 5,
Content = "item6",
IsLastItem = false
},
new Item
{
Id = 7,
ParentId = 5,
Content = "item7",
IsLastItem = false
},
new Item
{
Id = 8,
ParentId = 6,
Content = "item8",
IsLastItem = true
},
new Item
{
Id = 9,
ParentId = 8,
Content = "item9",
IsLastItem = true
}
};

A flat list like this is not optimal for these kinds of operations - it might be nice if you could get the list back in some kind of tree structure (maybe return it from SQL using FOR XML or JSON if you're on 2016) to begin with, where you'd have an easier time traversing the tree.
Also note that, as is, your sample data isn't setting IsLastItem...
As is, you have to iterate at least twice, something like this:
items.RemoveAll(x => x.IsLastItem == false &&
items.Any(y => y.ParentId == x.Id) == false);
You're saying to remove all the items where IsLastItem is false and where there's not at least one item whose parent id is that item's id.

You forgot to set IsLastItem in your mocked up data, FYI. You should be able to accomplish this with RemoveAll.
public static void Main()
{
var items = init();
items.RemoveAll(x => !items.Any(y => y.ParentId == x.Id) == true && x.IsLastItem == false);
}
public static List<Item> init()
{
return new List<Item>
{
new Item
{
Id = 1,
ParentId = 0,
Content = "item1"
},
new Item
{
Id = 2,
ParentId = 1,
Content = "item2"
},
new Item
{
Id = 3,
ParentId = 1,
Content = "item3",
IsLastItem = true
},
new Item
{
Id = 4,
ParentId = 1,
Content = "item4"
},
new Item
{
Id = 5,
ParentId = 2,
Content = "item5"
},
new Item
{
Id = 6,
ParentId = 5,
Content = "item6"
},
new Item
{
Id = 7,
ParentId = 5,
Content = "item7"
},
new Item
{
Id = 8,
ParentId = 6,
Content = "item8"
},
new Item
{
Id = 9,
ParentId = 8,
Content = "item9",
IsLastItem = true
}
};
}

Find the ParentIds. Compare each item to the list of ParentId collection and check IsLastitem.
var parents = items.Select(x => x.ParentId);
items.RemoveAll(x => !parents.Contains(x.Id) && !x.IsLastItem);

I would suggest you use a tree-like structure, and make IsLastItem a property that is calculated:
public class Item
{
public int Id { get; set; }
public string Content { get; set; }
public List<Item> SubItems { get; set; }
public bool IsLastItem { get { return SubItems.Count == 0; } }
}
Since the items stored in the database will have a flat structure, you'll have to write a function to create the tree from the database (and write the tree out to the database if necessary), but once that's done, the tree will be easier to manipulate.
You'd then write a recursive function to remove all last nodes, something like this:
List<Item> RemoveNodes(List<Item> tree)
{
var ret = tree.Where(item => !item.IsLastItem);
foreach (Item item in ret)
{
item.SubItems = RemoveNodes(item.SubItems);
}
return ret;
}
That may not be exactly the best way to do it but you get the idea.

Related

Convert Flat Data from C# to JSON

I have a c# class that looks something like this:
public class Item
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
}
and i would like to convert it to json where json property name is item name and its value is item description. if some item has any children then i would like the json property name to stay as item name and the children to be added like item name:item description(the parent item description and type become empty string if it has any children, except when the type is array). The type has following values: array, string, int, object. if the item type is an array then each time a child is added to the type array item, the child is item description. so i would like those values to be added to the json array as well.
if the type is string or int the json property value should be int or string.
I am trying to write custom JsonSerializer but i am getting nowhere.
so if i have a list with items:
List<Item> MyItems = new List<Item>()
{
new Item { Id = 1, ParentId = null, Name = "Name1", Description = "", Type = "" },
new Item { Id = 2, ParentId = 1, Name = "Name2", Description = "Description1", Type = "String" },
new Item { Id = 3, ParentId = 1, Name = "Name3", Description = "", Type = "Array" },
new Item { Id = 4, ParentId = 3, Name = "", Description = "ArrayItem1", Type = "" },
new Item { Id = 5, ParentId = 3, Name = "", Description = "ArrayItem2", Type = "" },
new Item { Id = 6, ParentId = 3, Name = "", Description = "ArrayItem3", Type = "" },
new Item { Id = 7, ParentId = null, Name = "Name4", Description = "5", Type = "Int" },
};
then the json should like this:
{
"name1":{
"name2":"description1",
"name3":[
"ArrayItem1",
"ArrayItem2",
"ArrayItem1"
]
},
"name4":5
}
Here's an extension method that I think does what you want:
public static class Ex
{
public static string ToJson(this List<Item> items)
{
var lookup = items.ToLookup(x => x.ParentId);
JObject ToJson(int? parentId)
{
JProperty ToProperty(Item item)
{
switch (item.Type)
{
case "":
return new JProperty(item.Name, ToJson(item.Id));
case "String":
return new JProperty(item.Name, item.Description);
case "Array":
return new JProperty(item.Name, lookup[item.Id].Select(x => x.Description).ToArray());
case "Int":
return new JProperty(item.Name, int.Parse(item.Description));
default:
return new JProperty(item.Name);
}
}
return new JObject(lookup[parentId].Select(x => ToProperty(x)));
}
var output = ToJson(null);
var text = Newtonsoft.Json.JsonConvert.SerializeObject(output, Newtonsoft.Json.Formatting.Indented);
return text;
}
}
I run it like this:
List<Item> MyItems = new List<Item>()
{
new Item { Id = 1, ParentId = null, Name = "Name1", Description = "", Type = "" },
new Item { Id = 2, ParentId = 1, Name = "Name2", Description = "Description1", Type = "String" },
new Item { Id = 3, ParentId = 1, Name = "Name3", Description = "", Type = "Array" },
new Item { Id = 4, ParentId = 3, Name = "", Description = "ArrayItem1", Type = "" },
new Item { Id = 5, ParentId = 3, Name = "", Description = "ArrayItem2", Type = "" },
new Item { Id = 6, ParentId = 3, Name = "", Description = "ArrayItem3", Type = "" },
new Item { Id = 7, ParentId = null, Name = "Name4", Description = "5", Type = "Int" },
};
Console.WriteLine(MyItems.ToJson());
And I get this out:
{
"Name1": {
"Name2": "Description1",
"Name3": [
"ArrayItem1",
"ArrayItem2",
"ArrayItem3"
]
},
"Name4": 5
}

Reduce Time Complexity of Code with nested foreach loops

I am new to C# development, I have a "Comments" Class as below.
class Comments
{
public Id {get; set;}
public Text {get;set;}
public ParentId {get;set;}
public List<Comments> childComments {get;set;}
}
We have child comments field within main comments. I am saving each comment object as a Document in NoSQL DB. I need to fetch these all comments and convert them into single comment object by placing all of its child comments inside 'childComments' field. ParentId will be null if comment is at level 0(top most level or first level comment).
I wrote the below code to retrieve it.
List<Comments> parentcomments = <from DB>.Where(t => t.ParentId == ObjectId.Empty).ToList();
List<Comments> childcomments = <from DB>.Where(t => t.ParentId != ObjectId.Empty).ToList();
foreach(comment t in parentcomments)
{
finalCommentTree = AggregateComment(childcomments, t.Id);
}
public List<Comments> AggregateComment(List<Comments> childcomments, ObjectId parentId)
{
List<Comments> recursiveObjects = new List<Comments>();
foreach (Comments item in childcomments.Where(x => x.ParentId.Equals(t.ParentId)))
{
recursiveObjects.Add(new Comments
{
Id = item.Id,
Text = item.Text,
childComments = AggregateComment(childcomments, item.Id)
});
}
return recursiveObjects;
}
Code works good without any issues, but problem is with time complexity. Is there a way to reduce time complexity and improve performance?
Another approach:
List<Comments> parentList = new List<Comments>()
{ new Comments() { Id = 1, Text = "Parent1", ParentId = -1 },
new Comments() { Id = 2, Text = "Parent2", ParentId = -1 },
new Comments() { Id = 3, Text = "Parent3", ParentId = -1 },
};
List<Comments> childList = new List<Comments>()
{
new Comments() { Id = 91, Text = "child1", ParentId = 3 },
new Comments() { Id = 92, Text = "child2", ParentId = 2 },
new Comments() { Id = 93, Text = "child3", ParentId = 1 },
new Comments() { Id = 94, Text = "child4", ParentId = 2 },
new Comments() { Id = 95, Text = "child5", ParentId = 2 },
new Comments() { Id = 96, Text = "child6", ParentId = 1 },
new Comments() { Id = 97, Text = "child7", ParentId = 2 }
};
List<Comments> k = ( from c in childList
join p in parentList
on c.ParentId equals p.Id
group c by new
{
c.ParentId
,p.Text
} into stdGrp
select new Comments
{
Id = stdGrp.Key.ParentId,
Text = stdGrp.Key.Text,
ParentId = -1,
childComments = stdGrp.OrderBy(j => j.Id).ToList(),
}
).ToList();

LINQ C# Except items from one list

I have Posts table:
Id ReplyId
1 null
2 1
3 null
4 3
5 null
posts contains all of these items.
I want to except posts where Id = ReplyId, so in result I want to get posts with Ids 2,4,5.
In other words, we can see ReplyId = 1 then we need to remove from list Post with Id = 1. Also ReplyId = 3 then remove from list Post with Id = 3.
How can I implement that?
posts.Where(x => !posts.Any(y => y.ReplyId == x.Id))
test:
void Main()
{
var posts = new[] {
new Post { Id = 1, ReplyId = null},
new Post { Id = 2, ReplyId = 1},
new Post { Id =3, ReplyId = null},
new Post { Id = 4, ReplyId = 3},
new Post { Id = 5, ReplyId = null},
};
var endItems = posts.Where(x => !posts.Any(y => y.ReplyId == x.Id));
foreach (var element in endItems)
Console.WriteLine(element.Id);
}
class Post
{
public int Id { get; set; }
public int? ReplyId { get; set; }
}
Try this:
var posts = new[]
{
new { Id = 1, ReplyId = (int?)null, },
new { Id = 2, ReplyId = (int?)1, },
new { Id = 3, ReplyId = (int?)null, },
new { Id = 4, ReplyId = (int?)3, },
new { Id = 5, ReplyId = (int?)null, },
};
var query =
from p in posts
join p2 in posts on p.Id equals p2.ReplyId into g
where !g.Any()
select p;
I get:

List union that depends / selects on property values

I have a class "item":
public class Item
{
public int Id { get; set; }
public decimal Price { get; set; }
public override bool Equals(object obj)
{
if (obj is Item item)
return item.Id == Id;
return false;
}
// GetHashCode omitted...
}
And I have 2 lists that I need to union:
var items1 = new List<Item>
{
new Item { Id = 1, Price = 10 },
new Item { Id = 2, Price = 10 },
new Item { Id = 3, Price = 10 },
};
var items2 = new List<Item>
{
new Item { Id = 1, Price = 10 },
new Item { Id = 2, Price = 8 },
new Item { Id = 4, Price = 10 },
};
The union I get like this:
var union = items1.Union(items2).ToList();
But I need also the constraint that the items with the lowest price is in the union. So for example in the above lists Item.ID = 2 from "items2" must be in the union...so the result should be a list consisting of these 4 items:
Item { Id = 1, Price = 10 }
Item { Id = 2, Price = 8 } // Not the one with Price = 10
Item { Id = 3, Price = 10 }
Item { Id = 4, Price = 10 }
Is there an elegant way of doing this in C# (preferably using Linq)?
You can try using groupby, like below :
var result = items1.Union(items2).GroupBy(x => x.Id)
.Select(x => new Item
{
Id = x.Key,
Price = x.Min(i => i.Price)
});

C# How to find specific item and it's related items from list

I have the following class
public class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Content { get; set; }
public bool IsLastItem { get; set; }
}
Let say I have the following model and I want to find specific item by Id and it's related item Ids. In this scenario I want to find Id of item9 and it's related item Ids.
Result should contains 9, 10, 11, 12, 13, 14
I get the list of my model from database and I simulated it in the code block like this
var items = new List<Item>
{
new Item
{
Id = 1,
ParentId = 0,
Content = "item1",
IsLastItem = false
},
new Item
{
Id = 2,
ParentId = 1,
Content = "item2",
IsLastItem = false
},
new Item
{
Id = 3,
ParentId = 1,
Content = "item3",
IsLastItem = true
},
new Item
{
Id = 4,
ParentId = 1,
Content = "item4",
IsLastItem = true
},
new Item
{
Id = 5,
ParentId = 2,
Content = "item5",
IsLastItem = false
},
new Item
{
Id = 6,
ParentId = 5,
Content = "item6",
IsLastItem = false
},
new Item
{
Id = 7,
ParentId = 5,
Content = "item7",
IsLastItem = false
},
new Item
{
Id = 8,
ParentId = 6,
Content = "item8",
IsLastItem = true
},
new Item
{
Id = 9,
ParentId = 7,
Content = "item9",
IsLastItem = false
},
new Item
{
Id = 10,
ParentId = 9,
Content = "item10",
IsLastItem = true
},
new Item
{
Id = 11,
ParentId = 9,
Content = "item11",
IsLastItem = false
},
new Item
{
Id = 12,
ParentId = 11,
Content = "item12",
IsLastItem = true
},
new Item
{
Id = 13,
ParentId = 11,
Content = "item13",
IsLastItem = true
},
new Item
{
Id = 14,
ParentId = 11,
Content = "item14",
IsLastItem = true
}
};
You need a recursive method
// Start finding the base item to start the recursive search
Item x = items.Where(i => i.Id == 9).FirstOrDefault();
List<Item> found = Search(items, x);
// Insert the starting item at the first position (Add is also good but...)
found.Insert(0, x);
public List<Item> Search(List<Item> list, Item parent)
{
// Prepare the list to return...
List<Item> found = new List<Item>();
if (parent != null)
{
// Search all the items that have the parent passed
List<Item> temp = list.Where(x => x.ParentId == parent.Id).ToList();
// Add the list to the return variable
found.AddRange(temp);
// For each child of this parent look for their childs
foreach(Item x in temp)
found.AddRange(Search(list, x));
}
return found;
}
You can use linq, something like below should get you started...
var parent = items.First(x => x.Content == "item9");
var children = items.Where(x => x.Id == parent.Id);

Categories