Reputation: 829
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
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