I have the following table:
Column_1 Column_2
val_1 | val_14
val_2 | val_17
val_1 | val_2
val_4 | null
val_1 | val_3
val_20 | val_4
val_17 | null
val_2 | val_20
val_14 | val_6
val_14 | null
Val_6 | null
val_3 | val_30
val_3 | val_19
I want to display Column_2 values
Eg: Select with Column_1 = val_1 will return (val_14, val_2, val_3) from Column_2.
Now, I want for each values in (val_14, val_2, val_3) to return also values from Column_2.
In summary:
val_1 => (val_14, val_2, val_3)
val_14 => (val_6, null)
val_6 => null
val_2 => (val_17, val_20)
val_17 => null
val_20 => (val_4)
val_4 => null
val_3 => (val_30, val_19)
etc...
Final output (val_14, val_2, val_3, val_6, val_17, val_20, val_4, val_30, val_19)
I have a function, with string parameter and list of all rows data
public List<string> MyFunction(string value)
{
return (from s in myListOfData where value.Contains(s.Column_1) select s).ToList();
}
This function return only the first level.
how can i do this query to display all children in linq? My attempts are unsuccessful.
Thank you
Desired order of records is a bit-tricky to get - looks like at first you want plain 1-st level and then traverse tree in down-left direction. It's a bit tricky.
If order is not important you can:
public List<string> MyFunction(string value)
{
return myListOfData
.Where(x => value.Contains(x.Column_1) && x.Column2 != null)
.Select(x => x.Column2)
.Aggregate(new List<string>(), (t, x) => {
t.Add(x);
t.AddRange(MyFunction(x));
return t; })
.ToList();
}
However, this results in lots of intermediate List creation. So better have enumerable:
public IEnumerable<string> MyFunction(string value)
{
foreach (var record in myListOfData.Where(x => value.Contains(x.Column_1) && x.Column2 != null)
{
yield return record.Column_2;
foreach (var child in MyFunction(record.Column_2))
yield return child;
}
}
And then take ToList() of this IEnumerable.
Still, if order is important you need two functions:
public List<string> MyFunction(string value)
{
.Where(x => value.Contains(x.Column_1) && x.Column2 != null)
.Select(x => new Tuple<string, IEnumerable<string>>(x.Column2, Traverse(x.Column2))
.Aggregate(new List<string>(), (t, x) => {
t.Add(x.Item1);
t.AddRange(x.Item2);
return t; })
.ToList();
}
public IEnumerable<string> Traverse(string value)
{
foreach (var record in myListOfData.Where(x => value.Contains(x.Column_1) && x.Column2 != null)
{
yield return record.Column_2;
foreach (var child in MyFunction(record.Column_2))
yield return child;
}
}
Assuming this is all in memory and nothing to do with an ORM.
You could use recursion. However, queues and stacks are safer and easier to debug.
Given some weird ill-defined class
public class Data
{
public int? Col1 { get; set; }
public int? Col2 { get; set; }
public Data(int? col1, int? col2)
{
Col1 = col1;
Col2 = col2;
}
}
You could use an iterator method and a Queue
public static IEnumerable<int> GetRecusive(List<Data> source,int val)
{
var q = new Queue<int>();
q.Enqueue(val);
while (q.Any())
{
var current = q.Dequeue();
var potential = source.Where(x => x.Col1 == current && x.Col2 != null);
foreach (var item in potential)
{
yield return item.Col2.Value;
q.Enqueue(item.Col2.Value);
}
}
}
Usage
// some ill-defined test data
var list = new List<Data>()
{
new Data(1, 14),
new Data(2, 17),
new Data(1, 2),
new Data(4, null),
new Data(1, 3),
new Data(20, 4),
new Data(17, null),
new Data(2, 20),
new Data(14, 6),
new Data(14, null),
new Data(6, null),
new Data(3, 30),
new Data(3, 19),
};
var results = GetRecusive(list,1);
// compose as a comma separated list
Console.WriteLine(string.Join(", ",results));
Output
14, 2, 3, 6, 17, 20, 30, 19, 4
Full Demo Here
If you like, you can turn it into an extension method to give you a LINQ Chain Method feel
public static IEnumerable<int> GetRecusive(this List<Data> source, int val)
Important Note : If you have a circular references then kiss your app goodbye. This will be the same for recursion or queues. If you need to protect against this, then I suggest using a HashSet of visited ids
Related
I have the below document schema.
{
"name":"Name",
"region":"New Jersey",
"address":"92 Something Rd",
"city":"Jersey City",
"state":"NJ",
"zipCode":"07302",
"country":"USA",
amenities":[
"Sauna",
"Locker",
"Shop"
],
"services":[
"Car Rental",
"Transportation"
]
}
I want with one call to the server to get all the documents matching any of the filter arguments where map 1-1 meaning "state" = "NJ" OR "city" = "Jersey City" but also when any value of the list contained to any document array child, example [ "Sauna", "Locker" ] ANY IN "amenities". And it should be an OR concatenation of all the possible filters.
Using the C# MongoDB Driver I came up with the below methods so far in a MongoRepository class but doesn't return the desired results.
public async Task<IEnumerable<T>> DocumentsMatchEqFieldValueAsync<T>(string collectionName,
IDictionary<string, string> fieldsValues = null,
IDictionary<string, IEnumerable<string>> fieldsWithEnumerableValues = null,
IEnumerable<ObjectId> ids = null)
{
var cursor = await GetEqAsyncCursor<T>(collectionName, fieldsValues, fieldsWithEnumerableValues, ids).ConfigureAwait(false);
return await cursor.ToListAsync().ConfigureAwait(false);
}
protected Task<IAsyncCursor<T>> GetEqAsyncCursor<T>(string collectionName,
IDictionary<string, string> fieldsValues = null,
IDictionary<string, IEnumerable<string>> fieldsWithEnumerableValues = null,
IEnumerable<ObjectId> ids = null)
{
var collection = GetCollection<T>(collectionName);
var builder = Builders<T>.Filter;
// Not sure if this is the correct way to initialize it because it seems adding an empty filter condition returning ALL document;
FilterDefinition<T> filter = new BsonDocument();
if (fieldsValues != null &&
fieldsValues.Any())
{
filter = filter | fieldsValues
.Select(p => builder.Eq(p.Key, p.Value))
.Aggregate((p1, p2) => p1 | p2);
}
if (fieldsWithEnumerableValues != null &&
fieldsWithEnumerableValues.Any())
{
filter = filter | fieldsWithEnumerableValues
.Select(p => builder.AnyEq(p.Key, p.Value))
.Aggregate((p1, p2) => p1 | p2);
}
if (ids != null &&
ids.Any())
{
filter = filter | ids
.Select(p => builder.Eq("_id", p))
.Aggregate((p1, p2) => p1 | p2);
}
return collection.FindAsync(filter);
}
I want it to be generic so the client can call the method like this.
public async Task should_return_any_records_matching_all_possible_criteria()
{
// Arrange
IDocumentRepository documentRepository = new MongoRepository(_mongoConnectionString, _mongoDatabase);
// Act
var documents = await documentRepository.DocumentsMatchEqFieldValueAsync<BsonDocument>(Courses,
fieldsValues: new Dictionary<string, string>
{
{ "state", "NJ" },
{ "city", "Jersey City" }
},
fieldsWithEnumerableValues: new Dictionary<string, IEnumerable<string>>
{
{ "services", new List<string> { "Car Rental", "Locker" } },
{ "amenities", new List<string> { "Sauna", "Shop" } }
});
// Assert
documents.ShouldNotBeEmpty();
}
I would expect documents that have "state" = "NJ" OR "city" = "Jersey City" OR "services" CONTAINS ANY OF "Car Rental", "Locker" OR "amenities" CONTAINS ANY OF "Sauna", "Shop".
I am posting below the method I ended up using after some research for the future help of anyone looking to do the same. I found how to query using regex here, write plain MongoDB queries and add them to the filter collection here, and how to debug the generated query here.
After having all this information and a little bit of experimentation using the Studio 3T client find below the method.
protected Task<IAsyncCursor<T>> GetEqAsyncCursor<T>(string collectionName,
IDictionary<string, string> fieldEqValue = null,
IDictionary<string, string> fieldContainsValue = null,
IDictionary<string, IEnumerable<string>> fieldEqValues = null,
IDictionary<string, IEnumerable<string>> fieldElemMatchInValues = null,
IEnumerable<ObjectId> ids = null)
{
var collection = GetCollection<T>(collectionName);
var builder = Builders<T>.Filter;
IList<FilterDefinition<T>> filters = new List<FilterDefinition<T>>();
if (fieldEqValue != null &&
fieldEqValue.Any())
{
filters.Add(fieldEqValue
.Select(p => builder.Eq(p.Key, p.Value))
.Aggregate((p1, p2) => p1 | p2));
}
if (fieldContainsValue != null &&
fieldContainsValue.Any())
{
filters.Add(fieldContainsValue
.Select(p => builder.Regex(p.Key, new BsonRegularExpression($".*{p.Value}.*", "i")))
.Aggregate((p1, p2) => p1 | p2));
}
if (fieldEqValues != null &&
fieldEqValues.Any())
{
foreach (var pair in fieldEqValues)
{
foreach (var value in pair.Value)
{
filters.Add(builder.Eq(pair.Key, value));
}
}
}
if (fieldElemMatchInValues != null &&
fieldElemMatchInValues.Any())
{
var baseQuery = "{ \"%key%\": { $elemMatch: { $in: [%values%] } } }";
foreach (var item in fieldElemMatchInValues)
{
var replaceKeyQuery = baseQuery.Replace("%key%", item.Key);
var bsonQuery = replaceKeyQuery.Replace("%values%",
item.Value
.Select(p => $"\"{p}\"")
.Aggregate((value1, value2) => $"{value1},
{value2}"));
var filter = BsonSerializer.Deserialize<BsonDocument>(bsonQuery);
filters.Add(filter);
}
}
if (ids != null &&
ids.Any())
{
filters.Add(ids
.Select(p => builder.Eq("_id", p))
.Aggregate((p1, p2) => p1 | p2));
}
var filterConcat = builder.Or(filters);
// Here's how you can debug the generated query
//var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<T>();
//var renderedFilter = filterConcat.Render(documentSerializer, BsonSerializer.SerializerRegistry).ToString();
return collection.FindAsync(filterConcat);
}
I have a straightforward LINQ query that is attempting to perform a GroupBy where one of the items in the statements is a List<string>.
var viewModel = reports
.GroupBy(c => new { c.Id, c.PetList })
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Prior to this statement I am executing my EF repository method which ultimately calls a method to create the PetList above.
If I remove the PetList from the GroupBy() it works as expected. Is there something I must do in order to group by a List<string> type?
I would assume that Id is an identifier, and hence any two c with the same Id is in fact the same and has the same PetList. As such we can GroupBy just the Id and get the PetList another way:
var viewModel = reports
.GroupBy(c => c.Id)
.Select(g => new ArmReportModel
{
PetList = g.First().PetList, // Might need FirstOrDefault() with some providers
Pets = g.Count()
});
Barring that, I'd want to first make sure I could use an IEqualityComparer<T> with the GroupBy. If the provider allows for that, then no problem. Otherwise I'd start with:
reports.Select(c => new {c.Id, c.PetList}).AsEnumerable()
This retrieves the minimum necessary from the provider into memory, so that the linq-to-objects provider can be used from that point on.
I need to be able to define an IEqualityComparer<T> for some T, so I stop using anonymous types:
private class IdAndList
{
public int Id { get; set; }
public List<string> PetList { get; set; }
}
private class ReportIdAndPetListComparer : IEqualityComparer<IdAndList>
{
public bool Equals(IdAndList x, IdAndList y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if (x.Id != y.Id) return false;
if (x.PetList == null) return y.PetList == null;
if (y.PetList == null) return false;
int count = x.PetList.Count;
if (y.PetList.Count != count) return false;
for (int i = 0; i != count; ++i)
if (x.PetList[i] != y.PetList[i]) return false;
return true;
}
public int GetHashCode(IdAndList obj)
{
int hash = obj.Id;
if (obj.PetList != null)
foreach (string pet in obj.PetList)
hash = hash * 31 + pet.GetHashCode();
return hash;
}
}
Some of the tests for null PetLists can be removed if you know that's not possible.
Now:
var viewModel = reports.Select(c => new IdAndList{c.Id, c.PetList}).AsEnumerable()
.GroupBy(c => c, new ReportIdAndPetListComparer())
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Or if the provider can't deal with constructing the IdAndPetList type, then:
var viewModel = reports.Select(c => new {c.Id, c.PetList})
.AsEnumerable()
.Select(c => new IdAndList{c.Id, c.PetList})
.GroupBy(c => c, new ReportIdAndPetListComparer())
.Select(g => new ArmReportModel
{
PetList = g.Key.PetList,
Pets = g.Count()
});
Consider this list of elements which has three properties, Value and Sequence Number and Group:
Value Sequence Number Group
Header, 1, null
Invoice, 2, 1
Invoice Line, 3, 1
Trailer, 4, null
The goal is only to sort by the sequence number. The value is irrelevant.
In the above, the obvious answer is to order by sequence number.
However, elements can repeat:
Header, 1, null
InvoiceA, 2, 1
Line Item, 3, 1
InvoiceB, 2, 2
Line Item, 3, 2
Trailer, 4, null
The above is the desired sequence. What Linq statement will produce the above?
Sorting by Sequence no longer works. Sorting by Group, then Sequence does not work.
The application of this is in EDI where the order of the data is significant.
So the first "trick" here is that you want all items with a null group to be separate groups, rather than having all null items combined into a single group.
This is actually fairly easy. We can just create an IEqualityComparer that compares items based on some other comparer, but that always considers two null items to be different, instead of being the same (typically two null items would be considered "equal").
public class SeparateNullComparer<T> : IEqualityComparer<T>
{
private IEqualityComparer<T> comparer;
public SeparateNullComparer(IEqualityComparer<T> comparer = null)
{
this.comparer = comparer ?? EqualityComparer<T>.Default;
}
public bool Equals(T x, T y)
{
if (x == null || y == null)
return false;
return comparer.Equals(x, y);
}
public int GetHashCode(T obj)
{
return comparer.GetHashCode(obj);
}
}
We can now group the items using this comparer so that all non-null items will be grouped together, whereas all of the null items will have their own groups.
Now how do we order the groups? We need to order these groups based on their sequence numbers, but we have a sequence of them, not just one, so we need a way of comparing two sequences to see which sequence comes first. We do this by checking the first item in each sequence, and then continually checking the next until one comes first or one ends and the other doesn't:
public class SequenceComparer<T> : IComparer<IEnumerable<T>>
{
private IComparer<T> comparer;
public SequenceComparer(IComparer<T> compareer = null)
{
this.comparer = comparer ?? Comparer<T>.Default;
}
public int Compare(IEnumerable<T> x, IEnumerable<T> y)
{
using (var first = x.GetEnumerator())
using (var second = x.GetEnumerator())
{
while (true)
{
var firstHasMore = first.MoveNext();
var secondHasMore = second.MoveNext();
if (!firstHasMore && !secondHasMore)
return 0;
var lengthComparison = firstHasMore.CompareTo(secondHasMore);
if (lengthComparison != 0)
return lengthComparison;
var nextComparison = comparer.Compare(first.Current, second.Current);
if (nextComparison != 0)
return nextComparison;
}
}
}
}
Combine that with flattening all of the groups back out when we're done, and we just need to put it all together:
var query = data.GroupBy(item => item.Group, new SeparateNullComparer<int?>())
.Select(group => group.OrderBy(item => item.SequenceNumber)
.ToList())
.OrderBy(group => group, new SequenceComparer<Foo>())
.ThenBy(group => group.First().Group)
.SelectMany(x => x);
You can also rely on the fact that GroupBy maintains the original order of items within groups, allowing you to order the data by SequenceNumber before grouping, instead of after. It'll do basically the same thing. It turns out to be a prettier query, but you just need to "know" that GroupBy maintains the proper ordering:
var query = data.OrderBy(item => item.SequenceNumber)
.GroupBy(item => item.Group, new SeparateNullComparer<int?>())
.OrderBy(group => group, new SequenceComparer<Foo>())
.ThenBy(group => group.Key)
.SelectMany(x => x);
If it doesn't have to be a linq query, you could write a single comparer that looks like this:
public class ValSeqGroupComparer : IComparer<ValSeqGroup>
{
public int Compare(ValSeqGroup x, ValSeqGroup y)
{
if (x == y) return 0;
// If only one has a group or there is no group in either
if (x.Group.HasValue ^ y.Group.HasValue || !x.Group.HasValue)
return x.Seq.CompareTo(y.Seq);
if (x.Group.Value != y.Group.Value)
return x.Group.Value.CompareTo(y.Group.Value);
return x.Seq.CompareTo(y.Seq);
}
}
Then using it like this:
[TestMethod]
public void One()
{
List<ValSeqGroup> items = new List<ValSeqGroup>()
{
new ValSeqGroup("x", 1, null),
new ValSeqGroup("x", 4, null),
new ValSeqGroup("x", 2, 1),
new ValSeqGroup("x", 2, 2),
new ValSeqGroup("x", 3, 1),
new ValSeqGroup("x", 3, 2)
};
items.Sort(new ValSeqGroupComparer());
foreach (var item in items)
{
Console.WriteLine("{0} {1} {2}", item.Value, item.Seq,item.Group);
}
}
You can achieve this by sorting the elements by Sequence Number (OrderBy(x => x.SequenceNumber)).
After that you can sort elements with exising group number (.Where(x => x.Group != null).OrderBy(x => x.Group))
In the end you have to insert null elements in list at the corresponding index.
var elements = new List<Element>
{
new Element{SequenceNumber = 1, Group = null}, new Element{SequenceNumber = 4, Group = null},new Element{SequenceNumber = 3, Group = 1},new Element{SequenceNumber = 3, Group = 3}, new Element{SequenceNumber = 3, Group = 2},new Element{SequenceNumber = 2, Group = 3},new Element{SequenceNumber = 2, Group = 1},new Element{SequenceNumber = 2, Group = 2}
};
// first sort
var sortedElements = elements.OrderBy(x => x.SequenceNumber).ToList();
// save null elements
var elementsWithNull = sortedElements
.Where(x => x.Group == null).ToList();
// group sorting
sortedElements = sortedElements
.Where(x => x.Group != null)
.OrderBy(x => x.Group).ToList();
// insert elements with null in sorted list
foreach (var element in elementsWithNull)
{
var firstIndexOfSequence = 0;
for (firstIndexOfSequence = 0;firstIndexOfSequence < sortedElements.Count && sortedElements[firstIndexOfSequence].SequenceNumber >= element.SequenceNumber; firstIndexOfSequence++)
{
// just to get index of the element with null group to know where to insert
}
sortedElements.Insert(firstIndexOfSequence, element);
}
I have 2 lists that I need to consolidate. List 1 has only the dates, and List 2 may have the time element as well:
var List1 = new[] {
new ListType{ val = new DateTime(2012, 1, 1)},
new ListType{ val = new DateTime(2012, 1, 2)}
};
List2 = new[] { new ListType{ val = new DateTime(2012, 1, 1, 5, 0, 0)} };
FinalList = new[] {
new ListType{ val = new DateTime(2012, 1, 1, 5, 0, 0)},
new ListType{ val = new DateTime(2012, 1, 2)}
};
The way I'm going about this is:
foreach (var l in List1) {
var match = List2.FirstOrDefault(q => q.val.Date == l.val);
if (match == null) continue;
l.val = match.val;
}
Is there a better way than iterating through List1, using FirstOrDefault and then reassigning the val? It works, so this is just more a curiosity if Linq has a more elegant way (i.e. I am missing something obvious).
Thanks
You can use Enumerable.Union with a custom IEqualityComparer<ListType>:
class ListType
{
public DateTime val { get; set; }
public class DateComparer : IEqualityComparer<ListType>
{
public bool Equals(ListType x, ListType y)
{
if (ReferenceEquals(x, y))
return true;
else if (x == null || y == null)
return false;
return x.val.Date == y.val.Date;
}
public int GetHashCode(ListType obj)
{
return obj.val.Date.GetHashCode();
}
}
}
and then ...
var finalList = List2.Union(List1, new ListType.DateComparer());
I wouldn't get rid of the loop, but for efficiency I'd build up a dictionary mapping dates to the first matchnig time:
var dateToTime = List2
.GroupBy(d => d.Date)
.ToDictionary(g => g.Key, g => g.First());
foreach (var l in List1)
{
DateTime match;
if (dateToTime.TryGetValue(l.val, out match))
l.val = match.val;
}
LINQ is made for querying items rather than updating items - if you need to update items, use something non-LINQ like a foreach loop. That said, if you want to generate a new list from the items in the first list, the following is the equivalent of your code:
var newList = List1.Select(l => new ListType { val =
dateToTime.ContainsKey(l.val) ? dateToTime[l.val] : l.val }).ToList();
Let's assume I have a list with objects of type Value. Value has a Name property:
private List<Value> values = new List<Value> {
new Value { Id = 0, Name = "Hello" },
new Value { Id = 1, Name = "World" },
new Value { Id = 2, Name = "World" },
new Value { Id = 3, Name = "Hello" },
new Value { Id = 4, Name = "a" },
new Value { Id = 5, Name = "a" },
};
Now I want to get a list of all "repeating" values (elements where the name property was identical with the name property of the previous element).
In this example I want a list with the two elements "world" and "a" (id = 2 and 5) to be returned.
Is this event possible with linq?
Of course I could so smth. like this:
List<Value> tempValues = new List<Value>();
String lastName = String.Empty();
foreach (var v in values)
{
if (v.Name == lastName) tempValues.Add(v);
lastName = v.Name;
}
but since I want to use this query in a more complex context, maybe there is a "linqish" solution.
There won't be anything built in along those lines, but if you need this frequently you could roll something bespoke but fairly generic:
static IEnumerable<TSource> WhereRepeated<TSource>(
this IEnumerable<TSource> source)
{
return WhereRepeated<TSource,TSource>(source, x => x);
}
static IEnumerable<TSource> WhereRepeated<TSource, TValue>(
this IEnumerable<TSource> source, Func<TSource, TValue> selector)
{
using (var iter = source.GetEnumerator())
{
if (iter.MoveNext())
{
var comparer = EqualityComparer<TValue>.Default;
TValue lastValue = selector(iter.Current);
while (iter.MoveNext())
{
TValue currentValue = selector(iter.Current);
if (comparer.Equals(lastValue, currentValue))
{
yield return iter.Current;
}
lastValue = currentValue;
}
}
}
}
Usage:
foreach (Value value in values.WhereRepeated(x => x.Name))
{
Console.WriteLine(value.Name);
}
You might want to think about what to do with triplets etc - currently everything except the first will be yielded (which matches your description), but that might not be quite right.
You could implement a Zip extension, then Zip your list with .Skip(1) and then Select the rows that match.
This should work and be fairly easy to maintain:
values
.Skip(1)
.Zip(items, (first,second) => first.Name==second.Name?first:null)
.Where(i => i != null);
The slight disadvantage of this method is that you iterate through the list twice.
I think this would work (untested) -- this will give you both the repeated word and it's index. For multiple repeats you could traverse this list and check for consecutive indices.
var query = values.Where( (v,i) => values.Count > i+1 && v == values[i+1] )
.Select( (v,i) => new { Value = v, Index = i } );
Here's another simple approach that should work if the IDs are always sequential as in your sample:
var data = from v2 in values
join v1 in values on v2.Id equals v1.Id + 1
where v1.Name == v2.Name
select v2;
I know this question is ancient but I was just working on the same thing so ....
static class utils
{
public static IEnumerable<T> FindConsecutive<T>(this IEnumerable<T> data, Func<T,T,bool> comparison)
{
return Enumerable.Range(0, data.Count() - 1)
.Select( i => new { a=data.ElementAt(i), b=data.ElementAt(i+1)})
.Where(n => comparison(n.a, n.b)).Select(n => n.a);
}
}
Should work for anything - just provide a function to compare the elements
You could use the GroupBy extension to do this.
Something like this
var dupsNames =
from v in values
group v by v.Name into g
where g.Count > 1 // If a group has only one element, just ignore it
select g.Key;
should work. You can then use the results in a second query:
dupsNames.Select( d => values.Where( v => v.Name == d ) )
This should return a grouping with key=name, values = { elements with name }
Disclaimer: I did not test the above, so I may be way off.