Is there an elegant way of converting this string array:
string[] a = new[] {"name", "Fred", "colour", "green", "sport", "tennis"};
into a Dictionary such that every two successive elements of the array become one {key, value} pair of the dictionary (I mean {"name" -> "Fred", "colour" -> "green", "sport" -> "tennis"})?
I can do it easily with a loop, but is there a more elegant way, perhaps using LINQ?
var dict = a.Select((s, i) => new { s, i })
.GroupBy(x => x.i / 2)
.ToDictionary(g => g.First().s, g => g.Last().s);
Since it's an array I would do this:
var result = Enumerable.Range(0,a.Length/2)
.ToDictionary(x => a[2 * x], x => a[2 * x + 1]);
How about this ?
var q = a.Zip(a.Skip(1), (Key, Value) => new { Key, Value })
.Where((pair,index) => index % 2 == 0)
.ToDictionary(pair => pair.Key, pair => pair.Value);
I've made a simular method to handle this type of request. But since your array contains both keys and values i think you need to split this first.
Then you can use something like this to combine them
public static IDictionary<T, T2> ZipMyTwoListToDictionary<T, T2>(IEnumerable<T> listContainingKeys, IEnumerable<T2> listContainingValue)
{
return listContainingValue.Zip(listContainingKeys, (value, key) => new { value, key }).ToDictionary(i => i.key, i => i.value);
}
a.Select((input, index) = >new {index})
.Where(x=>x.index%2!=0)
.ToDictionary(x => a[x.index], x => a[x.index+1])
I would recommend using a for loop but I have answered as requested by you.. This is by no means neater/cleaner..
public static IEnumerable<T> EveryOther<T>(this IEnumerable<T> source)
{
bool shouldReturn = true;
foreach (T item in source)
{
if (shouldReturn)
yield return item;
shouldReturn = !shouldReturn;
}
}
public static Dictionary<T, T> MakeDictionary<T>(IEnumerable<T> source)
{
return source.EveryOther()
.Zip(source.Skip(1).EveryOther(), (a, b) => new { Key = a, Value = b })
.ToDictionary(pair => pair.Key, pair => pair.Value);
}
The way this is set up, and because of the way Zip works, if there are an odd number of items in the list the last item will be ignored, rather than generation some sort of exception.
Note: derived from this answer.
IEnumerable<string> strArray = new string[] { "name", "Fred", "colour", "green", "sport", "tennis" };
var even = strArray.ToList().Where((c, i) => (i % 2 == 0)).ToList();
var odd = strArray.ToList().Where((c, i) => (i % 2 != 0)).ToList();
Dictionary<string, string> dict = even.ToDictionary(x => x, x => odd[even.IndexOf(x)]);
Related
I am trying to converting a Tuple<List<Guid>, string> to Dictionary<Guid, List<string>>. This is what I have so far:
var listOfTuples = GetListOfTuples(); // returns type List<Tuple<List<Guid>, string>>
var transformedDictionary = new Dictionary<Guid, List<string>>();
foreach (var listOfTuple in listOfTuples)
{
foreach (var key in listOfTuple.Item1)
{
if (!transformedDictionary.ContainsKey(key))
transformedDictionary[key] = new List<string> { listOfTuple.Item2 };
else transformedDictionary[key].Add(listOfTuple.Item2);
}
}
Is there a better way of doing this, perhaps using LINQ; SelectMany, Grouping, or toDictionary?
Update: I have tried this, but clearly not working:
listOfTuples.ToList()
.SelectMany(x => x.Item1,(y, z) => new { key = y.Item2, value = z })
.GroupBy(p => p.key)
.ToDictionary(x => x.Key, x => x.Select(m => m.key));
You are close. The problem is with selecting the right key and value
var result = listOfTuples.SelectMany(t => t.Item1.Select(g => (g, str: t.Item2)))
.GroupBy(item => item.g, item => item.str)
.ToDictionary(g => g.Key, g => g.ToList());
The mistake is here (y, z) => new { key = y.Item2, value = z } - you want the key to be the Guid and therefore instead of it being Item2 it should be z which is the Guid. So you can go with the way I wrote it or just
(y, z) => new { key = z, value = y.Item2 }
Also the .ToList() at the beginning is not needed. You say that listOfTuples already returns a list
I've writen a little test case to explain my issue.
I'm somehow able to query my DB to get a list of list of tuple.
From which I want to extract a list of tuple, with no duplicate, ordered by Item1 ... which is fine, but now I always want to remove tuple when Item2 is not sorted in descending order.
I was able to do this by creating a temporary list and then removing bad tuples.
Could you please help me do to this directly in linq (if possible ?) ?
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace Web.Test
{
[TestFixture]
public class ListListTupleTest
{
[TestCase]
public void TestCaseTest_1()
{
var input = new List<List<Tuple<int, decimal>>>
{
new List<Tuple<int, decimal>>
{
new Tuple<int, decimal>(5, 20),
new Tuple<int, decimal>(8, 10)
},
new List<Tuple<int, decimal>>
{
new Tuple<int, decimal>(7, 17),
new Tuple<int, decimal>(12, 9)
},
new List<Tuple<int, decimal>>
{
new Tuple<int, decimal>(7, 17),
new Tuple<int, decimal>(15, 10)
}
};
var goal = new List<Tuple<int, decimal>>()
{
new Tuple<int, decimal>(5, 20),
new Tuple<int, decimal>(7, 17),
new Tuple<int, decimal>(8, 10),
new Tuple<int, decimal>(12, 9)
};
var result = myFunction(input);
CollectionAssert.AreEqual(result, goal);
}
private List<Tuple<int, decimal>> myFunction(List<List<Tuple<int, decimal>>> myList)
{
var tmp = myList
.SelectMany(x => x.ToArray())
.Distinct()
.OrderBy(x => x.Item1)
.ToList();
var result = new List<Tuple<int, decimal>>();
if (tmp.Any())
{
result.Add(tmp.First());
decimal current = tmp.First().Item2;
foreach (var tuple in tmp.Skip(1))
{
if (tuple.Item2 < current)
{
result.Add(tuple);
current = tuple.Item2;
}
}
}
return result;
}
}
}
I agree with others that loop might be a best solution here, but if you really really want to use LINQ, you can use Aggregate like this:
return myList
.SelectMany(x => x.ToArray())
.Distinct()
.OrderBy(x => x.Item1)
.Aggregate(Enumerable.Empty<Tuple<int, decimal>>(),
(acc, value) => value.Item2 > acc.LastOrDefault()?.Item2 ?
acc :
acc.Concat(new[] {value}))
.ToList();
This basically replicates your loop: we start with empty set (Enumerable.Empty<Tuple<int, decimal>>()) and then aggregate gives values one by one to our callback. There we either return previous set as is, or adding current item to it, depending on Item2 comparision.
You can also use List as accumulator instead of Enumerable.Empty:
return myList
.SelectMany(x => x.ToArray())
.Distinct()
.OrderBy(x => x.Item1)
.Aggregate(new List<Tuple<int, decimal>>(),
(acc, value) =>
{
var last = acc.Count > 0 ? acc[acc.Count - 1] : null;
if (last == null || value.Item2 < last.Item2)
acc.Add(value);
return acc;
}); // ToList is not needed - already a list
To use LINQ for this, I use a special extension method that is based on the APL scan operator - it is like Aggregate, but returns all the intermediate results. In this case, I use a special variation that automatically pairs results with original data in a ValueTuple, and initializes the state with a Func on the first value:
public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, Func<T, TKey> fnSeed, Func<(TKey Key, T Value), T, TKey> combine) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var seed = (fnSeed(srce.Current), srce.Current);
while (srce.MoveNext()) {
yield return seed;
seed = (combine(seed, srce.Current), srce.Current);
}
yield return seed;
}
}
}
Now it is relatively straight forward to compute your result - you do it pretty much like you state:
var ans = input.SelectMany(sub => sub, (l, s) => s) // flatten lists to one list
.Distinct() // keep only distinct tuples
.OrderBy(s => s.Item1) // sort by Item1 ascending
.ScanPair(firstTuple => (Item2Desc: true, LastValidItem2: firstTuple.Item2), // set initial state (Is Item2 < previous valid Item2?, Last Valid Item2)
(state, cur) => cur.Item2 < state.Key.LastValidItem2 ? (true, cur.Item2) // if still descending, accept Tuple and remember new Item2
: (false, state.Key.LastValidItem2)) // reject Tuple and remember last valid Item2
.Where(statekv => statekv.Key.Item2Desc) // filter out invalid Tuples
.Select(statekv => statekv.Value); // return just the Tuples
I have an list with x items. I wish to get an results that groups this list based of a number and not a property.
For example.
I have a list of 8 items. I want to group them by 3.
I want to get a List thats contains three lists, where the first two lists contains each three items and the last list the remaining two.
I want a more elegant solution than this:
private static List<List<string>> GroupBy(List<string> pages, int groupSize)
{
var result = new List<List<TrimlinePage>>();
while (!(result.Count != 0 && result.Last().Count % 3 > 0))
{
int skip = result.Count*groupSize;
var group = pages.Skip(skip).Take(groupSize).ToList();
result.Add(group);
}
return result;
}
You can use the integer divison trick:
List<List<string>> lists = pages
.Select((str, index) => new { str, index })
.GroupBy(x => x.index / groupSize)
.Select(g => g.Select(x => x.str).ToList())
.ToList();
Example:
int groupSize = 3;
var pages = new List<string> { "A", "B", "C", "D", "E", "F", "G" };
List<List<string>> lists = pages
.Select((str, index) => new { str, index })
.GroupBy(x => x.index / groupSize)
.Select(g => g.Select(x => x.str).ToList())
.ToList();
Result:
foreach(var list in lists)
Console.WriteLine(string.Join(",", list));
Output:
A,B,C
D,E,F
G
So this approach will give you lists with the specified max-size, in this case 3. If you instead want to ensure that you always get three lists you need to use % instead of /:
List<List<string>> lists = pages
.Select((str, index) => new { str, index })
.GroupBy(x => x.index % groupSize)
.Select(g => g.Select(x => x.str).ToList())
.ToList();
Try this:
var list = Enumerable.Range(1,100);
var query = list
.Select((x, i) => new {x, i})
.GroupBy(v => v.i / 3).Select(g => g.Select(v =>v.x.ToList()))
.ToList();
Here's a simple solution using side effects (which is generally discouraged):
private static List<List<string>> GroupBy(List<string> pages, int groupSize)
{
var i = 0;
return pages.GroupBy(p => i++ / 3, (k, g) => g.ToList()).ToList();
}
Or if you want to avoid relying on side effects, you could use this:
private static List<List<string>> GroupBy(List<string> pages, int groupSize)
{
return pages.Select(p => new { p, i })
.GroupBy(x => x.i / 3)
.Select(g => g.Select(x => x.p).ToList())
.ToList();
}
LINQ is not the best solution. Often good old indexing is much more readable and efficient.
private static List<List<T>> GroupBy(List<T> pages, int groupSize)
{
var result = new List<List<T>>();
List<T> l;
for (int i=0; i < pages.Count; i++)
{
if (i%groupSize == 0)
{
l = new List<T>();
result.Add(l);
}
l.Add(pages[i]);
}
return result;
}
You could also have a look at morelinq which contains the Partition method.
It's available via NuGet.
int j = 0;
foreach (var e in XmlData.Elements())
{
xDictionary.Add(j++, e.Value);
}
You probably shouldn't be using a dictionary if the key is simply the positional index. I'd suggest using a list instead:
var xList = XmlData.Elements().ToList();
Well, this would do it, using the overload of Select which provides the index, and ToDictionary:
var dictionary = XmlData.Elements()
.Select((value, index) => new { value, index })
.ToDictionary(x => x.index, x => x.value);
That's assuming xDictionary was empty before you started.
Something like this: To create a new dictionary:
var dict = XmlData.Elements()
.Select((e, i) => new {Element = e, Index = i})
.ToDictionary(p => p.Index, p => p.Element.Value);
Also if you want to add to an existing dictionary you can use an AddRange convenience extension method:
xDictionary.AddRange(XmlData.Elements()
.Select((e, i) => new KeyValuePair<int, string>(i, e.Value)));
And the extension method implementation:
public static void AddRange<T>(this ICollection<T> source, IEnumerable<T> elements)
{
foreach (T element in elements)
{
source.Add(element);
}
}
Given two IEnumerables of the same size, how can I convert it to a Dictionary using Linq?
IEnumerable<string> keys = new List<string>() { "A", "B", "C" };
IEnumerable<string> values = new List<string>() { "Val A", "Val B", "Val C" };
var dictionary = /* Linq ? */;
And the expected output is:
A: Val A
B: Val B
C: Val C
I wonder if there is some simple way to achieve it.
And should I be worried about performance? What if I have large collections?
I don't if there is an easier way to do it, currently I'm doing like this:
I have an Extension method that will loop the IEnumerable providing me the element and the index number.
public static class Ext
{
public static void Each<T>(this IEnumerable els, Action<T, int> a)
{
int i = 0;
foreach (T e in els)
{
a(e, i++);
}
}
}
And I have a method that will loop one of the Enumerables and with the index retrieve the equivalent element on the other Enumerable.
public static Dictionary<TKey, TValue> Merge<TKey, TValue>(IEnumerable<TKey> keys, IEnumerable<TValue> values)
{
var dic = new Dictionary<TKey, TValue>();
keys.Each<TKey>((x, i) =>
{
dic.Add(x, values.ElementAt(i));
});
return dic;
}
Then I use it like:
IEnumerable<string> keys = new List<string>() { "A", "B", "C" };
IEnumerable<string> values = new List<string>() { "Val A", "Val B", "Val C" };
var dic = Util.Merge(keys, values);
And the output is correct:
A: Val A
B: Val B
C: Val C
With .NET 4.0 (or the 3.5 version of System.Interactive from Rx), you can use Zip():
var dic = keys.Zip(values, (k, v) => new { k, v })
.ToDictionary(x => x.k, x => x.v);
Or based on your idea, LINQ includes an overload of Select() that provides the index. Combined with the fact that values supports access by index, one could do the following:
var dic = keys.Select((k, i) => new { k, v = values[i] })
.ToDictionary(x => x.k, x => x.v);
(If values is kept as List<string>, that is...)
I like this approach:
var dict =
Enumerable.Range(0, keys.Length).ToDictionary(i => keys[i], i => values[i]);
If you use MoreLINQ, you can also utilize it's ToDictionary extension method on previously created KeyValuePairs:
var dict = Enumerable
.Zip(keys, values, (key, value) => KeyValuePair.Create(key, value))
.ToDictionary();
It also should be noted that using Zip extension method is safe against input collections of different lengths.