Remove rows and adjust number - c#

I am trying to remove objects from a list where a certain property value are identical to the previous/next objects property value. If an object are found I need to update the nested objects value.
Example:
Level | Text
1 | General
2 | Equipment
3 | Field Staff
2 | Scheduling
3 | Scheduling
4 | Deadlines
4 | Windows
1 | Specialities
In the example above I want to remove the second Scheduling and change the Deadlines Level to 3 as well as the Windows to 3.
I tried to look a head and compare with the next object in the list and also keep a counter but it didnt work.
int counter = 0;
for (int i = 0; i < notes.Count(); i++)
{
if (i <= notes.Count() - 1)
{
var currentRow = notes.ElementAt(i);
var nextRow = notes.ElementAt(i + 1);
if (currentRow.Text.Equals(nextRow.Text))
{
notes.Remove(nextRow);
counter++;
}
else
{
notes.ElementAt(i).Level = notes.ElementAt(i).Level - counter;
counter = 0;
}
}
}
Could anyone point me in the correct direction?

You can do it with Linq:
1 - Get distinct lines
var distinctList = notes
.GroupBy(p => p.Text)
.Select(v => v.First());
2 - get deleted level
IEnumerable<int> deletedLevel = notes
.Except(distinctList)
.Select(l => l.Level);
3 - update your distinct list
foreach(int level in deletedLevel)
{
distinctList
.Where(l => l.Level >= level + 1)
.ToList()
.ForEach(item => { item.Level -= 1; });
}
Result :
Level | Text
1 | General
2 | Equipment
3 | Field Staff
2 | Scheduling
3 | Deadlines
3 | Windows
1 | Specialities
i hope that will help you out

Try this:
var query = notesList.GroupBy(x => x.Text)
.Where(g => g.Count() > 1)
.Select(y => y.Key)
.Select(y => new { Element = y, Index = Array.FindIndex<Notes>(notesList.ToArray(), t => t.Text ==y) })
.ToList();
var filteredList = new List<Notes>();
foreach (var duplicate in query)
{
filteredList = notesList.Where((n, index) => index < duplicate.Index + 1).ToList();
var newElems = notesList.Where((n, index) => index > duplicate.Index + 1).Select(t =>
new Notes {Level = t.Level == 1 ? 1 : t.Level - 1, Text = t.Text});
filteredList.AddRange(newElems);
}

Related

Delete duplicates within a single collection c#

I'm new to C#, here is my problem
My expected result is removing the entire row of ABC.
Both rows (with duplicate ABC) will be removed.
I need to do it the iterative way. Can't use distinct and stuff as recommended by the other post.
I tried to remove duplicates but it didn't work.
So i decided to add the non-duplicates to a new collection.
But it isn't working as well.
CollectionIn --> My sample collection
| Folder| Times
------------------------
| ABC | 3 |
| CDE | 2 |
| ACD | 2 |
| ABC | 1 |
CollectionOut = new DataTable();
CollectionOut.Columns.Add("Folder");
CollectionOut.Columns.Add("Times");
bool duplicate = false;
for (int i = 0; i < CollectionIn.Rows.Count; i++)
{
string value1 = CollectionIn.Rows[i].ItemArray[0].ToString().ToLower();
for (int z = 0; z < i; z++)
{
string value2 = CollectionIn.Rows[z].ItemArray[0].ToString().ToLower();
if (value1 == value2)
{
duplicate = true;
break;
}
}
if (!duplicate)
{
CollectionOut.Rows.Add(value1);
}
}
Can anyone help to take a look. Thanks!
Since you dont want to use Distinct, you cant do it with LINQ like:
var newList = myList.GroupBy(s=>s).Where(s => s.Count() == 1).ToList();
I would use Linq-To-DataTable:
List<DataRow> duplicates = CollectionIn.AsEnumerable()
.GroupBy(r => r.Field<string>("Folder"))
.Where(g => g.Count() > 1)
.SelectMany(grp => grp)
.ToList();
duplicates.ForEach(CollectionIn.Rows.Remove);
This will remove the duplicates from the original collection(DataTable) without creating a new.

Linq Lambda : Getting last user updated with group of same user using group by and count?

I want to get last user updated with a linq lambda expression using group by and the count of the remaining users. I don't know how I can do that.
here is my data :
userid | name | datetime | isdelete
1 | abc | 16-03-2017 15:45:59 | 0
1 | abc | 16-03-2017 12:45:10 | 0
2 | xyz | 16-03-2017 15:45:59 | 0
1 | abc | 16-03-2017 10:40:59 | 0
I want the result to look like this:
userid | name | datetime | count
1 | abc | 16-03-2017 15:45:59 | 3
2 | xyz | 16-03-2017 15:45:59 | 1
Here the count for userid = 1 should be 3 as there are three records for that id in the table.
I have written this query, but it is getting all the records.
List<Users> UList = new List<Users>();
UList = db.Users.Where(a => a.isdelete == false)
.OrderByDescending(a => a.datetime)
.Skip(skip)
.Take(pageSize)
.ToList();
Anyone know how I can get the data I want? Please let me know using linq lambda expression.
You need to group by user, than sort each group and take first from each group
var UList = (db.Users
.Where(a => a.isdelete == false)
.GroupBy(a => a.UserId)
.Select(g => new MyNewClass
{
Count = g.Count(),
User = g.OrderByDescending(a => a.datetime).First()
}
))
.Skip(skip)
.Take(pageSize)
.ToList();
You forgot to group your data:
var result = db.Users.Where(a => !a.isdelete)
.GroupBy(x => x.userid)
.Select(x => new User
{
userid = x.Key,
name = x.Last().Name,
datetime = x.OrderByDescending(a => a.datetime).First().datetime,
count = x.Count()
});
EDIT: This might be not optimal considering the performance as the call to Last and OrderByAscending will both iterate the whole data. To overcome this a bit you may re-structure this query a bit:
var result = db.Users.Where(a => !a.isdelete)
.GroupBy(x => x.userid)
.Select(x => new
{
user = x.OrderByDescending(a => a.datetime).First(),
count = x.Count()
})
.Select(x => new User {
name = x.user.name,
userid = x.user.userid,
datetime = x.user.datetime,
count = x.count
});

Linq select all items where in list with groupby

I have the following data as a list:
raceId data position
1 A 0
1 B 0
1 F 1
1 J 0
2 A 2
2 F 1
3 A 0
3 J 2
3 M 1
3 V 3
I need to get the total (count) of races where there are ALL matching letters with the same raceid.
I.E a search on 'A' and 'J' = 2 (race's 1 and 3)
In addition I need to get the position data for each.
raceId data position
1 A 0
1 J 0
3 A 0
3 J 2
So far I have the following code.
var dataValues = new string[] { 'A', 'J' };
var races = raceData
.GroupBy( ac => ac.raceId )
.Select( grp => grp.First() )
.Where( t =>
dataValues
.All( s =>
dataValues
.Contains( t.data )
)
);
var racecount = races.count()
The issue is that this returns all raceId values where there is either letter in the data.
This should work for you:
var results = raceData.GroupBy(rd => rd.raceId)
.Where(g => dataValues.All(dv => g.Select(g2 => g2.data).Contains(dv)));
int raceCount = results.Count();
var results2 = results
.SelectMany(g => g)
.Where(rd => dataValues.Contains(rd.data));
raceCount will give you 2 and results2 will give you the 4 records you're expecting.
It works for me with your provided data anyway!
var groupedRaces = from r in raceData
group r by r.raceId into gp
select new { raceId = gp.Key, Datas = gp.Select(g => g.data).ToArray() };
var raceIds = from r in groupedRaces
where dataVals.All(mv => r.Datas.Contains(mv))
select r.raceId;
var races = from r in raceData
where raceIds.Contains(r.raceId) && dataVals.Contains(r.data)
select r;
Try this,
list.GroupBy(t => t.raceID).SelectMany(k => k).Where(x => dataValues.Contains(x.data))
.Select(f=> new { f.data ,f.position,f.raceID}).ToList();
Result,
Hope helps,

LINQ - Grouped list with condition

Read How to use Linq to group every N number of rows already
But i would like to know further
Suppose a list contains 22 ITEM01 with quantity 10 for each, and 2 ITEM02 with quantity 50 for each
# |ITEM |QUANTITY
==================
1 |ITEM01| 10
2 |ITEM01| 10
3 |ITEM01| 10
. . .
. . .
22|ITEM01| 10
23|ITEM02| 50
24|ITEM02| 50
How to get the result like : (If count > 10, go to next row)
ITEM |QUANTITY
=================
ITEM01 | 100
ITEM01 | 100
ITEM01 | 10
ITEM01 | 10
ITEM01 | 10
ITEM02 | 50
ITEM02 | 50
Thanks for your help!
Check comments within code to see what's going on and what the query really does.
// input generation
var input = Enumerable.Range(1, 22)
.Select(x => new { ID = x, Item = "ITEM01", Quantity = 10 })
.Concat(Enumerable.Range(23, 2)
.Select(x => new { ID = x, Item = "ITEM02", Quantity = 50 }));
// query you're asking for
var output =
input.GroupBy(x => x.Item) // group by Item first
.Select(g => g.Select((v, i) => new { v, i }) // select indexes within group
.GroupBy(x => x.i / 10) // group items from group by index / 10
.Select(g2 => g2.Select(x => x.v)) // skip the indexes as we don't need them anymore
.SelectMany(g2 => // flatten second grouping results and apply Sum logic
g2.Count() == 10
// if there are 10 items in group return only one item with Quantity sum
? new[] { new { Item = g.Key, Quantity = g2.Sum(x => x.Quantity) } }
// if less than 10 items in group return the items as they are
: g2.Select(x => new { Item = g.Key, Quantity = x.Quantity })))
.SelectMany(g => g); // flatten all results

Efficient way to merge table like datastructure using linq

Allow me to present you with my atrocious logic first:
public void MergeLotDataList(List<SPCMeasureData> sPCMeasureDataList)
{
double standMaxTotal = 0.0;
double standAimTotal = 0.0;
double standMinTotal = 0.0;
List<SPCLotData> lotDataRemovalList = new List<SPCLotData>();
foreach (SPCLotData lotData in sPCLotDataList)
{
//Find if there's any lotDatas with duplicate identify strings
var duplicateLotList = sPCLotDataList.Where(w => w.GetIdentifyString() == lotData.GetIdentifyString()).Select(s=>s);
int duplicateLotCount = duplicateLotList.Count();
if (duplicateLotCount <= 1)
continue;
//Get the standMax,standAim,standMin total for computing average later
//and remove duplicates, leaving only a single unique lotData
foreach (SPCLotData lotData_inner in duplicateLotList)
{
standMaxTotal += lotData_inner.GetStandMax();
standAimTotal += lotData_inner.GetStandAim();
standMinTotal += lotData_inner.GetStandMin());
if (lotData_inner != lotData)
lotDataRemovalList.Add(lotData_inner);
}
//Remove all duplicates
foreach (SPCLotData lotDataToRemove in lotDataRemovalList)
{
sPCLotDataList.Remove(lotDataToRemove);
}
lotDataRemovalList.Clear();
//Set the corresponding standdatas to average
lotData.SetStandData((standMaxTotal / duplicateLotCount),
(standAimTotal / duplicateLotCount),
(standMinTotal / duplicateLotCount);
standMaxTotal = 0.0;
standAimTotal = 0.0;
standMinTotal = 0.0;
}
}
Now that I've ensured that my code makes zero sense to everyone (and of course, doesn't work either because i'm modifying the container inside the foreach loop), let me explain what I'm trying to do.
So I have a datastructure like this:
identifyString standMax standAim standMin
-----------------------------------------
AA 3 4 5
AA 1 2 3
AA 1 2 4
AB 0 5 7
AC 3 4 5
The end result I'm trying to get is this:
identifyString standMax standAim standMin
-----------------------------------------
AA 2.5 2.667 4
AB 0 5 7
AC 3 4 5
Notice how the duplicate rows (with same identifyString) have been removed, and the uniquely remaining row have the values (standMax,aim,min) is updated as their average.
What's the most elegant way of achieving this?
You can use LINQ Enumerable.ToLookup and Enumerable.Average extention methods
Here is the what I mean:
var perIdentStrLookup = sPCMeasureDataList.ToLookup(k => k.GetIdentifyString());
foreach(var lk in perIdentStrLookup)
{
Console.WriteLine("identifyString={0}; standMax={1}; standAim={2}; standMin={1}",
lk.Key,//identifyString
lk.Average(l=>GetStandMax()),
lk.Average(l=>GetStandAim()),
lk.Average(l=>GetStandMin()),
)
}
or in case if you want unique list
var uniqueList = sPCMeasureDataList
.ToLookup(k => k.GetIdentifyString())
.Select(lk => new SPCLotData
{
IdentifyString = lk.Key,
StandMax = lk.Average(l=>GetStandMax()),
StandAim = lk.Average(l=>GetStandAim()),
StandMin = lk.Average(l=>GetStandMin())
})
.ToList()
You can use LINQ GroupBy:
var result = sPCLotDataList.GroupBy(x => x.identifyString)
.Select(g => new SPCLotData(){
identifyString = g.Key,
standMax = g.Average(x => x.standMax),
standAim = g.Average(x => x.standAim),
standMin = g.Average(x => x.standMin)
});
I assume sPCLotDataList is where you get the data?
In which case you could:
var result = from x in sPCLotDataList
group x by x.identifyString into grp
select new { identifyString = grp.key
standMax = grp.Average(c => c.standMax)
standAim = grp.Average(c => c.standAim)
standMin= grp.Average(c => c.standMin)
}

Categories