I have a SortedList of Lists and I am interested in finding the KEY that corresponds to the longest list (list with the most items in it). In code, that looks like:
// how the list is defined:
var myList = new SortedList<long, List<string>>();
// EXAMPLE data only:
myList.Add(0, new List<string>());
myList[0].AddRange(new []{"a", "b", "c"});
myList.Add(8, new List<string>());
myList[8].AddRange(new []{"1", "2"});
myList.Add(23, new List<string>());
myList[23].AddRange(new []{"c", "d", "e", "f", "g"});
In the above example the result should be "23" since that is the key that goes with the longest list.
I know how to write this with a for loop, but I think this should be a simple to do with LINQ. That said, I can't seem to get the syntax quite right! Any help is appreciated!
There's maybe a more efficient way, but you can order by count (of value) descending, and take first.
myList.OrderByDescending(m => m.Value.Count()).First().Key;
of course, if you want all the keys with highest count (they may be multiple values with same length), you should do a group by count.
Something like that.
myList.GroupBy(m => m.Value.Count())
.OrderByDescending(m => m.Key)//I'm the key of the group by
.First()
.Select(g => g.Key);//I'm the key of the SortedList
So if you add to your sample an item with same list length
myList.Add(24, new List<string>());
myList[24].AddRange(new[] {"a", "b", "c", "d", "e"});
you will get 23 And 24.
same could be achieved with
from item in myList
let maxCount = myList.Max(x => x.Value.Count())
where item.Value.Count() == maxCount
select item.Key;
Whilst a sort will give you correct results, it requires O(n log n) time to execute, which is asymptotically higher than a simple O(n) sweep:
int maxLength = myList.Max(x => x.Value.Count);
var longestKeys = myList.Where(x => x.Value.Count == maxLength).Select(x => x.Key);
using MaxBy of morelinq
var key = myList.MaxBy(x => x.Value.Count()).Key;
Just for the sake of adding yet another way of doing it, you can achieve this with Linq's Aggregate method like so:
//Extension method
public static long MaxIndex(this SortedList<long, List<string>> list)
{
return list.Aggregate(
new { MaxValue = -1, Key = -1L },
((agg, current) => (current.Value.Count.CompareTo(agg.MaxValue) > 0 || agg.Key == -1) ?
new { MaxValue = current.Value.Count, Key = current.Key } :
new { MaxValue = agg.MaxValue, Key = agg.Key })).
Key;
}
// how the list is defined:
var myList = new SortedList<long, List<string>>();
// EXAMPLE data only:
myList.Add(0, new List<string>());
myList[0].AddRange(new[] { "a", "b", "c" });
myList.Add(8, new List<string>());
myList[8].AddRange(new[] { "1", "2" });
myList.Add(23, new List<string>());
myList[23].AddRange(new[] { "c", "d", "e", "f", "g" });
var idx = myList.MaxIndex();
This is adapted from this SO answer: https://stackoverflow.com/a/15068695/172769
Cheers
Related
Currently, I am trying to implement a code to generate frequent sequences. In doing so I need to get an in-place sort of a list of lists of strings as follows:
List<List<string>> myList = new List<List<string>>();
List<string> input1 = new List<string>() {"a", "b", "d"};
List<string> input2 = new List<string>() {"a", "b", "c"};
myList.Add(input1);
myList.Add(input2);
The output that I need is:
myList = {{"a","b","c"},{"a","b","d"}};
I have tried to use myList.Sort() but it raised a System.InvalidOperationException.
I am not that good with LINQ so I haven't used anything of the sort.
How about :
myList = myList.OrderBy(s => string.Join(string.Empty, s)).ToList();
The trick is to sort according to the string made by the concatenation of each element of the child list.
If you want to solve with Sort() you may use this approach
myList.Sort((x, y) => x.Zip(y,(l1,l2) => string.Compare(l1,l2)).FirstOrDefault(c => c != 0));
Otherwise I would concartenate all items into a single string and compare those.
This is less efficient because the string objects have to be created first.
myList = myList.OrderBy(string.Concat).ToList();
Sample: https://dotnetfiddle.net/1VmohI
You can try below code:
List<string> input1 = new List<string>() { "a", "b", "d" };
List<string> input2 = new List<string>() { "a", "b", "c" };
//Instead of adding input as List<string>, add it as string
string delimiter = ",";
var input1Str = input1.Aggregate((i, j) => i + delimiter + j);
var input2Str = input2.Aggregate((i, j) => i + delimiter + j);
var myListStr = new List<string>();
myListStr.Add(input1Str);
myListStr.Add(input2Str);
myListStr.Sort();
//Now you can convert it into List<List<string>>
List<List<string>> myList = myListStr.Select(x => x.Split(',').ToList()).ToList();
You can also use
myList = myList.OrderBy(arr => arr[0])
.ThenBy(arr => arr[1])
.ThenBy(arr => arr[2])
.ToList();
Hi I have allLists that contains lists of string I want to find common items among these string lists
i have tried
var intersection = allLists
.Skip(1)
.Aggregate(
new HashSet<string>(allLists.First()),
(h, e) => { h.IntersectWith(e); return h);`
and also intersection ( hard code lists by index) all of them did not work when I tried
var inter = allLists[0].Intersect(allLists[1]).Intersect(allLists[2])
.Intersect(allLists[3]).ToList();
foreach ( string s in inter) Debug.WriteLine(s+"\n ");
So how am I going to do this dynamically and get common string items in the lists;
is there a way to avoid Linq?
Isn't this the easiest way?
var stringLists = new List<string>[]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "d", "b", "c" },
new List<string>(){ "a", "e", "c" }
};
var commonElements =
stringLists
.Aggregate((xs, ys) => xs.Intersect(ys).ToList());
I get a list with just "c" in it.
This also handles the case if elements within each list can be repeated.
I'd do it like this:
class Program
{
static void Main(string[] args)
{
List<string>[] stringLists = new List<string>[]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "d", "b", "c" },
new List<string>(){ "a", "e", "c" }
};
// Will contian only 'c' because it's the only common item in all three groups.
var commonItems =
stringLists
.SelectMany(list => list)
.GroupBy(item => item)
.Select(group => new { Count = group.Count(), Item = group.Key })
.Where(item => item.Count == stringLists.Length);
foreach (var item in commonItems)
{
Console.WriteLine(String.Format("Item: {0}, Count: {1}", item.Item, item.Count));
}
Console.ReadKey();
}
}
An item is a common item if it occurs in all groups hence the condition that its count must be equal to the number of groups:
.Where(item => item.Count == stringLists.Length)
EDIT:
I should have used the HashSet like in the question. For lists you can replace the SelectMany line with this one:
.SelectMany(list => list.Distinct())
I'd like know if at least one element of listRef is present more than once in listA ? The other values can be present more than once.
List<string> listA = new List<string> { "A", "A", "B", "C", "D", "E" };
List<string> listRef = new List<string> { "B", "D" };
Thanks,
Try this:
bool hasRef = listref.Any(r => listA.Count(a => a == r) > 1);
I would use ToLookup method to generate Lookup<string, string> first, and then use it to check your condition:
var lookup = listA.ToLookup(x => x);
return listRef.Any(x => lookup.Contains(x) && lookup[x].Count() > 1);
You could use GroupBy and ToDictionary to achieve the same:
var groups = listA.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
return listRef.Any(x => groups.ContainsKey(x) && groups[x] > 1);
something like this
var query = listRef.Where(x=>
listA.Where(a => a == x)
.Skip(1)
.Any());
listRef.ForEach(refEl => {
var count = listA.Count(aEl => aEl == refEl);
if(count > 1) {
//Do something
}
});
Finding the best performing option in this case is not simple because that depends on the number of items in the lists and the expected result.
Here's a way to do it that is performant in the face of big lists:
var appearances = listA.GroupBy(s => s)
.Where(g => g.Count() > 1)
.ToDictionary(g => g.Key, g => g.Count());
var hasItemAppearingMoreThanOnce = listRef.Any(r => appearances.ContainsKey(r));
this works
List<string> listA = new List<string> { "A", "A", "B", "C", "D", "E" };
List<string> listRef = new List<string> { "A", "D" };
foreach (var item in listRef)
{
if (listA.Where(x => x.Equals(item)).Count() > 1)
{
//item is present more than once
}
}
this can be another way to do
List<string> listA = new List<string> { "A", "A", "B", "C", "D", "E" , "D" };
List<string> listRef = new List<string> { "B", "D" };
var duplicates = listA.GroupBy(s => s).SelectMany(grp => grp.Skip(1));
var newData = duplicates.Select(i => i.ToString()).Intersect(listRef);
var result = listA.GroupBy(x=>x)
.Where(g=>g.Count()>1&&listRef.Contains(g.Key))
.Select(x=>x.First());
bool a = result.Any();
If the second list is large and can contain duplicates i would use a HashSet<string> and IntersectWith to remove possible duplicates and strings which are not in the first list from the second:
var refSet = new HashSet<string>(listRef);
refSet.IntersectWith(listA);
bool anyMoreThanOne = refSet.Any(rs => listA.ContainsMoreThanOnce(rs, StringComparison.OrdinalIgnoreCase));
Here the extension which is not very elegant but works:
public static bool ContainsMoreThanOnce(this IEnumerable<string> coll, String value, StringComparison comparer)
{
if (coll == null) throw new ArgumentNullException("col");
bool contains = false;
foreach (string str in coll)
{
if (String.Compare(value, str, comparer) == 0)
{
if (contains)
return true;
else
contains = true;
}
}
return false;
}
DEMO
However, if the second listRef isn't large or doesn't contain duplicates you can just use:
bool anyMoreThanOne = listRef
.Any(rs => listA.ContainsMoreThanOnce(rs, StringComparison.OrdinalIgnoreCase));
I have a very simple List<string> setup which contains lots of single characters per item (IE a foreach would console out to "a" "k" "p" etc)
What I'd like to do is be able to group the items and also count how many of each occurs so I'd get an output similar to:
a - 2
t - 3
y - 3
Any tips on the best way to do this?
I am using .Net 4 if that's any help.
(Given that each entry is a single character, is there any reason you don't have a List<char> by the way?)
How about:
// To get a Dictionary<string, int>
var counts = list.GroupBy(x => x)
.ToDictionary(g => g.Key, g => g.Count());
// To just get a sequence
var counts = list.GroupBy(x => x)
.Select(g => new { Text = g.Key, Count = g.Count() });
Note that this is somewhat inefficient in terms of internal representation. You could definitely do it more efficiently "manually", but it would also take more work. Unless your list is large, I would stick to this.
The easiest way to do this is the Linq using
var list = new[] { "a", "a", "b", "c", "d", "b" };
var grouped = list
.GroupBy(s => s)
.Select(g => new { Symbol = g.Key, Count = g.Count() });
foreach (var item in grouped)
{
var symbol = item.Symbol;
var count = item.Count;
}
var list = new[] {"a", "t", "t", "y", "a", "y", "y", "t"};
var result = (from item in list
group item by item into itemGroup
select String.Format("{0} - {1}", itemGroup.Key, itemGroup.Count()));
Let's say I have a list of unknown number of elements in string value, I want to divide it to n subarray or lists (n could be any int, for example n=3), what is best way to do it?
note: the number of elements in each group is not necessary to be equal
LINQ GroupBy and Select methods can help:
var list = new List<string>() { "A", "B", "C", "D", "E", "F", "G" };
int groupCount = 3;
var subLists = list.Select((s, i) => new {Str = s, Index = i}).
GroupBy(o => o.Index % groupCount, o => o.Str).
Select(coll => coll.ToList()).
ToList();
This code will result in subLists containing a list of three List<string> collections: {"A", "D", "G"}, {"B", "E"} and {"C", "F"}. In order to achieve that I based my grouping on element indices in the original list (there is an overload for Select method that lets you do that, see link above). You can use some other logic to select the key.
In my example subLists is a List<List<string>>. If you need an array, use ToArray where appropriate.
EDIT: using modulo operation for grouping may not be a good idea if you care about the way values are distributed between lists. Probably the better option is to do it this way:
var list = new List<string>() { "A", "B", "C", "D", "E", "F", "G" };
int groupCount = 3;
int maxPerGroup = (int)Math.Ceiling((double)list.Count / groupCount);
var subLists = list.Select((s, i) => new {Str = s, Index = i}).
GroupBy(o => o.Index / maxPerGroup, o => o.Str).
Select(coll => coll.ToList()).
ToList();
This will produce the following result: {"A", "B", "C"}, {"D", "E", "F"}, {"G"} which may be more sane way to distribute the values.
Bottom line is, you can achieve what you need by using GroupBy and Select methods, just provide the correct grouping logic that is suitable for your domain.
alternately, you can do it in linq like
var n = 4;
var i = 0;
var list = new List<string> { "a", "b", "c", "d", "e", "f", "g" };
var res = list.GroupBy(x => Math.Ceiling((double)++i / n)).Select(x=>x.Select(y=>y).ToList()).ToList();
Console.Write(res);
write this in Console program or Linqpad and change values of n to see the effect