I have two lists as follow
var topicsA = new List<Topic>()
{
new Topic(){ TopicCode = "T01", TopicAccessed = false },
new Topic(){ TopicCode = "T02", TopicAccessed = false },
new Topic(){ TopicCode = "T03", TopicAccessed = false }
};
var topicsB = new List<Topic>()
{
new Topic(){ TopicCode = "T01"},
new Topic(){ TopicCode = "T02"}
};
What is a quick way of setting the TopicAccessed value of topicsA to true where the property values of TopicCode are the same in topicsB
for example, in this case T01and T02 would have TopicAccessed set to true
Regards
All answers show quadratic algorithm complexity (Big O). This code snippet shows linear big O:
var accessedTopicsByCode = topicsB.ToDictionary(x => x.TopicCode);
foreach (var t in topicsA)
{
if (accessedTopicsByCode.ContainsKey(t.TopicCode))
{
t.TopicAccessed = true;
}
}
Use a dictionary to have better performance:
var hash = new Dictionary<string,bool>();
foreach(var topicB in topicsB)
{
hash[topicB.TopicCode] = true;
}
foreach(var topicA in topicsA)
{
topicA.TopicAccessed = hash.ContainsKey(topicA.TopicCode);
}
The complexity of this solution is O(n+m) whereas nested loop is O(n*m). ContainsKey's complexity is O(1)
Or you can also use a HashSet because we only need the key, not the value:
var hash = new HashSet<string>();
foreach(var topicB in topicsB)
{
hash.Add(topicB.TopicCode);
}
foreach(var topicA in topicsA)
{
topicA.TopicAccessed = hash.Contains(topicA.TopicCode);
}
This has the same complexity as using a Dictionary, but it's more efficient in memory usage. Contains' complexity is O(1)
You could try something like this:
// We iterate though the items of topicsA list.
foreach(var topicA in topicsA)
{
// If topicsB list contains any item with the same TopicCode like
// the current's item TopicCode, update the TopicAccessed.
if(topicsB.Any(x=>x.TopicCode == topicaA.TopicCode))
{
topicA.TopicAccessed = true;
}
}
Related
I am working with two lists. The first contains a large sequence of strings. The second contains a smaller list of strings. I need to find where the second list exists in the first list.
I worked with enumeration, and due to the large size of the data, this is very slow, I was hoping for a faster way.
List<string> first = new List<string>() { "AAA","BBB","CCC","DDD","EEE","FFF" };
List<string> second = new List<string>() { "CCC","DDD","EEE" };
int x = SomeMagic(first,second);
And I would need x to = 2.
Ok, here is my variant with old-good-for-each-loop:
private int SomeMagic(IEnumerable<string> source, IEnumerable<string> target)
{
/* Some obvious checks for `source` and `target` lenght / nullity are ommited */
// searched pattern
var pattern = target.ToArray();
// candidates in form `candidate index` -> `checked length`
var candidates = new Dictionary<int, int>();
// iteration index
var index = 0;
// so, lets the magic begin
foreach (var value in source)
{
// check candidates
foreach (var candidate in candidates.Keys.ToArray()) // <- we are going to change this collection
{
var checkedLength = candidates[candidate];
if (value == pattern[checkedLength]) // <- here `checkedLength` is used in sense `nextPositionToCheck`
{
// candidate has match next value
checkedLength += 1;
// check if we are done here
if (checkedLength == pattern.Length) return candidate; // <- exit point
candidates[candidate] = checkedLength;
}
else
// candidate has failed
candidates.Remove(candidate);
}
// check for new candidate
if (value == pattern[0])
candidates.Add(index, 1);
index++;
}
// we did everything we could
return -1;
}
We use dictionary of candidates to handle situations like:
var first = new List<string> { "AAA","BBB","CCC","CCC","CCC","CCC","EEE","FFF" };
var second = new List<string> { "CCC","CCC","CCC","EEE" };
If you are willing to use MoreLinq then consider using Window:
var windows = first.Window(second.Count);
var result = windows
.Select((subset, index) => new { subset, index = (int?)index })
.Where(z => Enumerable.SequenceEqual(second, z.subset))
.Select(z => z.index)
.FirstOrDefault();
Console.WriteLine(result);
Console.ReadLine();
Window will allow you to look at 'slices' of the data in chunks (based on the length of your second list). Then SequenceEqual can be used to see if the slice is equal to second. If it is, the index can be returned. If it doesn't find a match, null will be returned.
Implemented SomeMagic method as below, this will return -1 if no match found, else it will return the index of start element in first list.
private int SomeMagic(List<string> first, List<string> second)
{
if (first.Count < second.Count)
{
return -1;
}
for (int i = 0; i <= first.Count - second.Count; i++)
{
List<string> partialFirst = first.GetRange(i, second.Count);
if (Enumerable.SequenceEqual(partialFirst, second))
return i;
}
return -1;
}
you can use intersect extension method using the namepace System.Linq
var CommonList = Listfirst.Intersect(Listsecond)
I have an array of several objects, they have a isClosed bool property. I would like to know how to determine:
if all values are true
if only one of these values false
using Linq.
You can paraphrase your questions like this:
I would like to know how to determine:
If count of false = 0
If count of false = 1
You can simply use LINQ Count:
switch (collection.Count(x => !x.isClosed))
{
case 0:
// case 1, all values are true
break;
case 1:
// case 2, exactly one of these values is false
break;
default:
// other cases, more than 1 false value
break;
}
With this approach you will iterate through your collection only once.
List<Item> items = new List<Item>()
{
new Item() { IsClosed = true },
new Item() { IsClosed = true },
new Item() { IsClosed = true }
};
var allValuesAreTrue = items.All(it => it.IsClosed);
var onlyOneValueIsTrue = items.Count(it => it.IsClosed) == 1;
Use Linq .all like
It tells you if all the elements in a collection match a certain condition. It is part of the System.Linq namespace in the .NET Framework. It returns true or false.
if (array.All(element => element.isClosed))
{
return true;
}
var count = arr.Count(a => a.isClosed);
if(count == arr.Length)
//all
else if(count == 1)
//only one
else
//not all, but more than one
You can achieve both your desired requirements using the code below :
var list = new List<MyClass>();
var myClass1 = new MyClass {IsClosed = true};
var myClass2 = new MyClass { IsClosed = true };
var myClass3 = new MyClass { IsClosed = true };
list.Add(myClass1);
list.Add(myClass2);
list.Add(myClass3);
var response=list.All(x => x.IsClosed);
It returns true if all elements of your list have the same value.
I want to add one by one values but in for loop how can I iterate
through one by one values and add it inside dictionary.
IEnumerable<Customer> items = new Customer[]
{
new Customer { Name = "test1", Id = 111},
new Customer { Name = "test2", Id = 222}
};
I want to add { Name = "test1", Id = 111} when i=0
and want to add { Name = "test2", Id = 222} when i=1 n so on..
Right now i'm adding full collection in every key.(want to achieve this using foreach or forloop)
public async void Set(IEnumerable collection)
{
RedisDictionary<object,IEnumerable <T>> dictionary = new RedisDictionary>(Settings, typeof(T).Name);
// Add collection to dictionary;
for (int i = 0; i < collection.Count(); i++)
{
await dictionary.Set(new[] { new KeyValuePair<object,IEnumerable <T> ( i ,collection) });
}
}
If the count is need and the IEnumerable is to be maintained, then you can try this:
int count = 0;
var enumeratedCollection = collection.GetEnumerator();
while(enumeratedCollection.MoveNext())
{
count++;
await dictionary.Set(new[] { new KeyValuePair<object,T>( count,enumeratedCollection.Current) });
}
New version
var dictionary = items.Zip(Enumerable.Range(1, int.MaxValue - 1), (o, i) => new { Index = i, Customer = (object)o });
By the way, dictionary is a bad name for some variable.
I'm done using
string propertyName = "Id";
Type type = typeof(T);
var prop = type.GetProperty(propertyName);
foreach (var item in collection)
{
await dictionary.Set(new[] { new KeyValuePair<object, T>(prop.GetValue(item, null),item) });
}
So you want to a an item from the collection to the dictionary in the for loop?
If you cast your IEnumerable to a list or an array, you can easily access it via the index. For example like this:
Edit: Code at first created a list every time it looped, which should of course be avoided.
var list = collection.ToList(); //ToArray() also possible
for (int i = 0; i < list.Count(); i++)
{
dictionary.Add(i, list[i]);
}
I'm not 100% if that is what you need, though. More details to your question would be great.
So i have two lists .One is an Attorney object list and the other is a list with GUID .I am first just filling the GUID list with values and then looping through it and then matching it to the ID field of Attorney object to get the Attorney's with the given ID .
Is there a nicer way than my try to achieve this.
List<Attorney> Attorneys = msg.CaseDocument.Attorneys;
List<Attorney> CaseDefendantAtt = new List<Attorney>();
List<Guid> AttorneyID = new List<Guid>();
foreach (var s in msg.CaseDocument.Defendants)
{
AttorneyID.AddRange(s.Attorneys);
}
foreach (var p in AttorneyID)
{
var z = Attorneys.FindAll(o => o.AttorneyId == p);
if (z != null)
{
CaseDefendantAtt.AddRange(z);
}
}
How about...
var caseDefendantAtt = msg.CaseDocument.Attorneys.Where(o =>
msg.CaseDocument.Defendants.SelectMany(d => d.Attorneys).Contains(o.AttorneyId));
Try this
var allAttorneyIds = msg.CaseDocument.Defendants.SelectMany(x=> x.Attroneys);
var caseDefendantsAtt = msg.CaseDocument.Attorneys.Where(x=> allAttorneyIds.Contains(x.AttorneyId)).ToList();
Maybe you could store your Attorneys in a Dictionary of [Guid, Attorney], then looking for each Guid in Dictionary.
Dictionary<Guid, Attorney> Attorneys = CreateDictionaryFrom(msg.CaseDocument.Attorneys);
List<Attorney> CaseDefendantAtt = new List<Attorney>();
List<Guid> AttorneyID = new List<Guid>();
foreach (var s in msg.CaseDocument.Defendants)
{
AttorneyID.AddRange(s.Attorneys);
}
foreach (var p in AttorneyID)
{
var z = Attorneys[p];
if (z != null)
{
CaseDefendantAtt.Add(z);
}
}
It will increase the performance with the x1000 factor. But you should notice that we assume there is only one attorney per Guid.
Without using a dictionary, we could improve your search in list seeking for FirstOrDefault instead of FindAll
is there any way to write this foreach in linq or another better way,
int itemNr = -1;
foreach(ItemDoc itemDoc in handOverDoc.Assignment.Items) {
itemNr++;
foreach(ItemDetailDoc detail in itemDoc.Details) {
int eventDocNr = -1;
foreach(EventDoc eventDoc in detail.Events) {
eventDocNr++;
if(!eventDoc.HasEAN) {
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = itemDoc.Name;
lbl_ArtNr.Text = itemDoc.Number;
lbl_unitDesc.Text = eventDoc.Description;
m_tempItemNr = itemNr;
m_tempEventNr = eventDocNr;
txt_EAN.Focus();
return;
}
}
}
}
I just think this is not the correct way to write it. please advise.
If itemNr and eventDocNr is not needed you could use:
var item =
(from itemDoc in handOverDoc.Assignment.Items
from detail in itemDoc.Details
from eventDoc in detail.Events
where !eventDoc.HasEAN
select new
{
Name = itemDoc.Name,
Number = itemDoc.Number,
Description = eventDoc.Description
}).FirstOrDefault();
if (item != null)
{
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = item.Name;
lbl_ArtNr.Text = item.Number;
lbl_unitDesc.Text = item.Description;
txt_EAN.Focus();
}
No, I dont think there is a better way to do that. LINQ is about queries, you do quite a lot of processing in there. Unless you have a shortcut that is not obvious here.... this seems t obe the only way.
If you COULD start from the eventDoc - you could filter out those without EAN and then go from there backward, but Ican not say how feasible that is as I miss the complete model (as in: maybe you have no back lniks, so you would be stuck wit hthe eventDoc an dcoul dnot get up to the item.
First look that looks fine.
You could try the following LINQ:
var nonEANs = from ItemDoc itemDocs in itemDocList
from ItemDetailDoc itemDetailDocs in itemDocs.Details
from EventDoc eventDocs in itemDetailDocs.Events
where !eventDocs.HasEAN
select eventDocs;
foreach (var i in nonEANs)
{
System.Diagnostics.Debug.WriteLine( i.HasEAN);
}
Should return 7 false EANs: I recreated you nested structures like this
List<ItemDoc> itemDocList = new List<ItemDoc>()
{
new ItemDoc()
{
Details = new List<ItemDetailDoc>()
{
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=false},
new EventDoc()
{HasEAN=false}
}
},
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=true},
new EventDoc()
{HasEAN=false}
}
}
}
},
new ItemDoc()
{
Details = new List<ItemDetailDoc>()
{
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=false},
new EventDoc()
{HasEAN=false}
}
},
new ItemDetailDoc()
{
Events = new List<EventDoc>()
{
new EventDoc()
{HasEAN=false},
new EventDoc()
{HasEAN=false}
}
}
}
}
};
I think you are stuck with the for each loops as you need the itemNr and eventDocNr. You can use for loops to avoid increasing the itemNr and eventDocNr, but this does not reduce the number of loops.
Edit:
And if you do need the itemNr and eventDocNr try this:
var query = handOverDoc.Assignment.Items
.SelectMany(
(x, i) => x.Details.SelectMany(
(d, di) => d.Events.Where(x => x.HasEAN).Select(
(e, ei) => new {
ItemIndex = di,
EventIndex = ei,
Detail = d,
Event = e
}
)
)
);
foreach (var eventInfo in query) {
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = eventInfo.Detail.Name;
lbl_ArtNr.Text = eventInfo.Detail.Number;
lbl_unitDesc.Text = eventInfo.Event.Description;
txt_EAN.Focus();
return;
}
If you need only the first event with an EAN you could also use the following on the above query:
var item = query.FirstOrDefault();
if (item != null) {
// do you stuff here
}
You can get the index in LINQ quite easily, for example :-
var itemDocs = handOverDoc.Assignment.Items.Select((h, i) => new { item = h, itemindex = i })
You can repeat this process for your inner loops also and I suspect you could then use SelectMany() to simplify it even further.
You're trying to do two different things here. Firstly you're trying to find a document, and secondly you're trying to change things based upon it. The first stage in the process is simply to clarify the code you already have, e.g.
(Note this takes into account previous comments that the computed indexes in the original code are not needed. The exact same type of split into two methods could be done whether or not the computed indexes are required, and it would still improve the original code.)
public void FindAndDisplayEventDocWithoutEAN(HandOverDoc handOverDoc)
{
var eventDoc = FindEventDocWithoutEAN(handOverDoc);
if (eventDoc != null)
{
Display(eventDoc);
}
}
public EventDoc FindEventDocWithoutEAN(HandOverDoc handOverDoc)
{
foreach(ItemDoc itemDoc in handOverDoc.Assignment.Items)
foreach(ItemDetailDoc detail in itemDoc.Details)
foreach(EventDoc eventDoc in detail.Events)
if(!eventDoc.HasEAN)
return eventDoc;
return null;
}
public void Display(EventDoc eventDoc)
{
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = itemDoc.Name;
lbl_ArtNr.Text = itemDoc.Number;
lbl_unitDesc.Text = eventDoc.Description;
m_tempItemNr = itemNr;
m_tempEventNr = eventDocNr;
txt_EAN.Focus();
}
Once you've done that, you should be able to see that one method is a query over the main document, and the other is a method to display the results of the query. This is what's known as the single responsibility principle, where each method does one thing, and is named after what it does.
The transformation of the nested foreach loops to a linq query is now almost trivial. Compare the method below with the method above, and you can see how mechanical it is to translate nested foreach loops into a linq query.
public EventDoc FindEventDocWithoutEAN(HandOverDoc handOverDoc)
{
return (from itemDoc in handOverDoc.Assignment.Items
from detail in itemDoc.Details
from eventDoc in detail.Events
where !eventDoc.HasEAN
select eventDoc).FirstOrDefault();
}
yet another spin...
var query = from itemDocVI in handOverDoc.Assignment
.Items
.Select((v, i) => new { v, i })
let itemDoc = itemDocVI.v
let itemNr = itemDocVI.i
from detail in itemDoc.Details
from eventDocVI in detail.Events
.Select((v, i) => new { v, i })
let eventDoc = eventDocVI.v
let eventDocNr = eventDocVI.i
where eventDoc.HasEAN
select new
{
itemDoc,
itemNr,
detail,
eventDoc,
eventDocNr
};
var item = query.FirstOrDefault();
if (item != null)
{
HideShowPanels(pMatch);
txt_EAN.Text = String.Empty;
lbl_Match_ArtName.Text = item.itemDoc.Name;
lbl_ArtNr.Text = item.itemDoc.Number;
lbl_unitDesc.Text = item.eventDoc.Description;
m_tempItemNr = item.itemNr;
m_tempEventNr = item.eventDocNr;
txt_EAN.Focus();
}