1

How can I sort an array of Accounts that need to be sorted on an array of PersonRoles that say which role an associated person has with that account?

For example,

Bob is the Owner (O) of account 12, the Co-signer (CO) of account 123, and the Beneficiary of account 1234.

Joe is the Owner (O) of account 123 and account 1234 the Beneficiary (BE) of account 12.

How would I sort the array of Accounts for Bob in the order of Owners (O) first, then Co-signer('CO'), then Beneficiary (BE) in that order.

Accounts object structure

Accounts
{
    AccountNumber: 12,
    PersonRoles: [
                     {
                         AccountRoleCode: "O",
                         AccountRoleDescription: "Owner",
                         Person: "Bob"
                     },
                     {
                         AccountRoleCode: "CO",
                         AccountRoleDescription: "Co-Signer",
                         Person: ""
                     },
                     {
                         AccountRoleCode: "BE",
                         AccountRoleDescription: "Beneficiary",
                         Person: "Joe"
                     },
                 ],
    Balance: 5.00
},
{
    AccountNumber: 123,
    PersonRoles: [
                     {
                         AccountRoleCode: "O",
                         AccountRoleDescription: "Owner",
                         Person: "Joe"
                     },
                     {
                         AccountRoleCode: "CO",
                         AccountRoleDescription: "Co-Signer",
                         Person: "Bob"
                     },
                     {
                         AccountRoleCode: "BE",
                         AccountRoleDescription: "Beneficiary",
                         Person: null
                     },
                 ],
    Balance: 100.00
},
{
    AccountNumber: 1234,
    PersonRoles: [
                     {
                         AccountRoleCode: "O",
                         AccountRoleDescription: "Owner",
                         Person: "Joe"
                     },
                     {
                         AccountRoleCode: "CO",
                         AccountRoleDescription: "Co-Signer",
                         Person: null
                     },
                     {
                         AccountRoleCode: "BE",
                         AccountRoleDescription: "Beneficiary",
                         Person: "Bob"
                     },
                 ],
    Balance: 10000000.00
}

Original array of Accounts listed under Bob returned from API.

[1234, 12, 123]

Desired Sorted array.

[12, 123, 1234]

My initial approach is to use LINQ on the array, but I'm unsure how to loop through the Accounts[] and then loop through the PersonRoles[] to sort the Accounts[] based on the PersonRoles[].

Does this need like a double LINQ query? Or would another approach be better?

1

4 Answers 4

1
public class AccountsByNameComparer : IComparer<Account>
{
    private readonly string _name;

    public AccountsByNameComparer(string name)
    {
        _name = name;
    }

    public int Compare(Account x, Account y)
    {
        return AccountSortValue(x).CompareTo(AccountSortValue(y));
    }

    private int AccountSortValue(Account account)
    {
        if (account.PersonRoles.Any(role => role.AccountRoleCode == "O"
                                            && role.Name == _name)) return 0;
        if (account.PersonRoles.Any(role => role.AccountRoleCode == "CO"
                                            && role.Name == _name)) return 1;
        if (account.PersonRoles.Any(role => role.AccountRoleCode == "BE"
                                            && role.Name == _name)) return 2;
        return 3;
    }
}

Now you can do

accounts.Sort(new AccountsByNameComparer("Bob"));

or

var sorted = accounts.OrderBy(a => a, new AccountsByNameComparer("Bob"));

The benefits of this are

  • You can unit test the comparer
  • You can have different comparers for the same class, in case in some other context you want to sort differently
  • Putting it in a separate class ensures that you won't duplicate code if you find yourself needing the same sorting in multiple places.

This is a regrettably long and convoluted unit test. But you have to verify that it works somehow, and this is usually easier than actually running the whole application.

[TestClass]
public class SortAccountsByNameTests
{
    [TestMethod]
    public void AccountsAreSortedInCorrectOrder()
    {
        var account1 = new Account
        {
            PersonRoles = new PersonRole[]
            {
                new PersonRole {AccountRoleCode = "BE", Name = "Bob"},
                new PersonRole {AccountRoleCode = "CO", Name = "Steve"},
                new PersonRole {AccountRoleCode = "O", Name = "John"},
            }
        };
        var account2 = new Account
        {
            PersonRoles = new PersonRole[]
            {
                new PersonRole {AccountRoleCode = "CO", Name = "Bob"},
                new PersonRole {AccountRoleCode = "O", Name = "Steve"},
                new PersonRole {AccountRoleCode = "BE", Name = "John"},
            }
        };
        var account3 = new Account
        {
            PersonRoles = new PersonRole[]
            {
                new PersonRole {AccountRoleCode = "O", Name = "Bob"},
                new PersonRole {AccountRoleCode = "CO", Name = "Steve"},
                new PersonRole {AccountRoleCode = "BE", Name = "John"},
            }
        };
        var account4 = new Account
        {
            PersonRoles = new PersonRole[]
            {
                new PersonRole {AccountRoleCode = "O", Name = "Al"},
                new PersonRole {AccountRoleCode = "CO", Name = "Steve"},
                new PersonRole {AccountRoleCode = "BE", Name = "John"},
            }
        };
        var unsorted = new Account[] {account1, account2, account3, account4};
        var comparer = new AccountsByNameComparer("Bob");
        var sorted = unsorted.OrderBy(a => a, comparer);
        var expectedOrder = new Account[]{account3, account2, account1, account4};
        Assert.IsTrue(expectedOrder.SequenceEqual(sorted));
    }
}

I'm going to get carried away now. What if you want to change the order in which you sort these without rewriting the entire comparer? Or you just don't like those if statements? (Sorry, this is obnoxious and useless. Why am I doing this?)

public class AccountsByNameComparer : IComparer<Account>
{
    private readonly string _name;
    private readonly List<string> _preferredRoleSequence;

    public AccountsByNameComparer(string name, IEnumerable<string> preferredRoleSequence)
    {
        _name = name;
        _preferredRoleSequence = preferredRoleSequence.ToList();
    }

    public int Compare(Account x, Account y)
    {
        return AccountSortValue(x).CompareTo(AccountSortValue(y));
    }

    private int AccountSortValue(Account account)
    {
        var rolesMatchedByName = account.PersonRoles
            .Where(role => role.Name == _name);
        var preferredRoleMatches =
            rolesMatchedByName.Select(role => 
                _preferredRoleSequence.IndexOf(role.AccountRoleCode))
                    .Where(index => index > -1)
                    .ToArray();
        if (preferredRoleMatches.Any())
            return preferredRoleMatches.Min();
        return Int32.MaxValue;
    }
}

public class ExecutiveAccountsByNameComparer : AccountsByNameComparer
{
   public ExecutiveAccountsByNameComparer(string name)
        :base(name, new []{"O", "CO", "BE" }) { }

}
Sign up to request clarification or add additional context in comments.

Comments

1

Something like this should work:

    var result = accounts
        .Select(a => new
        {
            a.AccountNumber,
            RoleCodes = a.PersonRoles
                .Where(r => r.Person == "Bob")
                .Select(r => r.AccountRoleCode)
                .ToArray(),
        })
        .OrderBy(a => a.RoleCodes.Select(code => GetOrderByCode(code)).Max())
        .ThenBy(a => a.AccountNumber);

Comments

1

Here's one way, assuming Bob has one and only one role in all accounts:

var ordered = 
    from a in Accounts
    from r in a.PersonRoles 
    where r.Person == "Bob" 
    let ordering = r.AccountRoleCode == "O" ? 1 : r.AccountRoleCode == "CO" ? 2 : 3
    orderby ordering
    select a.AccountNumber;

Variation, if Bob can have multiple roles (and Bob has a role in every account). In this case, we first select the right role, the first one in the order specified:

var ordered = 
    from a in Accounts
    let bobsrole = (
        from r in a.PersonRoles
        where r.Person == "Bob"
        let o = r.AccountRoleCode == "O" ? 1 : r.AccountRoleCode == "CO" ? 2 : 3
        orderby o
        select (rolename: r,ordering: o)
    ).First()
    orderby bobsrole.ordering
    select a.AccountNumber;

Excercise for the reader: what if there are accounts where Bob is not involved?

Comments

1

You can use a Linq statement like this:

var givenPerson = "Bob";
Accounts.Where(a => a.PersonRoles.SelectMany(r => r.Person).Contains(givenPerson))
    .OrderBy(a => a, new CustomComparerForRoleCode(givenPerson));

To do a custom compare to sort by the AccountRoleCode, you'll need a comparer class:

public class CustomComparerForRoleCode : IComparer<PersonRole[]>
{
    public string PersonInRole { get; set; }

    public CustomComparerForRoleCode(string personInRole) {
        this.PersonInRole = personInRole;
    }

    public int Compare(PersonRole[] x, PersonRole[] y) {
        var roleCode = x.First(r => r.Person == PersonInRole).AccountRoleCode;
        var otherRoleCode = y.First(r => r.Person == PersonInRole).AccountRoleCode;
        if (roleCode == otherRoleCode)
            return 0;
        switch (roleCode) {
            case "O":
                return 1;
            case "BE":
                return -1;
            case "CO":
                if (otherRoleCode == "O")
                    return -1;
                return 1;
        }
    }
}

This assumes a person can have only one role per account. Adjust as needed.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.