Ajmal Jamil
Ajmal Jamil

Reputation: 829

Sort by most occurring string from json by key order and order contains array itself

I have JSON file which contains orders as arrays against same key as

[
   {
      "order":["Order1"]
   },
   {
      "order":["Order2"]
   },
   {
      "order":["Order2","Order3"]
   },
   {
      "order":["Order1","Order2"]
   },
   {
      "order":["Order2","Order3"]
   }
]

I want it to order by most occurred orders combination.

Kindly help me out in this.

NOTE: It is not a simple array of string kindly look at the json before you mark it as probable duplicate.

Upvotes: 1

Views: 393

Answers (1)

dbc
dbc

Reputation: 116805

This can be done as follows. First, introduce a data model for your orders as follows:

public class Order 
{
    public string[] order { get; set; }
}

Next, define the following equality comparer for enumerables:

public class IEnumerableComparer<TEnumerable, TElement> : IEqualityComparer<TEnumerable> where TEnumerable : IEnumerable<TElement>
{
    //Adapted from IEqualityComparer for SequenceEqual
    //https://stackoverflow.com/questions/14675720/iequalitycomparer-for-sequenceequal
    //Answer https://stackoverflow.com/a/14675741 By Cédric Bignon https://stackoverflow.com/users/1284526/c%C3%A9dric-bignon 
    public bool Equals(TEnumerable x, TEnumerable y)
    {
        return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y));
    }

    public int GetHashCode(TEnumerable obj)
    {
        // Will not throw an OverflowException
        unchecked
        {
            return obj.Where(e => e != null).Select(e => e.GetHashCode()).Aggregate(17, (a, b) => 23 * a + b);
        }
    }
}

Now you can deserialize the JSON containing the orders listed above and sort the unique orders by descending frequency as follows:

var items = JsonConvert.DeserializeObject<List<Order>>(jsonString);

//Adapted from LINQ: Order By Count of most common value
//https://stackoverflow.com/questions/20046563/linq-order-by-count-of-most-common-value
//Answer https://stackoverflow.com/a/20046812 by King King https://stackoverflow.com/users/1679602/king-king
var query = items
    //If order items aren't already sorted, you need to do so first.
    //use StringComparer.OrdinalIgnoreCase or StringComparer.Ordinal or StringComparer.CurrentCulture as required.
    .Select(i => i.order.OrderBy(s => s, StringComparer.Ordinal).ToArray()) 
    //Adapted from writing a custom comparer for linq groupby
    //https://stackoverflow.com/questions/37733773/writing-a-custom-comparer-for-linq-groupby
    //Answer https://stackoverflow.com/a/37734601 by Gert Arnold https://stackoverflow.com/users/861716/gert-arnold
    .GroupBy(s => s, new IEnumerableComparer<string [], string>())
    .OrderByDescending(g => g.Count())
    .Select(g => new Order { order = g.Key } );

var sortedItems = query.ToList();

Demo fiddle here.

Alternatively, if you want to preserve duplicates rather than merging them, you can do:

var query = items
    //If order items aren't already sorted, you may need to do so first.
    //use StringComparer.OrdinalIgnoreCase or StringComparer.Ordinal or StringComparer.CurrentCulture as required.
    .Select(i => i.order.OrderBy(s => s, StringComparer.Ordinal).ToArray()) 
    //Adapted from writing a custom comparer for linq groupby
    //https://stackoverflow.com/questions/37733773/writing-a-custom-comparer-for-linq-groupby
    //Answer https://stackoverflow.com/a/37734601 by Gert Arnold https://stackoverflow.com/users/861716/gert-arnold
    .GroupBy(s => s, new IEnumerableComparer<string [], string>())
    .OrderByDescending(g => g.Count())
    .SelectMany(g => g)
    .Select(a => new Order { order = a });

Demo fiddle #2 here.

Notes:

  • I define the equality comparer using two generic types IEnumerableComparer<TEnumerable, TElement> : IEqualityComparer<TEnumerable> where TEnumerable : IEnumerable<TElement> rather than just IEnumerableComparer<string> as shown in this answer to IEqualityComparer for SequenceEqual by Cédric Bignon in order to prevent the string [] sort key from being upcast to IEnumerable<string> via type inferencing in the .GroupBy(s => s, new IEnumerableComparer<string>()) lambda expression.

  • If you are sure the orders are already sorted, or ["Order3", "Order1"] differs from ["Order1", "Order3"], then replace i.order.OrderBy(s => s, StringComparer.Ordinal).ToArray() with just i.order.

Upvotes: 1

Related Questions