I have a list which contains order property of string.
This is what the data looks like:
1
2
2.1
2.1.1
2.1.2
2.2
2.2.1
2.2.2
3
3.1
3.2
3.3
4
And the structure of class is like this
public class CMNavBarDefaultDto
{
public int ID { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public string? Comments { get; set; }
public string Order { get; set; }
public bool ToShowGray { get; set; }
public bool IsNew { get; set; }
}
if the user delete any order from the list, like user delete 3.1 then 3.2 and 3.3 rearrange
3.2 become 3.1, 3.3 become 3.1,
if the user delete any parent like 1 then all the hierarchy should be maintained in a new form
like 2 become 1 and its child and sub-child should start from 1.
Can anyone suggest to me what approach is helpful in this scenario?
Seems to me, best method is to use list of uints to specify level as needed. You could then parallel the html table with Order object to update table as needed by having delete operation actually modify Order, which then updates html table.
The question is what to do if user deletes middle level? For example, if user deletes 1.1 in the following order, what happens to 1.1.1 and 1.1.2?
1
1.1
1.1.1
1.1.2
1.2
Does it become like below?
1
1.1
1.2
1.3 (was 1.2)
Knowing these rules, you can create a conversion function to create parallel Order and then operate on it as needed. The below code assumes all prior levels are defined as you go deeper (i.e., if you have a level 2, it is assumed the prior level was 2 or 1). At any point you can jump up levels (i.e., you could be at level 4 and then have the next level 1). If you don't follow these rules, the ToString function would throw Exception.
public class Order
{
var list = new List<unit>();
// needed: static function or constructor that takes html table string and returns Order and methods to modify Order
public override string ToString()
{
var sb = new StringBuilder();
var stack = Stack<(uint, string))>();
uint current = 0;
string order = "";
foreach (var next in list)
{
if (next > stack.Count)
{
// next is in deeper level, make sure prior level(s) exist.
if ((current == 0) || (next != (stack.Count + 1))) throw new Exception("missing level");
// prior level(s) exist, push current level in stack in case lower level needed later, then restart count within next level
stack.Push((current, order));
order = $"{order}{current}.";
current = 0;
}
else if (next < stack.Count)
{
// at lower level! pop out levels from stack until we get back to next level
while (stack.Count > next)
{
(current, order) = stack.Pop();
}
}
// append next level to output
current++;
sb.AppendLine($"{order}{current}");
}
return sb.ToString();
}
}
The following will create the tree. You have to add code to do add and remove items in tree. When you add or remove you have to renumber the items
using System;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
List<CMNavBarDefaultDto> list = new List<CMNavBarDefaultDto>()
{
new CMNavBarDefaultDto() { Order = "1"},
new CMNavBarDefaultDto() { Order = "2"},
new CMNavBarDefaultDto() { Order = "2.1"},
new CMNavBarDefaultDto() { Order = "2.1.1"},
new CMNavBarDefaultDto() { Order = "2.1.2"},
new CMNavBarDefaultDto() { Order = "2.2"},
new CMNavBarDefaultDto() { Order = "2.2.1"},
new CMNavBarDefaultDto() { Order = "2.2.2"},
new CMNavBarDefaultDto() { Order = "3"},
new CMNavBarDefaultDto() { Order = "3.1"},
new CMNavBarDefaultDto() { Order = "3.2"},
new CMNavBarDefaultDto() { Order = "3.3"},
new CMNavBarDefaultDto() { Order = "4"},
};
Tree root = new Tree();
root.MakeTree(list, root);
}
}
public class CMNavBarDefaultDto
{
public int ID { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public string? Comments { get; set; }
public string Order { get; set; }
public bool ToShowGray { get; set; }
public bool IsNew { get; set; }
}
public class Tree
{
public CMNavBarDefaultDto cmNav { get; set; }
public int[] orderInt { get; set; }
public List<Tree> children { get; set; }
public void MakeTree(List<CMNavBarDefaultDto> cmNavs, Tree parent)
{
List<Tree> list = new List<Tree>();
foreach(CMNavBarDefaultDto cmNav in cmNavs)
{
Tree node = new Tree() { cmNav = cmNav, orderInt = cmNav.Order.Split(new char[] { '.' }).Select(x => int.Parse(x)).ToArray() };
list.Add(node);
}
int level = 0;
list = list.OrderBy(x => x.orderInt.Length).ToList();
MakeTreeRecursive(list, parent, level);
}
public void MakeTreeRecursive(List<Tree> list, Tree parent, int level)
{
var groups = list.GroupBy(x => x.orderInt[level]).ToList();
foreach(var group in groups)
{
if (parent.children == null) parent.children = new List<Tree>();
parent.children.Add(group.First());
if (group.Count() > 1)
{
if (group.Last().orderInt.Length == level + 1)
{
group.First().children = new List<Tree>();
group.First().children = group.Skip(1).ToList();
}
else
{
MakeTreeRecursive(group.Skip(1).ToList(), group.First(), level += 1);
}
}
}
}
}
}
Related
I have a List of IJapaneseDictionaryEntry objects which are described below. Inside this are IKanji objects that contain Priorites objects.
I have a rather difficult thing I would like to do and would appreciate any advice / suggestions. What I would like to do is to retrieve entries that have an entry that have Priority of "Frequency1" have Priority of "Frequency2" or Priority of "Frequency3" from the list entries that I created.
public interface IJapaneseDictionaryEntry
{
int Sequence { get; }
IEnumerable<IKanji> Kanjis { get; }
IEnumerable<IReading> Readings { get; }
IEnumerable<ISense> Senses { get; }
}
Where each object contains a list of IKanji objects
public interface IKanji
{
string Text { get; }
IEnumerable<KanjiInformation> Informations { get; }
IEnumerable<Priority> Priorities { get; }
}
Here's the list:
List<IJapaneseDictionaryEntry> entries = dictionary.GetEntries().ToList();
Here's a view that I think might help explain the contents:
I hope the information here is enough as it seems difficult to explain what I need to retrieve.
var result = entries.Where(e => e.Kanjis.Any(k => k.Priorities.Contains(Priority.Frequency1) ||
k.Priorities.Contains(Priority.Frequency2) ||
k.Priorities.Contains(Priority.Frequency3)
)).ToList();
Considering your 2 questions, I would have made something like this:
[Flags]
public enum Priority
{
Frequency1 = 1,
Frequency2 = 2,
Frequency3 = 4,
Frequency4 = 8
}
public interface IKanji
{
string Text { get; }
IEnumerable<KanjiInformation> Informations { get; }
Priority Priorities { get; }
}
In above consider each Priority as a bit in an int, you can add priority by using bitwise or (|) :
Priorities = Priority.Frequency1 | Priority.Frequency2 // means have both priorities
To check if it has specific priority use bitwise and (&):
if((Priorities & Priority.Frequency1) == Priority.Frequency1
{
// it contains Priority.Frequency1
}
Then the answer you were looking for will be like:
Priority p = Priority.Frequency1 | Priority.Frequency2 | Priority.Frequency3
var result = entries.Where(e => e.Kanjis.Any(k => k.Priorities & p == p)))
.ToList();
This could be one solution:
var filteredEntries = entries.Where( // Only entries
e => e.Kanjis.Any( // which have one or more kanjis with..
a => a.Priorities.Any( // which have one or more priorities
p => p.Value == "Frequency1" // which have a value of "Frequency1"
)));
I changed your interfaces to classes to make it run with some example-data:
public class IJapaneseDictionaryEntry
{
public int Sequence { get; set; }
public IEnumerable<IKanji> Kanjis { get; set; }
}
public class IKanji
{
public string Text { get; set; }
public IEnumerable<Priority> Priorities { get; set; }
}
public class Priority
{
public string Value { get; set; }
}
public static void Main(string[] args)
{
// Initialize 3 objects. One has Priority we're searching
List<IJapaneseDictionaryEntry> entries = new List<IJapaneseDictionaryEntry>()
{
new IJapaneseDictionaryEntry(){ Sequence = 1, Kanjis = new List<IKanji>() { new IKanji() { Priorities = new List<Priority>() { new Priority() { Value = "Frequency1" } } } } },
new IJapaneseDictionaryEntry(){ Sequence = 2, Kanjis = new List<IKanji>() { new IKanji() { Priorities = new List<Priority>() { new Priority() { Value = "Frequency2" } } } } },
new IJapaneseDictionaryEntry(){ Sequence = 3, Kanjis = new List<IKanji>() { new IKanji() { Priorities = new List<Priority>() { new Priority() { } } } } },
};
// Here's the magic:
var filteredEntries = entries.Where( // Only entries
e => e.Kanjis.Any( // which have one or more kanjis with..
a => a.Priorities.Any( // which have one or more priorities
p => p.Value == "Frequency1" // which have a value of "Frequency1"
)));
// Let's check the output
foreach (var e in filteredEntries)
{
Console.WriteLine(e.Sequence);
}
}
I have a question. It's about linq in combination with c#.
I want to create a tree structure from a flatten structure in a pre defined object structure.
The following code which I've got work, but both are not exactly what i want.
In linq:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new { AgnNummer = group.Key, Items = group.ToList()}).ToList();
the issue is that this does not result in the object I want.
So I've rewritten this to the following code
List<string> test = listAgenderingen.Select(x => x.Agnnummer).Distinct().ToList();
foreach (var item in test)
{
List<Agendering> listAgendering = listAgenderingen.Where(agend => agend.Agnnummer == item).OrderBy(ord => ord.Agnnummer).ToList();
AgnAgendering AgnAgendering = new AgnAgendering() {AgnNummer =item, Agenderingen = listAgendering };
}
this code actually works correct. but for 200000 records, it's taking a lot of time while the original linq takes a few seconds.
my question is can the linq be rewritten so it will create or convert to the richt object?
the structure of the classes:
public class Agendering
{
public int AgnID { get; set; }
public string Agnnummer { get; set; }
}
public class AgnAgendering
{
public string AgnNummer { get; set; }
public List<Agendering> Agenderingen { get; set; }
}
I hope someone has a sollution.
If I understand correctly, you want:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new AgnAgendering { AgnNummer = group.Key, Agenderingen = group.ToList()}).ToList();
Your properties naming makes it absolutely unreadable and unclear.
Assuming that you have a flat structure like:
public class Item
{
public int ID { get; set; }
public int? ParentID { get; set; }
}
and you want a tree-like structure:
public class TreeItem
{
public int ID { get; set; }
public TreeItem Parent { get; set; }
public List<TreeItem> Children { get; set; }
public TreeItem(int id)
{
ID = id;
Children = new List<TreeItem>();
}
public TreeItem(int id, TreeItem parent) : this(id)
{
Parent = parent;
}
}
You can do most optimally in O(n) using Dictionary:
Item[] items = ...;
Dictionary<int, TreeItem> result = new Dictionary<int, TreeItem>();
foreach (var item in items.OrderBy(x => x.ParentID ?? -1))
{
TreeItem current;
if (item.ParentID.HasValue)
{
TreeItem parent = result[item.ParentID]; // guaranteed to exist due to order
current = new TreeItem(item.ID, parent);
parent.Children.Add(current);
} else {
current = new TreeItem(item.ID);
}
}
TreeItem[] treeItems = result.Values.ToArray();
I have following table:
---------------------
Id Title Parent
---------------------
1 Parent NULL
2 Level_1 1
3 Level_2 1
4 Level_3 1
5 Level NULL
6 Level_New 5
Now I want to display these data in my console application, I know I need a recursive function but no idea how to do it becuase I want to read these data using ADO.NET not EntityFramework.In EF I could define a model that has a navigation property for children:
public class Menu
{
public int Id { get; set; }
public string Title { get; set; }
public int? Parent { get; set; }
public ICollection<Menu> Children { get; set; }
}
But the problem is that I don't want to use EF. I want to do it using raw ADO.NET
Recursion isn't fun, this is a solution that I used to test for a much larger recursion
public class MyObject
{
public string Id;
public string ParentId;
public string Name;
public string Comments;
}
a lot of this code you wont need, but this should give you want you need on recursion.
private void BindTree(IEnumerable<MyObject> list, TreeNode parentNode, string previousNode)
{
var myObjects = list as IList<MyObject> ?? list.ToList();
var nodes = myObjects.Where(x => (parentNode == null ? x.ParentId == "[].[].[(root)]" : x.ParentId == parentNode.Value));
var listOfNodeNames = new List<string>();
foreach (var node in nodes)
{
var newNode = new TreeNode(node.Name, node.Id);
BindTree(myObjects, newNode, previousNode);
}
}
The above code does the recursion I need ( code you wont need stripped out ) and builds a treeview on a page based on data from a datatable.
But, this should give you want you need to do your recursion.
You need to pull data from server first, then construct tree on client side. Beware of circular reference.
First, change your Menu class to ensure that Children will never null
public class Menu
{
public Menu()
{
Children = new HashSet<Menu>();
}
public int Id { get; set; }
public string Title { get; set; }
public int? Parent { get; set; }
public ICollection<Menu> Children { get; private set; }
}
Then pull the data from database, and construct the tree
var connBuilder = new SqlConnectionStringBuilder();
connBuilder.DataSource = "localhost";
connBuilder.InitialCatalog = "YourDatabaseName";
connBuilder.IntegratedSecurity = true;
using (var con = new SqlConnection(connBuilder.ToString()))
{
con.Open();
var list = new List<Menu>();
//pull data from database
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT Id, Title, Parent FROM [dbo].[YourTableName]";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
list.Add(new Menu
{
Id = reader.GetInt32(0),
Title = reader.GetString(1),
Parent = reader.IsDBNull(2) ?(int?) null : reader.GetInt32(2)
});
}
}
}
//construct tree
var newList = new List<Menu>();
foreach (var l1 in list)
{
if (l1.Parent == null)
{
newList.Add(l1);
}
foreach (var l2 in list)
{
if (l2.Parent == l1.Id)
{
l1.Children.Add(l2);
}
}
}
// do whatever you want with newList
}
You will get data like this
I am trying to understand how to implement a treeview control - it all looks hideously complicated. However, the Treeview control would be more appropriate.
I have an SQL table containing fields ID and ParentLevelID.
I have added a basic Treeview control to my code:
<asp:TreeView ID="tvLevels" runat="server">
</asp:TreeView>
I want to populate this table using LinqToSQL. Presently, I am displaying the same data as a Gridview:
protected void SetupLevelsPanel()
{
// display levels according to current parentId
_svsCentralDataContext = new SVSCentralDataContext();
object levels;
if (_intParentLevelId == 0)
{
levels = (from sl in _svsCentralDataContext.SVSSurvey_Levels
where sl.ParentLevelID == null && sl.SurveyID == _intSurveyId
select new
{
sl.ID,
sl.SurveyID,
sl.UserCode,
sl.ExternalRef,
sl.Description,
sl.ParentLevelID,
sl.LevelSequence,
sl.Active
});
backUpButton.Visible = false;
}
else
{
levels = (from sl in _svsCentralDataContext.SVSSurvey_Levels
where sl.ParentLevelID == _intParentLevelId && sl.SurveyID == _intSurveyId
select new
{
sl.ID,
sl.SurveyID,
sl.UserCode,
sl.ExternalRef,
sl.Description,
sl.ParentLevelID,
sl.LevelSequence,
sl.Active
});
}
grdLevels.DataSource = levels;
grdLevels.DataBind();
GrdLevelButtons();
}
How can I convert this information to use my Treeview control?
This is my solution.
On my code behind page:
private void BuildTree()
{
tvLevels .Nodes.Clear();
_svsCentralDataContext = new SVSCentralDataContext();
List<DataAccessLayer.Level> items = DataAccessLayer.Levels.GetLevels(_intSurveyId).ToList();
List<DataAccessLayer.Level> rootItems = items.FindAll(p => p.ParentLevelId == null);
foreach (DataAccessLayer.Level item in rootItems)
{
var tvi = new TreeNode(item.Description, item.Id.ToString(CultureInfo.InvariantCulture) );
BuildChildNodes(tvi, items, item.Id);
tvLevels.Nodes.Add(tvi);
}
}
private void BuildChildNodes(TreeNode parentNode, List<DataAccessLayer.Level> items, int parentId)
{
List<DataAccessLayer.Level> children = items.FindAll(p => p.ParentLevelId == parentId).ToList();
foreach (DataAccessLayer.Level item in children)
{
var tvi = new TreeNode(item.Description, item.Id.ToString(CultureInfo.InvariantCulture));
parentNode.ChildNodes.Add(tvi);
BuildChildNodes(tvi, items, item.Id);
}
}
Class Levels.cs
using System;
using System.Collections.Generic;
using System.Linq;
using SVSVoidSurveyDesigner.Database;
namespace SVSVoidSurveyDesigner.DataAccessLayer
{
public class Levels
{
public static IEnumerable<Level> GetLevels(int intSurveyId)
{
var dataContext = new SVSCentralDataContext();
var levels = (from l in dataContext.SVSSurvey_Levels where l.SurveyID == intSurveyId
select new Level
{
Id = l.ID,
SurveyId = l.SurveyID,
UserCode = l.UserCode ,
ExternalRef = l.ExternalRef ,
Description = l.Description ,
ParentLevelId = (l.ParentLevelID),
LevelSequence = ( l.LevelSequence ),
Active = Convert .ToBoolean( l.Active )
});
return levels;
}
}
}
Class Level.cs
namespace SVSVoidSurveyDesigner.DataAccessLayer
{
public class Level
{
public int Id { get; set; }
public int SurveyId { get; set; }
public string UserCode { get; set; }
public string ExternalRef { get; set; }
public string Description { get; set; }
public int? ParentLevelId { get; set; }
public int? LevelSequence { get; set; }
public bool Active { get; set; }
}
}
I am iterating through a List of objects of Type "prvEmployeeIncident".
The object has the following properties:
public DateTime DateOfIncident { get; set; }
public bool IsCountedAsAPoint;
public decimal OriginalPointValue;
public bool IsFirstInCollection { get; set; }
public bool IsLastInCollection { get; set; }
public int PositionInCollection { get; set; }
public int DaysUntilNextPoint { get; set; }
public DateTime DateDroppedBySystem { get; set; }
public bool IsGoodBehaviorObject { get; set; }
My List is sorted by the DateOfIncident property. I would like to find the next object up the list where IsCounted == true and change it to IsCounted = false.
One question:
1) How do I find this object up the list ?
If I understand your question correctly, you can use LINQ FirstOrDefault:
var nextObject = list.FirstOrDefault(x => x.IsCountedAsAPoint);
if (nextObject != null)
nextObject.IsCountedAsAPoint = false;
If I understand correctly this can be solved with a simple foreach loop. I don't exactly understand your emphasis on "up" as you don't really move up a list, you traverse it. Anyways, the following code snippet finds the first Incident where IsCounted is true and changes it to false. If you're starting from a given position change the for each loop to a for loop and start at i = currentIndex with the exit condition being i < MyList.Count. Leave the break statement to ensure you only modify one Incident object.
foreach (prvEmployeeIncident inc in MyList)
{
if (inc.IsCountedAsAPoint)
{
inc.IsCountedAsAPoint = false;
break;
}
}
You can use List(T).FindIndex to search up the list.
Example:
public class Foo
{
public Foo() { }
public Foo(int item)
{
Item = item;
}
public int Item { get; set; }
}
var foos = new List<Foo>
{
new Foo(1),
new Foo(2),
new Foo(3),
new Foo(4),
new Foo(5),
new Foo(6)
};
foreach (var foo in foos)
{
if(foo.Item == 3)
{
var startIndex = foos.IndexOf(foo) + 1;
var matchedFooIndex = foos.FindIndex(startIndex, f => f.Item % 3 == 0);
if(matchedFooIndex >= startIndex) // Make sure we found a match
foos[matchedFooIndex].Item = 10;
}
}
Just be sure you do not modify the list itself since that will throw an exception.