2

I've got newbie question, problem which I have to solve, to keep moving forward with my Unity project... It's probably pretty easy, but I'm not a programmer and a just don't know how to implement something I need. I checked documentation, but still don't know how to do it :/

I'm using graph class and I need to clone graph objects. Already know it's necessary to write deep clone function, coz I need NO references, just list of independent objects.

What I need is: create list of graphs. Change first one (by adding some edges), and clone it to the 2nd position on the list. Change 2nd (without changing 1st), and clone it to the 3rd position on the list, and so on...

This is my starting point:

public class Graph<T>
    {
        public int xMatrix;
        public int yMatrix;
        public int[] vertices;
        public Graph() { }

        public Graph(IEnumerable<T> vertices, IEnumerable<Tuple<T, T>> edges)
        {
            foreach (var vertex in vertices)
                AddVertex(vertex);

            foreach (var edge in edges)
                AddEdge(edge);
        }

        public Dictionary<T, HashSet<T>> AdjacencyList { get; } = new Dictionary<T, HashSet<T>>();

        public void AddVertex(T vertex)
        {
            AdjacencyList[vertex] = new HashSet<T>();
        }

        public void AddEdge(Tuple<T, T> edge)
        {
            if (AdjacencyList.ContainsKey(edge.Item1) && AdjacencyList.ContainsKey(edge.Item2))
            {
                AdjacencyList[edge.Item1].Add(edge.Item2);
                AdjacencyList[edge.Item2].Add(edge.Item1);
            }
        }
    } 

And what I tried:

public class Graph<T> :ICloneable
{
    public int xMatrix;
    public int yMatrix;
    public int[] vertices;
    public IEnumerable<int> verts;
    public IEnumerable<Tuple<int, int>> edgs;

    public Graph() { }

    public Graph(IEnumerable<T> vertices, IEnumerable<Tuple<T, T>> edges)
    {
        foreach (var vertex in vertices)
            AddVertex(vertex);

        foreach (var edge in edges)
            AddEdge(edge);
        verts = (IEnumerable<int>)vertices;
        edgs = (IEnumerable<Tuple<int, int>>)edges;
    }

    public Dictionary<T, HashSet<T>> AdjacencyList { get; set; } = new Dictionary<T, HashSet<T>>();

    public void AddVertex(T vertex)
    {
        AdjacencyList[vertex] = new HashSet<T>();
    }

    public void AddEdge(Tuple<T, T> edge)
    {
        if (AdjacencyList.ContainsKey(edge.Item1) && AdjacencyList.ContainsKey(edge.Item2))
        {
            AdjacencyList[edge.Item1].Add(edge.Item2);
            AdjacencyList[edge.Item2].Add(edge.Item1);
        }

                        //this.edgs.Append<edge>;
                        //IEnumerable<Tuple<int, int>> newEdgs = this.edgs.Append<Tuple<edge.Item1, edge.Item2>;
                        //IEnumerable<Tuple<int, int>> newEdgs = this.edgs.Append<edge>;
        //???
    }

    public object Clone()
    {
        IEnumerable<int> vertices = this.verts;
        IEnumerable<Tuple<int, int>> edges = this.edgs;
        Graph<int> other = new Graph<int>(vertices, edges);
        return other;
    }
}

Thank you in advance!

  • If your object is serialisable you can serialise/deserialise it to obtain a deep clone. https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-of-an-object-in-net – user5226582 Aug 04 '21 at 08:13

2 Answers2

2

For the most part your implementation doesn't have any inherent flaws.

The issue that might arise from the IEnumerables that you are passing around.

When you store an enumerable object, such as an array of integers inside of a IEnumerable variable you aren't copying the values but rather just storing the address(reference) to the original object, unless that object itself is a value type.

If the original type is a reference type, whether mutable or not, such as an array, we can work around this by copying the content of the reference type, for arrays we could use .CopyTo(destinationArray).

Another thing that might mess with you is that Tuple<> unlike ValueTuple or (type,type) is an immutable reference type.

Most of this can be avoided though. If you feel comfortable modifying how you clone the object, you can instead copy AdjacencyList instead.

public class Graph<T>
{
    public int xMatrix;
    public int yMatrix;
    public int[] vertices;

    public Dictionary<T, HashSet<T>> AdjacencyList => _AdjacencyList;

    protected Dictionary<T, HashSet<T>> _AdjacencyList = new Dictionary<T, HashSet<T>>();

    public Graph() { }

    public Graph(IEnumerable<T> vertices, IEnumerable<Tuple<T, T>> edges) { }

    public void AddVertex(T vertex) { }

    public void AddEdge(Tuple<T, T> edge) { }

    public object Clone()
    {
        Graph<T> newGraph = new Graph<T>()
        {
            yMatrix = this.yMatrix,
            xMatrix = this.xMatrix,
            vertices = new int[vertices.Length]
        };

        vertices.CopyTo(newGraph.vertices, 0);

        newGraph._AdjacencyList = new Dictionary<T, HashSet<T>>(AdjacencyList.Select(x =>
        {
            T[] copied = new T[x.Value.Count];

            x.Value.CopyTo(copied);

            return new KeyValuePair<T, HashSet<T>>(x.Key, new HashSet<T>(copied));
        }));

        return newGraph;
    }
}
DekuDesu
  • 2,224
  • 1
  • 5
  • 19
  • "For the most part your implementation doesn't have any inherent flaws." - I disagree. `verts` and `edgs` are just the enumerables passed in during construction. They don't include _any_ further added vertices or edges. The clone should really be constructed from `AdjacencyList` since it has the actual state of the graph in it. (This all leaves aside the fact that all of the fields and properties are public so they could be absolutely anything) – moreON Aug 04 '21 at 07:06
  • I can completely agree that his code *may* have design or overall architectural problems, but I feel it is outside of the scope of SO to fix problems that OP is not asking for. He is having trouble cloning the object as it is currently designed, which overall for his current design pattern and construction was not to far off of what he needed to do to be successful in that goal. – DekuDesu Aug 04 '21 at 07:19
  • I agreed with moreON that creating new graphs from AdjacencyList could be a solution, coz it's properly updated part. Unfortunately it has only 'get' atribiute, I don't know how could I 'set' this in new Graph with old one's value... As you told DekuDesu, this.edgs.ToArray(); doesn't coppy Tuples correctly, and this.edgs.Select(x=>(Tuple)x.MemberWiseClone()); returns error CS1540: Cannot access protected member 'object.MemberWiseClone()' via a qualifier of type 'Tuple'; the qualifier must be of type 'Graph' (or derived from it)...but edges are Tuples, not Graphs, so...??? – Matt the Wizard Aug 04 '21 at 18:57
  • I provided a separate implementation that uses `AdjacencyList` instead. It should be noted this implementation is making the assumption T is a value type. I'm happy to hear that you're open a more extensible approach, I think you'll find @moreON's approach more elegant and will pay off in efficiency! – DekuDesu Aug 04 '21 at 20:12
  • Another option would be using the Constructor to create the cloned Graph. e.g. `return new Graph(vertices, _AdjacencyList.SelectMany(kvp => kvp.Value.Select(dest => Tuple.Create(kvp.Key, dest))));` - I haven't actually checked that this compiles, but it feels like approximately what I'm thinking. – moreON Aug 05 '21 at 04:50
  • @DekuDesu thanks! it looks really good, makes sense to me, but now i'm getting: CS1061: 'HashSet' does not contain a definition for 'ToHashSet' and no accessible extension method 'ToHashSet' accepting a first argument of type 'HashSet' could be found (are you missing a using directive or an assembly reference?). From doc i read - it should contain that metod... – Matt the Wizard Aug 05 '21 at 18:26
  • Sorry about that you will need to add `using System.Linq;` to the top of the file to denote that some code uses methods from LINQ. – DekuDesu Aug 05 '21 at 18:37
  • It's already there, but still doesn't work ;c – Matt the Wizard Aug 05 '21 at 19:53
  • An easy work around would be to use `.ToArray().ToHashSet();` then – DekuDesu Aug 06 '21 at 06:08
  • @DekuDesu Still getting same error, like system.linq doesn't work... but it does, .Select works ok... CS1061: 'T[]' does not contain a definition for 'ToHashSet' and no accessible extension method 'ToHashSet' accepting a first argument of type 'T[]' could be found (are you missing a using directive or an assembly reference?). – Matt the Wizard Aug 06 '21 at 10:51
  • What version of Unity/Mono are you using? You find the documentation on [hashsets here](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.tohashset?view=net-5.0) – DekuDesu Aug 06 '21 at 10:53
  • @DekuDesu I know it should work, but i doesn't ;x Unity 2020.2.6f1 Personal, VS Enterprise 2019, .NET 5.0 – Matt the Wizard Aug 06 '21 at 12:00
  • `((IEnumerable)x.Value).ToHashSet();` – DekuDesu Aug 06 '21 at 13:04
  • @DekuDesu nope, still same CS1061 error :/ – Matt the Wizard Aug 06 '21 at 15:24
  • I've updated the post with an additional, additional, additional work around that avoids LINQ. – DekuDesu Aug 06 '21 at 16:04
  • @DekuDesu to avoid compiler error i had to add `newGraph._AdjacencyList = new Dictionary>((IDictionary>)AdjacencyList.Select(x =>` , but now _I'm getting `InvalidCastException: Specified cast is not valid.` error in unity console... (but types seems to be right?) – Matt the Wizard Aug 06 '21 at 19:47
  • That isn't a valid cast. You're attempting to cast an IEnumerable> to an IDictionary, although dictionary *does* implement that interface, you cannot create a new one by casting it. Could you post your entire source code for the file in a pastebin or .NET Fiddle. In the mean time I'll replicate your environment when I get a chance to see if that's the issue. – DekuDesu Aug 07 '21 at 03:57
  • I think your code works great, and my problem comes to question: why same code works great in VS project, but doesn't in Unity script? – Matt the Wizard Aug 07 '21 at 17:00
  • Unity uses a different compiler called Mono, this has small and large differences compared to Roslyn(Vs). However, this kind of issue normally doesn't happen. – DekuDesu Aug 08 '21 at 02:48
  • After short vacation from that problem, I asked Unity community about it and it appears that the cause of the problem is in .NET compatibility. Those code (from DekuDesu’s answer) works great in VS C# console applications with .NET Core 3.1 or .NET 5.0, but Unity engine doesn't support that, and to fix it I should change code to work in VS in C# ClassLibrary (.NET Framework 4.7.1 & Unity settings on .NET 4.x) or (.NET Standard & Unity settings on .NET Standard 2.0). So... I'm back here with question, anybody know differences between those .NET Frameworks, to easily fix this? – Matt the Wizard Aug 25 '21 at 22:59
0

So... with some help 'of the Internet' problem is finally solved:

Code from DekuDesu’s answer works great in VS C# console applications with .NET Core 3.1 or .NET 5.0, but Unity engine doesn't support that.

Fix for that is change code to work in VS in C# ClassLibrary (.NET Framework 4.7.1 & Unity settings on .NET 4.x) or (.NET Standard & Unity settings on .NET Standard 2.0).

The ability to construct a new Dictionary<> with an IEnumerable<KeyValuePair> isn't available in .NET Framework. Alternatively I used IEnumerable<>.ToDictionary() and it worked.

This works:

public class Graph<T>
{
    public int xMatrix;
    public int yMatrix;
    public int[] vertices;

    public Dictionary<T, HashSet<T>> AdjacencyList => _AdjacencyList;

    protected Dictionary<T, HashSet<T>> _AdjacencyList = new Dictionary<T, HashSet<T>>();

    public Graph() { }

    public Graph(IEnumerable<T> vertices, IEnumerable<Tuple<T, T>> edges)
    {
        foreach (var vertex in vertices)
            AddVertex(vertex);

        foreach (var edge in edges)
            AddEdge(edge);
    }

    public void AddVertex(T vertex)
    {
        AdjacencyList[vertex] = new HashSet<T>();
    }

    public void AddEdge(Tuple<T, T> edge)
    {
        if (AdjacencyList.ContainsKey(edge.Item1) && AdjacencyList.ContainsKey(edge.Item2))
        {
            AdjacencyList[edge.Item1].Add(edge.Item2);
            AdjacencyList[edge.Item2].Add(edge.Item1);
        }
    }

    public object Clone()
    {
        Graph<T> newGraph = new Graph<T>()
        {
            yMatrix = this.yMatrix,
            xMatrix = this.xMatrix,
            vertices = new int[vertices.Length]
        };

        vertices.CopyTo(newGraph.vertices, 0);

        newGraph._AdjacencyList = AdjacencyList.Select(x =>
        {
            T[] copied = new T[x.Value.Count];

            x.Value.CopyTo(copied);

            return new KeyValuePair<T, HashSet<T>>(x.Key, new HashSet<T>(copied));
        }).ToDictionary(o => o.Key, o => o.Value);

        return newGraph;
    }
}

Thanks for your help!

gknicker
  • 5,509
  • 2
  • 25
  • 41