1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
Ajmal Jamil
  • 799
  • 1
  • 8
  • 15
  • It is not duplicate, the link you both provided is a simple array of strings. Mine is different. I have tried that solution but did not work. Can you open your visual studio and take ss of your output and post here? – Ajmal Jamil Jun 17 '19 at 20:34
  • 1
    It's not quite a duplicate. An initial call to `SelectMany` is required to convert the collection of collection of toppings into a flat enumerable of strings. See https://dotnetfiddle.net/G7T0AS and [Flatten List in LINQ](https://stackoverflow.com/q/1590723) or [Difference Between Select and SelectMany](https://stackoverflow.com/q/958949) – dbc Jun 17 '19 at 20:43
  • Thanks @dbc, your solution works like a charm but the "toppings": ["sausage", "beef"] will be considered as one. It is a combination of toppings and I want to sort them by the combination of toppings. As I have also write the output you can see. Thanks in advance. – Ajmal Jamil Jun 17 '19 at 20:57
  • 1
    Note that "3 most occurring orders" may not be well defined. If you have more than three orders that have the same count and are all most common, what do you want to return? – dbc Jun 17 '19 at 20:59
  • Then I will return the top 3 only, it does not matter in my case if more than three orders that have the same count. I need to return the top three most ordered toppings combination. – Ajmal Jamil Jun 17 '19 at 21:01
  • Can you tell me the solution of it? @dbc – Ajmal Jamil Jun 17 '19 at 21:06
  • 1
    Oh OK now I understand, you need to combine the answers from [GroupBy on complex object (e.g. `List`)](https://stackoverflow.com/q/35128996) and [writing a custom comparer for linq groupby](https://stackoverflow.com/q/37733773) with [IEqualityComparer for SequenceEqual](https://stackoverflow.com/q/37733773), see https://dotnetfiddle.net/hIzTfs – dbc Jun 17 '19 at 21:07
  • 1
    @RufusL - this isn't a pure duplicate of the linked question, one would need to combine the answers from [GroupBy on complex object (e.g. `List`)](https://stackoverflow.com/q/35128996) with [IEqualityComparer for SequenceEqual](https://stackoverflow.com/q/37733773) to get a proper answer, see https://dotnetfiddle.net/hIzTfs. Think this should be reopened? – dbc Jun 17 '19 at 21:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/195086/discussion-between-ajmal-jamil-and-dbc). – Ajmal Jamil Jun 17 '19 at 21:13
  • is `["sausage", "beef"]` equivalent to `["beef", "sausage"]`? – dbc Jun 17 '19 at 21:14
  • Yes they are same, Thanks it worked like a charm! You are an amazing person. @dbc – Ajmal Jamil Jun 17 '19 at 21:15
  • They are same but I don't think that will be the case as we always save toppings alphabetically. – Ajmal Jamil Jun 17 '19 at 21:16

1 Answers1

1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340