I have 2 list object of type of some class,
class person
{
public string id { get; set; }
public string name { get; set; }
}
List<person> pr = new List<person>();
pr.Add(new person { id = "2", name = "rezoan" });
pr.Add(new person { id = "5", name = "marman" });
pr.Add(new person { id = "3", name = "prithibi" });
List<person> tem = new List<person>();
tem.Add(new person { id = "1", name = "rezoan" });
tem.Add(new person { id = "2", name = "marman" });
tem.Add(new person { id = "1", name = "reja" });
tem.Add(new person { id = "3", name = "prithibi" });
tem.Add(new person { id = "3", name = "prithibi" });
Now i have to get all the ids from "pr" ListObject that has no entry or odd number of entries in the "tem" ListObejct. using lamda.
To do this i have used,
HashSet<string> inconsistantIDs = new HashSet<string>(pr.Select(p => p.id).Where(p => tem.FindAll(t => t.id == p).Count == 0 || tem.FindAll(t => t.id == p).Count % 2 != 0));
and it works fine.
but you can see from the code i have used tem.FindAll(t => t.id == p).Count twice to comapre with ==0 and %2!=0.
Is there any way to use tem.FindAll(t => t.id == p).Count once and
save it to a temporary variable and then compare this variable with
==0 and %2!=0.
More simply i just want to use it once for two condition here.
Use a statement lambda instead of an expression lambda
var inconsistantIDs = new HashSet<string>(
pr.Select(p => p.id).Where(p =>
{
var count = tem.FindAll(t => t.id == p).Count;
return count == 0 || count % 2 != 0;
}
));
Perhaps simply:
var query = pr.Where(p => { int c = tem.Count(p2 => p.id == p2.id); return c == 0 || c % 2 != 0; });
returns two persons:
2 "rezoan"
5 "marman"
Besides statement lambda you can use let clause:
HashSet<string> inconsistantIDs = new HashSet<string>(
from p in pr
let count = tem.FindAll(t => t.id == p).Count
where count == 0 || count % 2 != 0
select p.id
);
HashSet<string> inconsistantIDs = new HashSet<string>(
pr.Select(p => new { Id = p.id, Cnt = tem.FindAll(t => t.id == p.id).Count() })
.Where(p => p.Cnt == 0 || p.Cnt % 2 != 0)
.Select(p => p.Id);
On a side note, strictly performance wise, you would get better performance if you created a hash mapping of each ID to its count and then search it in a loop.
Right now you have a O(n*m) algorithm, which would be reduced to O(n+m):
// create a map (id -> count), O(m) operation
var dictionary = new Dictionary<string, int>();
foreach (var p in tem)
{
var counter = 0;
dictionary.TryGetValue(p.id, out counter);
counter++;
dictionary[p.id] = counter;
}
// search the map, O(n) operation
var results = new HashSet<string>();
foreach (var p in pr)
{
var counter = 0;
dictionary.TryGetValue(p.id, out counter);
if (counter == 0 || counter % 2 != 0)
results.Add(p.id);
}
Related
Combine multiple lists into one list and order by amount
I have a class like this
public class PriceList
{
public string Name { get; set; }
public int Amount { get; set; }
public int Price { get; set; }
}
First list. It's called VOne
Name Amount Price
P5 5000 6
P10 10000 10
P20 20000 20
Second list. It's called VTwo
Name Amount Price
P5 5000 5
P10 10000 10
P15 15000 15
P20 20000 21
Third list. It's called VThree
Name Amount Price
P1 1000 1
P10 10000 9
P20 20000 19
I want the result like this
Name Amount VOne VTwo VThree
P1 1000 0 0 1
P5 5000 6 5 0
P10 10000 10 10 9
P15 15000 0 15 0
P20 20000 20 21 19
This is what I try. It works but seems sophisticated. I need the simpler way to do this. Linq will be prefered but I have no idea how to use it.
static void Test()
{
var VOne = new List<PriceList>(new[]
{
new PriceList { Name = "P5", Amount = 5000, Price = 6},
new PriceList { Name = "P10", Amount = 10000 , Price = 10},
new PriceList { Name = "P20", Amount = 20000, Price = 20}
});
var VTwo = new List<PriceList>(new[]
{
new PriceList { Name = "P5", Amount = 5000, Price = 5},
new PriceList { Name = "P10", Amount = 10000 , Price = 10},
new PriceList { Name = "P15", Amount = 10000 , Price = 15},
new PriceList { Name = "P20", Amount = 20000, Price = 21}
});
var VThree = new List<PriceList>(new[]
{
new PriceList { Name = "P1", Amount = 5000, Price = 1},
new PriceList { Name = "P10", Amount = 10000 , Price = 9},
new PriceList { Name = "P20", Amount = 20000, Price = 19}
});
var prices = new List<PriceListResult>();
foreach (var m in VOne)
{
var exist = prices.Any(x => x.Name == m.Name && x.Amount == m.Amount);
if (!exist)
prices.Add(new PriceListResult { Name = m.Name, Amount = m.Amount });
}
foreach (var m in VTwo)
{
var exist = prices.Any(x => x.Name == m.Name && x.Amount == m.Amount);
if (!exist)
prices.Add(new PriceListResult { Name = m.Name, Amount = m.Amount });
}
foreach (var m in VThree)
{
var exist = prices.Any(x => x.Name == m.Name && x.Amount == m.Amount);
if (!exist)
prices.Add(new PriceListResult { Name = m.Name, Amount = m.Amount });
}
prices = prices.OrderBy(x => x.Name).ThenBy(x => x.Amount).ToList();
foreach (var price in prices)
{
var v1 = VOne.FirstOrDefault(x => x.Name == price.Name && x.Amount == price.Amount);
if (v1 != null)
price.VOne = v1.Price;
var v2 = VTwo.FirstOrDefault(x => x.Name == price.Name && x.Amount == price.Amount);
if (v2 != null)
price.VTwo = v2.Price;
var v3 = VThree.FirstOrDefault(x => x.Name == price.Name && x.Amount == price.Amount);
if (v3 != null)
price.VThree = v3.Price;
}
}
public class PriceListResult
{
public string Name { get; set; }
public int Amount { get; set; }
public int VOne { get; set; }
public int VTwo { get; set; }
public int VThree { get; set; }
}
My advice would be that you spend some time get acquainted to the standard LINQ methods
You can use Enumerable.Concat to put your three sequences into one sequence, then you can use one of the overloads of Enumerable.GroupBy to make groups of PriceLists that have the same amount. User parameter resultSelector to create one object per Amount and PriceLists with this Amount.
The problem is, that if you've got a PriceList, you can't see whether it is a PriceList of vOne, vTwo or vThree. So from every Price we'll have to remember the Amount, the Price and from which priceList the data originated.
I'll do it in small steps, so it is easy to see what is done. If you want, you can put it in one big LINQ. As all statements use delayed execution, this will not improve performance, however one big LINQ will deteriorate readability.
var vOne = VOne.Select(priceList => new
{
Id = 1,
Name = priceList.Name,
Amount = priceList.Amount,
Price = priceList.Price,
});
var vTwo = Select(priceList => new
{
Id = 2,
Name = priceList.Name,
Amount = priceList.Amount,
Price = priceList.Price,
});
vThree = Select(priceList => new
{
Id = 3,
Name = priceList.Name,
Amount = priceList.Amount,
Price = priceList.Price,
});
var allPriceLists = vOne.Concat(vTwo).Concat(vThree);
Make groups of sequences that have the same value for the combination [Name, Amount]. Use parameter resultSelector to create one object per group
var result = allPriceLists.GroupBy(priceList => new {priceList.Name, priceList.Amount},
// parameter resultSelector: use every [Name, Amount] combination,
// with all priceLists that have this combination to make one new:
(nameAmountCombinatin, priceListsWithThisCombination) => new
{
Name = nameAmountCombination.Name,
Amount = nameAmountCombination.Amount,
Vone = priceListsWithThisCombination
.Where(priceList => priceList.Id == 1)
.Select(priceList => priceList.Amount)
.FirstOrDefault(),
Vtwo = priceListsWithThisCombination
.Where(priceList => priceList.Id == 2)
.Select(priceList => priceList.Amount)
.FirstOrDefault(),
Vthree = priceListsWithThisCombination
.Where(priceList => priceList.Id == 3)
.Select(priceList => priceList.Amount)
.FirstOrDefault(),
})
Note: I assume that in Vone (etc.) doesn't have two PriceLists with the same [name, amount] combination. If you allow that Vone has two elements with [P5, 5000], consider to Sum the Amounts
If there is a missing Amount in one of the original PriceLists, you will get the default value for Price: 0.
make class like this:
public class PriceList
{
public string Name { get; set; }
public int Amount { get; set; }
public int Price { get; set; }
public string type {get;set;}
}
Then:
var list1Typed = List1.ForEach(f => f.type = "VOne");
var list2Typed = List2.ForEach(f => f.type = "VTwo");
var list3Typed = List3.ForEach(f => f.type = "VThree");
var all = list1Typed.Union(list2Typed).Union(list3Typed).ToList();
var allComplete = all.GroupBy(g => g.Name).Select(s =>
new PriceListResult(){
Name = g.Key,
Amount = g.First().Amount,
VOne = g.Any(a => a.Type == "VOne") ? g.Where(w => w.Type =="VOne").Sum(s => s.Amount) : 0,
VTwo = g.Any(a => a.Type == "VTwo ") ? g.Where(w => w.Type =="VTwo").Sum(s => s.Amount) : 0,
VThree = g.Any(a => a.Type == "VThree") ? g.Where(w => w.Type =="VThree").Sum(s => s.Amount) : 0
}).OrderBy(o => o.Amount).ToList();
Try this:
var all = list1.Union(list2).Union(list3).ToList();
then:
var grouped = all.GroupBy(p => new {
p.Name,
p.Amount
});
finally:
var results = grouped.Select(p => new {
p.Name,
p.Amount,
Price = string.Join(", ", p.Select(x => x.Price))
}).ToList().OrderBy(item => item.Amount);
var finalResult = results.Select(item => new PriceListResult {
Name = item.Name,
Amount = item.Amount ,
VOne = Price.Split(new[] { ','},StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray()[0],
VTwo = Price.Split(new[] { ','},StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray()[1],
VThree = Price.Split(new[] { ','},StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray()[2]
}).ToList();
I've edited Kai's answer and it works
var results = list1.Union(list2).Union(list3)
.GroupBy
(
p => p.Name,
p => p,
(key, list) => new { Name = key, Amount = list.First().Amount, List = list }
)
.Select(s => new
{
Name = s.Name,
Amount = s.Amount,
VOne = list1.Where(x => x.Name == s.Name && x.Amount == x.Amount).Select(x => x.Price).FirstOrDefault(),
VTwo = list2.Where(x => x.Name == s.Name && x.Amount == x.Amount).Select(x => x.Price).FirstOrDefault(),
VThree = list3.Where(x => x.Name == s.Name && x.Amount == x.Amount).Select(x => x.Price).FirstOrDefault()
})
.OrderBy(o => o.Amount).ToList();
I have a list of Products, with the Price. I would like to get the the cheapest one only if it is unique. If there are more than one Product with the same lowest price, it should not return any.
In the sample below, for the uniqProductList the query should return the BestOne while for the dupProductList, no product should be returned.
How do I write the Linq query ?
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime ExpiryDate { get; set; }
}
List<Product> uniqProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
new Product { Name = "BestOne", Price = 9.99M }
};
List<Product> dupProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
};
This is one way if you want to do it in a single query:
Product result = uniqProductList
.GroupBy(x => x.Price)
.OrderBy(x => x.Key)
.Take(1)
.FirstOrDefault(x => x.Count() == 1)?
.FirstOrDefault();
Group the results by price
Order by price so that the cheapest is the first result
Take the first result, since we aren't interested in the other ones
Return the grouping if there is only one result in the group
Return the value
There's almost certainly some other method that is faster, though.
You are looking for ArgMax which is not included into standard Linq but can be implemented manually with a help of Aggregate. Having a collection of the cheapest Products we can return null if we have more than 1 of them:
using System.Linq;
...
List<Product> source = ...
var bests = source
.Aggregate(new List<Product>(), (s, a) => {
if (s.Count <= 0 || s[0].Price == a.Price)
s.Add(a);
else if (a.Price <= s[0].Price) {
s.Clear();
s.Add(a);
}
return s;
});
Product best = bests.Count == 1 ? bests[1] : default(Product);
You could group by the elements by their price and getting the cheapest group:
var cheapestGrp = uniqProductList.GroupBy(i => i.Price).OrderBy(i => i.Key).First();
Then, based on the number of elements of the group, return the only element or return nothing:
if (cheapestGrp.Count() > 1)
return null;
else
return cheapestGrp.ToList().First();
result = Products.GroupBy(x => x.Price)
.Select(g => new { g.Name, Count = g.Count()
})
.Orderby(s.Count)
.Select(x.Name, x.Count).FirstOrDefault();
if(result.Count == 1){
return result;
}
else{
return null;
}
I would suggest this solution:
public Product? TryGetBestOne(IEnumerable<Product> products)
{
var bestProducts = products
.GroupBy(x => x.Price)
.OrderBy(x => x.Key)
.FirstOrDefault()?
.ToArray() ?? Array.Empty<Product>();
return bestProducts.Count() == 1 ? bestProducts.Single() : null;
}
You can use GroupBy and then use Where to get items where there is just one Count and then just sort in ascending order:
var result = uniqProductList
.GroupBy(u => u.Price)
.Select(grp => new { grp.Key, Count = grp.Count(), Items = grp.ToList() })
.Where(s => s.Count == 1)
.OrderBy(o=> o.Key)
.FirstOrDefault();
An example:
List<Product> uniqProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
new Product { Name = "BestOne", Price = 9.99M }
};
List<Product> dupProductList = new List<Product>() {
new Product { Name = "GoodOne", Price = 12M },
new Product { Name = "NiceOne", Price = 12M },
new Product { Name = "ExpensiveOne", Price = 15M },
};
var result = uniqProductList
.GroupBy(u => u.Price)
.Select(grp => new { grp.Key, Count = grp.Count(), Items = grp.ToList() })
.Where(s => s.Count == 1)
.OrderBy(o=> o.Key)
.FirstOrDefault();
This is another solution :
var product = uniqProductList.OrderBy(a => a.Price)
.GroupBy(a => a.Price).FirstOrDefault()
.Aggregate(new List<Product>(), (result, item) =>
{
result.Add(item);
if (result.Count() > 1)
result = new List<Product>();
return result;
}).FirstOrDefault();
You can get the lowest price first, then you can group them.
I have List of object of class "Data" that look like:
class Data
{
int code;
string name;
...
DateTime date_update;
}
and I have another list of class, like:
class RefCodes
{
int old_code;
int new_code;
string new_name;
DateTime date_update;
}
The list of "Data" contains like 1,000 objects.
The list of "RefCodes" contains like 30 objects.
I need to replace in list "Data",
the fields:
"code" to be with value of "new_code",
and the "name" to be with value of "new_name".
The replacement need to be only for the objects that their code exist in list "RefCodes".
by the query: if code in Data.code == RefCodes.old_code
How can I do it?
I think you're looking for this:
foreach (var rcodeObj in RefCode)
{
foreach(var obj in (Data.Where(t => t.code == rcodeObj.old_code)))
{
obj.code = rcodeObj.new_code;
obj.name = rcodeObj.new_name;
}
}
If you are using C#6 you could use linq to do something like this
var updatedData = data.Select(x => new Data
{
code = refCodes.FirstOrDefault(y => y.old_code == x.code)?.new_code ?? x.code,
name = refCodes.FirstOrDefault(y => y.old_code == x.code)?.new_name ?? x.name,
});
You can use the following code:
foreach (var x in DataList)
{
var itemRefCode = RefCodesList.FirstOrDefault(d => d.old_code == x.code);
if (itemRefCode != null)
{
x.code = itemRefCode.new_code;
x.name = itemRefCode.new_name;
}
}
You can iterate through each of the lists and update the values as follows. Here I am using some sample inputs as shown below. Note that I am considering the fields of the classes to be public, for simplicity:
List<Data> dataList = new List<Data>
{
new Data { code = 1, name = "A" },
new Data { code = 2, name = "B" },
new Data { code = 10, name = "C" },
};
List<RefCodes> refList = new List<RefCodes>
{
new RefCodes { old_code = 1, new_code = 11, new_name = "X" },
new RefCodes { old_code = 2, new_code = 22, new_name = "Y" }
};
Console.WriteLine("Before");
dataList.ForEach(data => Console.WriteLine(data.code + ": " + data.name));
Console.WriteLine("");
Here is the code to do the updating:
foreach (var refCodes in refList)
{
foreach (var data in dataList)
{
if (data.code == refCodes.old_code)
{
data.code = refCodes.new_code;
data.name = refCodes.new_name;
}
}
}
Console.WriteLine("After");
dataList.ForEach(data => Console.WriteLine(data.code + ": " + data.name));
Output:
Before
1: A
2: B
10: C
After
11: X
22: Y
10: C
Would this solve your problem:
public void Update( List<Data> data, List<RefCodes> refCodes )
{
List<RefCodes> differences = refCodes
.Where( r => data.Any( d => r.old_code == d.code ) )
.ToList();
differences.ForEach( ( RefCodes item ) =>
{
Data element = data.FirstOrDefault( d => d.code == item.old_code );
element.code = item.new_code;
element.name = item.new_name;
} );
}
What you need is a Left Outer Join.
For example,
IEnumerable<Data> query = from data in dataList
join refCode in refList on data.code equals refCode.old_code into joined
from subCode in joined.DefaultIfEmpty()
select new Data
{
code = subCode?.new_code ?? data.code,
name = subCode?.new_name ?? data.name,
date_update = subCode == null ? data.date_update : DateTime.Now
};
will return a sequence with the result you expect.
**Let say tempAllocationR is list 1 and tempAllocationV is List2 **
var tempAllocation = new List<Object>();
if (tempAllocationR.Count > 0 && tempAllocationV.Count > 0)
{
foreach (TempAllocation tv in tempAllocationV)
{
var rec = tempAllocationR.FirstOrDefault(tr => tr.TERR_ID == tv.TERR_ID && tr.TERR == tv.TERR && tr.Team == tv.Team);
if (rec != null)
{
rec.Vyzulta = tv.Vyzulta;
}
else
{
tempAllocationR.Add(tv);
}
}
tempAllocation = tempAllocationR;
}
else if (tempAllocationV.Count == 0 && tempAllocationR.Count > 0)
{
tempAllocation = tempAllocationR;
}
else if (tempAllocationR.Count == 0 && tempAllocationV.Count > 0)
{
tempAllocation = tempAllocationV;
}
In the system I use modifications to data are received in pairs of rows old and new with a RowMod flag, for example deleted, added, updated and unchanged rows come through as:
RowID Data RowMod
Row1 "fish" ""
Row1 "fish" "D"
Row2 "cat" "A"
Row3 "fox" ""
Row3 "dog" "U"
Row4 "mouse" ""
I'd like to match these up using the RowID that each row has and get something like:
RowID OldData NewData RowMod
Row1 "fish" null "D"
Row2 null "cat" "A"
Row3 "fox" "dog" "U"
Row4 "mouse" "mouse" ""
class Program
{
static void Main(string[] args)
{
IEnumerable<DataRow> rows = new[]
{
new DataRow(1,"fish",""),
new DataRow(1,"fish","D"),
new DataRow(2,"cat","A"),
new DataRow(3,"fox",""),
new DataRow(3,"dog","U"),
new DataRow(4,"mouse","")
};
var result = rows
.GroupBy(x => x.Id)
.Select(g => new
{
Count = g.Count(),
Id = g.First().Id,
FirstRow = g.First(),
LastRow = g.Last()
}).Select(item => new
{
RowId = item.Id,
OldData = item.Count == 1 && item.FirstRow.RowMod != "" ? null : item.FirstRow.Data,
NewData = item.LastRow.RowMod == "D" ? null : item.LastRow.Data,
RowMod = item.LastRow.RowMod
});
//Or using query syntax
var result2 = from x in rows
orderby x.Id, x.RowMod
group x by x.Id into g
select new
{
RowId = g.First().Id,
OldData = g.Count() == 1 && g.First().RowMod != "" ? null : g.First().Data,
NewData = g.Last().RowMod == "D" ? null : g.Last().Data,
RowMod = g.Last().RowMod
};
// Test
Console.WriteLine("RowID\tOldData\tNewData\tRowMod");
foreach (var item in result)
{
Console.WriteLine("{0}\t'{1}'\t'{2}'\t'{3}'",item.RowId,item.OldData ?? "null",item.NewData ?? "null",item.RowMod);
}
}
}
public class DataRow
{
public int Id { get; set; }
public string Data { get; set; }
public string RowMod { get; set; }
public DataRow(int id, string data, string rowMod)
{
Id = id;
Data = data;
RowMod = rowMod;
}
}
Output:
RowID OldData NewData RowMod
1 'fish' 'null' 'D'
2 'null' 'cat' 'A'
3 'fox' 'dog' 'U'
4 'mouse' 'mouse' ''
I am not sure if this is the best way to achieve your requirement but this is what I have:-
var result = rows.GroupBy(x => x.RowId)
.Select(x =>
{
var firstData = x.FirstOrDefault();
var secondData = x.Count() == 1 ? x.First().RowMod == "A" ? firstData : null
: x.Skip(1).FirstOrDefault();
return new
{
RowId = x.Key,
OldData = firstData.RowMod == "A" ? null : firstData.Data,
NewData = secondData != null ? secondData.Data : null,
RowMod = String.IsNullOrEmpty(firstData.RowMod) && secondData != null ?
secondData.RowMod : firstData.RowMod
};
});
Working Fiddle.
Getting the two parts of the intended object can be done iteratively:
foreach(var rowId in myList.Select(x => x.RowId).Distinct())
{
//get the left item
var leftItem = myList.SingleOrDefault(x => x.RowId == rowId && String.IsNullOrWhiteSpace(x.rowmod);
//get the right item
var rightItem = myList.SingleOrDefault(x => x.RowId == rowId && !String.IsNullOrWhiteSpace(x.rowmod);
}
Your question doesn't specify how you create the second object. Is it a different class?
Either way, you can extrapolate from the above snippet that either item might be null if it doesn't exist in the original set.
All you need to do is use those found objects to create your new object.
While I love LINQ a lot, I don't think it is appropriate here as you want to buffer some values while iterating. If you do this with LINQ, it will be at best not performing well, at worst it will iterate the collection multiple times. It also looks way cleaner this way in my opinion.
IEnumerable<TargetClass> MapOldValues(IEnumerable<SourceClass> source)
{
var buffer = new Dictionary<string, string>();
foreach(var item in source)
{
string oldValue;
buffer.TryGetValue(item.RowId, out oldValue);
yield return new TargetClass
{
RowId = item.RowId,
OldData = oldValue,
NewData = (item.RowMod == "D" ? null : item.Data),
RowMod = item.RowMod };
// if the rows come sorted by ID, you can clear old values from
// the buffer to save memory at this point:
// if(oldValue == null) { buffer.Clear(); }
buffer[item.RowId] = item.Data;
}
}
if you then only want the latest updates, you can go with LINQ:
var latestChanges = MapOldValues(source).GroupBy(x => x.RowId).Select(x => x.Last());
I guess there are more elegant ways to do it, but this produces the output you expect:
public class MyClass
{
public int RowID { get; set; }
public string Data { get; set; }
public string RowMod { get; set; }
}
var result = (from id in myList.Select(x => x.RowID).Distinct()
let oldData = myList.Where(x => x.RowID == id).SingleOrDefault(x => x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => x.RowMod.Equals("")).Data
: null
let newData = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).Data
: null
let rowMod = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).RowMod
: null
select new
{
RowID = id,
OldData = oldData,
NewData = rowMod == null ? oldData : rowMod.Equals("D") ? null : newData,
RowMod = rowMod
});
foreach (var item in result)
{
Console.WriteLine("{0} {1} {2} {3}", item.RowID, item.OldData ?? "null", item.NewData ?? "null", item.RowMod ?? "-");
}
I have an ICollection of records (userID,itemID,rating) and an IEnumerable items
for a specific userID and each itemID from a set of itemIDs, i need to produce a list of the users rating for the items or 0 if no such record exists. the list should be ordered by the items.
example:
records = [(1,1,2),(1,2,3),(2,3,1)]
items = [3,1]
userID = 1
result = [0,2]
my attempt:
dataset.Where((x) => (x.userID == uID) & items.Contains(x.iID)).Select((x) => x.rating);
it does the job but it doesn't return 0 as default value and it isnt ordered...
i'm new to C# and LINQ, a pointer in the correct direction will be very appreciated.
Thank you.
This does the job:
var records = new int[][] { new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, new int[] { 2, 3, 1 } };
var items = new int[] { 3, 1 };
var userId = 1;
var result = items.Select(i =>
{
// When there's a match
if (records.Any(r => r[0] == userId && r[1] == i))
{
// Return all numbers
return records.Where(r => r[0] == userId && r[1] == i).Select(r => r[2]);
}
else
{
// Just return 0
return new int[] { 0 };
}
}).SelectMany(r => r); // flatten the int[][] to int[]
// output
result.ToList().ForEach(i => Console.Write("{0} ", i));
Console.ReadKey(true);
How about:
dataset.Where((x) => (x.userID == uID)).Select((x) => items.Contains(x.iID) ? x.rating : 0)
This does the job. But whether it's maintainable/readable solution is topic for another discussion:
// using your example as pseudo-code input
var records = [(1,1,2),(1,2,3),(2,3,1)];
var items = [3,1];
var userID = 1;
var output = items
.OrderByDescending(i => i)
.GroupJoin(records,
i => i,
r => r.ItemId,
(i, r) => new { ItemId = i, Records = r})
.Select(g => g.Records.FirstOrDefault(r => r.UserId == userId))
.Select(r => r == null ? 0 : r.Rating);
How this query works...
ordering is obvious
the ugly GroupJoin - it joins every element from items with all records that share same ItemId into annonymous type {ItemId, Records}
now we select first record for each entry that matches userId - if none is found, null will be returned (thanks to FirstOrDefault)
last thing we do is check whether we have value (we select Rating) or not - 0
How about this. your question sounds bit like an outer join from SQL, and you can do this with a GroupJoin, SelectMany:
var record1 = new Record() { userID = 1, itemID = 1, rating = 2 };
var record2 = new Record() { userID = 1, itemID = 2, rating = 3 };
var record3 = new Record() { userID = 2, itemID = 3, rating = 1 };
var records = new List<Record> { record1, record2, record3 };
int userID = 1;
var items = new List<int> { 3, 1 };
var results = items
.GroupJoin( records.Where(r => r.userID == userID), item => item, record => record.itemID, (item, record) => new { item, ratings = record.Select(r => r.rating) } )
.OrderBy( itemRating => itemRating.item)
.SelectMany( itemRating => itemRating.ratings.DefaultIfEmpty(), (itemRating, rating) => rating);
To explain what is going on
For each item GroupJoin gets the list of rating (or empty list if no rating) for the specified user
OrderBy is obvious
SelectMany flattens the ratings lists, providing a zero if the ratings list is empty (by DefaultIfEmpty)
Hope this makes sense.
Be aware, if there is more than one rating for an item by a user, they will all appear in the final list.