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);
}
}
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 duplicate keys with different values and I want to convert it to a dictionary with 1 key and its values.
The next example will explain best what I mean:
var tup = new List<Tuple<int, int>>();
tup.Add(new Tuple<int, int>(1, 1));
tup.Add(new Tuple<int, int>(1, 2));
var dic = new Dictionary<int, List<int>>();
What is an elegant way to convert the tup to dic?
I managed to do this with foreach but would like to write it in LINQ.
foreach (var item in tup)
{
if (dic.ContainsKey(item.Item1))
{
dic[item.Item1].Add(item.Item2);
}
else
{
dic.Add(item.Item1, new List<int> { item.Item2 });
}
}
var list = tup.GroupBy(x => x.Item1)
.ToDictionary(
x => x.Key,
x => x.Select(y => y.Item2).ToList());
First, we group by GroupBy item 1. This should be obvious enough.
Then, we call ToDictionary and pass in a keySelector and an elementSelector. They select the key and value respectively, given an IGrouping<int, Tuple<int, int>>.
For reference, this particular overload of ToDictionary is used.
Alternatively, as Iridium has said in the comments, this works as well:
var list = tup.GroupBy(x => x.Item1, x => x.Item2)
.ToDictionary(x => x.Key, x => x.ToList());
This overload of GroupBy allows you to select 2 things!
You first need to group by the first tuple element in order to find all elements that have the same key in the dictionary. And then just collect the second tuple elements and make a list out of it:
tup.GroupBy(t => t.Item1)
.ToDictionary(g => g.Key, g => g.Select(t => t.Item2).ToList());
You can use GroupBy to resolve this problem, like:
var tup = new List<Tuple<int, int>>();
tup.Add(new Tuple<int, int>(1, 1));
tup.Add(new Tuple<int, int>(1, 2));
var dic = tup
.GroupBy(x => x.Item1)
.ToDictionary(x => x.Key, tuples => tuples.Select(x => x.Item2).ToList());
BTW, in some cases you can use NameValueCollection, but this is not save your target type, for example
var nvc = tup.Aggregate(new NameValueCollection(),
(seed, current) =>
{
seed.Add(current.Item1.ToString(), current.Item2.ToString());
return seed;
});
foreach (var item in nvc)
{
Console.WriteLine($"Key = {item} Value = {nvc[item.ToString()]}");
}
I really don't understand this T thing yet. I need to convert below result to List
private void generateKeywords_Click(object sender, RoutedEventArgs e)
{
string srText = new TextRange(
txthtmlsource.Document.ContentStart,
txthtmlsource.Document.ContentEnd).Text;
List<string> lstShuffle = srText.Split(' ')
.Select(p => p.ToString().Trim().Replace("\r\n", ""))
.ToList<string>();
lstShuffle = GetPermutations(lstShuffle)
.Select(pr => pr.ToString())
.ToList();
}
public static IEnumerable<IEnumerable<T>> GetPermutations<T>(
IEnumerable<T> items)
{
if (items.Count() > 1)
{
return items
.SelectMany(
item => GetPermutations(items.Where(i => !i.Equals(item))),
(item, permutation) => new[] { item }.Concat(permutation));
}
else
{
return new[] { items };
}
}
this line below fails because i am not able to convert properly. i mean not error but not string list either
lstShuffle = GetPermutations(lstShuffle).Select(pr => pr.ToString()).ToList();
For any IEnumerable<IEnumerable<T>> we can simply call SelectMany.
Example:
IEnumerable<IEnumerable<String>> lotsOStrings = new List<List<String>>();
IEnumerable<String> flattened = lotsOStrings.SelectMany(s => s);
Since lstShuffle implements IEnumerable<string>, you can mentally replace T with string: you're calling IEnumerable<IEnumerable<string>> GetPermutations(IEnumerable<string> items).
As Alexi says, SelectMany(x => x) is the easiest way to flatten an IEnumerable<IEnumerable<T>> into an IEnumerable<T>.
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)]);