C# Synchronisation functions

Date: 2020-08-25
using System;
using System.Collections.Generic;
using System.Linq;

public interface IIdComparer<T>
{
    bool IdEquals(T a, T b);
}

public interface IContentComparer<T>
{
    bool ContentEquals(T a, T b);
}

public class Change<T>
{
    public Change(T left, T right)
    {
        Left = left;
        Right = right;
    }
    public T Left { get; }
    public T Right { get; }
}

public class SyncHelper
{
    public static IEnumerable<T> GetNew<T>(IEnumerable<T> left, IEnumerable<T> right, IIdComparer<T> comparer)
    {
        return right.Where(x => !left.Any(y => comparer.IdEquals(x, y)));
    }

    public static IEnumerable<Change<T>> GetChanges<T>(IEnumerable<T> left, IEnumerable<T> right, IIdComparer<T> comparer, IContentComparer<T> contentComparer)
    {
        return right.Aggregate(new List<Change<T>>(), (List<Change<T>> items, T x) =>
        {
            var leftItem = left.Where(y => comparer.IdEquals(x, y)).FirstOrDefault();
            if (leftItem != null && !contentComparer.ContentEquals(leftItem, x))
            {
                items.Add(new Change<T>(leftItem, x));
            }
            return items;
        });
    }

    public static IEnumerable<T> GetChanged<T>(IEnumerable<T> left, IEnumerable<T> right, IIdComparer<T> comparer, IContentComparer<T> contentComparer)
    {
        return right.Aggregate(new List<T>(), (List<T> items, T x) =>
        {
            var leftItem = left.Where(y => comparer.IdEquals(x, y)).FirstOrDefault();
            if (leftItem != null && !contentComparer.ContentEquals(leftItem, x))
            {
                items.Add(x);
            }
            return items;
        });
    }

    public static IEnumerable<T> GetUnchanged<T>(IEnumerable<T> left, IEnumerable<T> right, IIdComparer<T> comparer, IContentComparer<T> contentComparer)
    {
        return right.Aggregate(new List<T>(), (List<T> items, T x) =>
        {
            var leftItem = left.Where(y => comparer.IdEquals(x, y)).FirstOrDefault();
            if (leftItem != null && contentComparer.ContentEquals(leftItem, x))
            {
                items.Add(x);
            }
            return items;
        });
    }

    public static IEnumerable<T> GetDeleted<T>(IEnumerable<T> left, IEnumerable<T> right, IIdComparer<T> comparer)
    {
        return GetNew(right, left, comparer);
    }
}

public class Card
{
    public Card(string id, string content)
    {
        Id = id;
        Content = content;
    }

    public string Id { get; set; }
    public string Content { get; set; }
}

public class CardComparer : IIdComparer<Card>, IContentComparer<Card>
{
    public bool ContentEquals(Card a, Card b)
    {
        return a.Content == b.Content;
    }

    public bool IdEquals(Card a, Card b)
    {
        return a.Id == b.Id;
    }
}

class Program
{
    public static IEnumerable<TResult> FullOuterJoinIterator<TLeft, TRight, TKey, TResult>(
        IEnumerable<TLeft> left,
        IEnumerable<TRight> right,
        Func<TLeft, TKey> leftKeySelector,
        Func<TRight, TKey> rightKeySelector,
        Func<TLeft, TRight, TKey, TResult> resultSelector,
        IEqualityComparer<TKey> comparer,
        TLeft defaultLeft,
        TRight defaultRight)
    {
        var leftLookup = left.ToLookup(leftKeySelector, comparer);
        var rightLookup = right.ToLookup(rightKeySelector, comparer);
        var keys = leftLookup.Select(g => g.Key).Union(rightLookup.Select(g => g.Key), comparer);

        foreach (var key in keys)
            foreach (var leftValue in leftLookup[key].DefaultIfEmpty(defaultLeft))
                foreach (var rightValue in rightLookup[key].DefaultIfEmpty(defaultRight))
                    yield return resultSelector(leftValue, rightValue, key);
    }

    static void Main(string[] args)
    {
        var left = new List<Card>()
            {
                new Card("1", "A"),
                new Card("2", "B"),
                new Card("3", "C"),
                new Card("4", "D"),
                new Card("5", "E"),
            };

        var right = new List<Card>()
            {
                new Card("6", "A"),
                new Card("2", "B"),
                new Card("3", "E"),
                new Card("5", "F"),
                new Card("7", "A")
            };

        var compared = FullOuterJoinIterator(left, right, x => x.Id, x => x.Id, (left, right, key) => new { Left = left, Right = right }, StringComparer.OrdinalIgnoreCase, null, null);
        foreach (var item in compared)
        {
            var differs = item.Left != null && item.Right != null && item.Left?.Content != item.Right?.Content ? "(differs)" : "";
            Console.WriteLine($"{item.Left?.Id ?? "_":12} <=> {item.Right?.Id ?? "_":12} {differs}");
        }

        foreach (var item in SyncHelper.GetNew(left, right, new CardComparer()))
        {
            Console.WriteLine($"new: {item.Id}");
        }

        Console.WriteLine($"=================");

        foreach (var change in SyncHelper.GetChanges(left, right, new CardComparer(), new CardComparer()))
        {
            Console.WriteLine($"change: {change.Left.Id}, '{change.Left.Content}' => '{change.Right.Content}'");
        }

        Console.WriteLine($"=================");

        foreach (var item in SyncHelper.GetDeleted(left, right, new CardComparer()))
        {
            Console.WriteLine($"deleted: {item.Id}");
        }

        Console.WriteLine($"=================");

        foreach (var item in SyncHelper.GetChanged(left, right, new CardComparer(), new CardComparer()))
        {
            Console.WriteLine($"changed: {item.Id} '{item.Content}'");
        }

        Console.WriteLine($"=================");

        foreach (var item in SyncHelper.GetUnchanged(left, right, new CardComparer(), new CardComparer()))
        {
            Console.WriteLine($"unchanged: {item.Id} '{item.Content}'");
        }

        Console.WriteLine($"=================");
        Console.WriteLine("Finished.");
    }
}

// new: 6
// new: 7
// =================
// change: 3, 'C' => 'E'
// change: 5, 'E' => 'F'
// =================
// deleted: 1
// deleted: 4
// =================
// changed: 3 'E'
// changed: 5 'F'
// =================
// unchanged: 2 'B'
// =================
// Finished.
39520cookie-checkC# Synchronisation functions