How do you break down your code?

  • (3 Pages)
  • +
  • 1
  • 2
  • 3

39 Replies - 1643 Views - Last Post: 07 February 2019 - 02:34 PM

#31 baavgai   User is offline

  • Dreaming Coder
  • member icon


Reputation: 7495
  • View blog
  • Posts: 15,531
  • Joined: 16-October 07

Re: How do you break down your code?

Posted 06 February 2019 - 01:15 PM

This feels like a job for reflection...

class Person {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public char? MiddleInitial { get; set; }
}
private static (string propName, object valX, object valY, bool match) CheckMatch(string name, object x, object y) =>
    (name, x, y, ((x != null && y != null && x.Equals(y)) || (x == null && y == null)));
private static (string propName, object valX, object valY, bool match) MatchItem(System.Reflection.PropertyInfo p, object x, object y) =>
    CheckMatch(p.Name, p.GetValue(x), p.GetValue(y));
private static IEnumerable<(string propName, object valX, object valY, bool match)> MatchProps<T>(T objX, T objY) =>
    typeof(T).GetProperties().Select(prop => MatchItem(prop, objX, objY));

private static void Test() {
    var peeps = new Person[] {
        new Person() { FirstName= "John", LastName="Doe" },
        new Person() { FirstName= "Jane", LastName="Doe" },
        new Person() { FirstName= "Don", LastName="Ho", MiddleInitial = 'A' },
        new Person() { FirstName= "Jon", LastName="Ho", MiddleInitial = 'B' },
    };
    for (int i = 0; i < peeps.Length; i++) {
        for (int j = i + 1; j < peeps.Length; j++) {
            var matches = MatchProps(peeps[i], peeps[j]);
            Debug.Write(matches.Where(x => x.match).Count() + ": ");
            foreach (var x in MatchProps(peeps[i], peeps[j])) {
                Debug.Write(x);
            }
            Debug.WriteLine("");
        }
    }
}




Results:
2: (FirstName, John, Jane, False)(LastName, Doe, Doe, True)(MiddleInitial, , , True)
0: (FirstName, John, Don, False)(LastName, Doe, Ho, False)(MiddleInitial, , A, False)
0: (FirstName, John, Jon, False)(LastName, Doe, Ho, False)(MiddleInitial, , B, False)
0: (FirstName, Jane, Don, False)(LastName, Doe, Ho, False)(MiddleInitial, , A, False)
0: (FirstName, Jane, Jon, False)(LastName, Doe, Ho, False)(MiddleInitial, , B, False)
1: (FirstName, Don, Jon, False)(LastName, Ho, Ho, True)(MiddleInitial, A, B, False)



You needn't really be object type specific. You could match up available props based strictly on name and type and you'd be close enough. Or just have a list of given props to compare.
Was This Post Helpful? 0
  • +
  • -

#32 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7109
  • View blog
  • Posts: 24,146
  • Joined: 05-May 12

Re: How do you break down your code?

Posted 06 February 2019 - 03:30 PM

View Postbaavgai, on 06 February 2019 - 03:15 PM, said:

This feels like a job for reflection...

See his other thread Help Comparing Objects. In particular, I've put in my conclusions/sentiments in post #31 and #32.
Was This Post Helpful? 0
  • +
  • -

#33 Sheepings   User is offline

  • D.I.C Lover
  • member icon

Reputation: 224
  • View blog
  • Posts: 1,260
  • Joined: 05-December 13

Re: How do you break down your code?

Posted 06 February 2019 - 04:32 PM

View PostSkydiver, on 06 February 2019 - 06:55 PM, said:

Why is null different from an empty list?

How does this jig with you? Wouldn't null mean no collection exists at all. As an empty collection is still a collection, but without any objects in it, which just makes it an empty collection and not a non existing/null collection.

View Postfearfulsc2, on 06 February 2019 - 07:03 PM, said:

In this case, we have 2 guarantor objects but nothing to compare it to. That's why I have the compare for null/empty objects because I still have to show that there is a data mismatch. In this case, the total mismatches come to 5

Sorry, I am not following, can you explain this more, or if you already did, kindly link me to the post mentioned. You say have the compare for null/empty objects... I assume you DON'T mean you are trying to compare empty against null. Your reply left me perplexed. Care to elaborate?
Was This Post Helpful? 0
  • +
  • -

#34 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7109
  • View blog
  • Posts: 24,146
  • Joined: 05-May 12

Re: How do you break down your code?

Posted 06 February 2019 - 11:04 PM

Sheepings: I understand from the computer science point of view the difference between a null pointer to a list, as opposed to a pointer to an empty list, but I trying to understand it from the end user, real world perspective. To the IRS, it doesn't matter if I failed to send in my tax forms, or if I just send in a form with just name and tax ID number and signature before the deadline. They will still go after me for failing to file my taxes. It's a lack of useful data in both cases. I wish for the latter case, they would just treat it as filing on time, but needing corrections.
Was This Post Helpful? 1
  • +
  • -

#35 fearfulsc2   User is offline

  • D.I.C Regular

Reputation: 16
  • View blog
  • Posts: 279
  • Joined: 25-May 16

Re: How do you break down your code?

Posted 07 February 2019 - 08:27 AM

@Skydiver,

I'll try to be as clear as I can be because I know what I'm typing doesn't make much sense.

Real-world scenario goes like this:

1. We have a package for a certain application that has lots of information/objects such as Customer, Guarantor, Assets(Items), Application Information(ie: Term, Payment amount, interest rate, etc...), etc...

2. A customer sends us a package(same package/application id as one of our packages

3. We compare the package to our package by the objects inside and compare them by the individual fields. If there is a mismatch, increase total mismatch count by 1 for each mismatch.

4. If they send us information such as an extra Guarantor that we do not have, we still have to display that information but say all fields in that object are a mismatch since we have no information on our side. We can also do the same in reverse such as if we have an extra guarantor but they didn't send us that info.


In this thread, I was trying to find a way to make this code cleaner/shorter/more reusable since it's a huge recursive function.

I made a post earlier a month or so ago about how to compare objects by property and so forth. The comparisons work and the code for that is not in this post as I put that code in its own helper class and is not part of the code base in this post.

I'll share a little bit about what it does though.

The helper class will take two objects of any type and compare them by the individual property and see if they match. It then takes those values and converts them to a string and I add those string values to another object called History where that information is then displayed as our value and their value, the property name, and if the match is true or false and how many mismatches there are.

I hope that makes more sense.
Was This Post Helpful? 0
  • +
  • -

#36 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7109
  • View blog
  • Posts: 24,146
  • Joined: 05-May 12

Re: How do you break down your code?

Posted 07 February 2019 - 08:50 AM

I understood that part. What I don't understand why you would consider this a mismatch: Your company has a list of assets that is empty, while the submitter has a null pointer for assets.
Was This Post Helpful? 0
  • +
  • -

#37 fearfulsc2   User is offline

  • D.I.C Regular

Reputation: 16
  • View blog
  • Posts: 279
  • Joined: 25-May 16

Re: How do you break down your code?

Posted 07 February 2019 - 08:55 AM

Ohhhh I think I get what you're asking.
If I have an empty list and they gave me a null value, I return 0 mismatches and display no data. It's only when they send me something that I do not have or the other way around is when I would say there are n amount of mismatches.

If you go back to #16 and go to lines 54-58, that is when both are null, empty, or a combination of null/empty.
Was This Post Helpful? 0
  • +
  • -

#38 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7109
  • View blog
  • Posts: 24,146
  • Joined: 05-May 12

Re: How do you break down your code?

Posted 07 February 2019 - 10:36 AM

Thanks.

Anyway, this is the way I would refactor the code from post #16:
    public async Task<(IList<DisparityHistory>, int)> 
        CalculateDisparities<T, S>(IList<T> listA, IList<S> listB, IList<DisparityField> fields, bool hasMultiple)
        where T : class
        where S : class
    {
        if (listA == null || listB == null || listA.Count == 0 || listB.Count == 0)
            return await HandleNullOrZero();

        if (listA.Count < listB.Count)
            return await CalculateDisparities(listB, listA, fields, hasMultiple);

        var disparityHistories = new List<DisparityHistory>();
        int disparities = 0;
        bool shouldRestart = false;
        do
        {
            var lastItemA = listA.Last();
            foreach (var (itemA, itemB) in CartesianProduct())
            {
                if (await ProcessFields(itemA, itemB, itemA == lastItemA))
                {
                    shouldRestart = await RemoveMatch(itemA, itemB);
                    break;
                }
            }
        } while (shouldRestart);
        return (disparityHistories, disparities);

        IEnumerable<(T a, S b )> CartesianProduct() => listA.SelectMany(a => listB, (a, b ) => (a, b ));

        async Task<bool> RemoveMatch(T removeA, S removeB)
        {
            groupIndex++;
            listA.Remove(removeA);
            listB.Remove(removeB);
            if (listA.Count > 0 && listB.Count > 0)
                return true;

            listA = listA.Count == 0 ? null : listA;
            listB = listB.Count == 0 ? null : listB;
            await RecursivelyCalculateDisparities(listA, listB);
            return false;
        }

        async Task<bool> ProcessFields(T itemA, S itemB, bool isLastItemA)
        {
            bool foundUnique = false;

            foreach (var field in fields)
            {
                var (result, disparityHistory) = await this._compareHelper.Compare(itemB, itemA, field, groupIndex);
                disparityHistories.Add(disparityHistory);

                if (field.IsUnique && hasMultiple)
                {
                    if (result)
                    {
                        foundUnique = true;
                    }
                    else if (field.Compare)
                    {
                        disparityHistories.Remove(disparityHistory);
                        if (isLastItemA)
                        {
                            await RecursivelyCalculateDisparities(listA, (IList<S>)null);
                            await RecursivelyCalculateDisparities((IList<T>)null, listB);
                        }
                        return false;
                    }
                }

                if (!result && field.Compare)
                    disparities++;
            }
            return foundUnique;
        }

        async Task RecursivelyCalculateDisparities(IList<T> listAIn, IList<S> listBIn)
        {
            var (histories, count) = await this.CalculateDisparities(listAIn, listBIn, fields, hasMultiple);
            disparities += count;
            disparityHistories.AddRange(histories);
        }

        async Task<(IList<DisparityHistory>, int)> HandleNullOrZero()
        {
            var compares = new List<Task<(IList<DisparityHistory> list, int count)>>();
            int count = Math.Min(listA?.Count ?? 0, listB?.Count ?? 0);
            for (int i = 0; i < count; i++)
            {
                if (groupIndex == 0)
                    groupIndex++;
                compares.Add(this.Compare(fields, listA?[i], listB?[i], groupIndex));
                if (groupIndex >= 1)
                    groupIndex++;
            }

            return (await Task.WhenAll(compares)).Aggregate(
                        (list: new List<DisparityHistory>(), count: 0),
                        (accumulator, item) =>
                        {
                            accumulator.list.AddRange(item.list);
                            accumulator.count += item.count;
                            return accumulator;
                        });
        }
    }

    async Task<(IList<DisparityHistory>, int)>
        Compare(IList<DisparityField> disparityFields, object objectA, object objectB, int groupIndex)
    {
        IList<DisparityHistory> disparityHistories = new List<DisparityHistory>();
        int disparities = 0;
        foreach (var field in disparityFields)
        {
            var (result, disparityHistory) = await this._compareHelper.Compare(objectB, objectA, field, groupIndex);
            disparityHistories.Add(disparityHistory);
            if (field.Compare)
                disparities++;
        }
        return (disparityHistories, disparities);
    }


Was This Post Helpful? 1
  • +
  • -

#39 fearfulsc2   User is offline

  • D.I.C Regular

Reputation: 16
  • View blog
  • Posts: 279
  • Joined: 25-May 16

Re: How do you break down your code?

Posted 07 February 2019 - 12:40 PM

@skydiver

Thanks for the feedback! I will study it a bit and see what you did there. Thank you!
Was This Post Helpful? 0
  • +
  • -

#40 Skydiver   User is online

  • Code herder
  • member icon

Reputation: 7109
  • View blog
  • Posts: 24,146
  • Joined: 05-May 12

Re: How do you break down your code?

Posted 07 February 2019 - 02:34 PM

Beware the code there is just me doodling at the keyboard. I was doodling while distracted since I was taking care of a sick child and kept on being interrupted every 5 or 10 minutes.

The major change was to get rid of the crazy isAHigher condition switching you were doing. I got rid of that by always assuming that objectA (or listA as I've renamed it) is always bigger than or equal to the other list. If that assumption is not true, a recursive call is made reversing the parameters. See line 9-10.

The next major change was trying to invert the control structure of your nested for loops on lines 76-209. The CartesianProduct() I used to replace the nested for loops should be pretty obvious. Your setting i and j to -1 on your lines 87-88 really threw me for a loop (pun intended). There was also the setting i to higherValue on your line 205. These stealth goto's are why I created the RemoveMatches() and the bool it returns.

The rest of the code changes are mostly just folding together similar code, as well as, just applying some boolean induction to determine when conditions will never be true or always true to allow simplifying if statements or being able to split up their bodies into separate locations.

I didn't really grok what you were trying to on your lines 129-142. I rewrote those as my lines 65-66:
await RecursivelyCalculateDisparities(listA, (IList<S>)null);
await RecursivelyCalculateDisparities((IList<T>)null, listB);


If I was technically to follow exactly what you had written, my lines 65-66 would look like this:
await RecursivelyCalculateDisparities(listA, (IList<S>)null);
await RecursivelyCalculateDisparities((IList<S>)null, listB);


Notice the second call uses type S like the first call. I just made a judgement call to use T to fix what I believe to be typo in the original code.
Was This Post Helpful? 1
  • +
  • -

  • (3 Pages)
  • +
  • 1
  • 2
  • 3