i want to ask you for help with combine all subitems from list, which looks like:
public class Subitem
{
public string Name { get; set; }
public string Code { get; set; }
public float Price { get; set; }
}
public class Item
{
public string Name { get; set; }
public string Code { get; set; }
public List<Subitem> Subitems { get; set; }
}
var components = new List<Item>();
components.Add(new Item()
{
Code = "ItemCode1",
Name = "Item1Name",
Subitems = new List<Subitem>
{
new Subitem { Code = "SubitemCode1", Price = 32 },
new Subitem { Code = "SubitemCode2", Price = 21 },
new Subitem { Code = "SubitemCode3", Price = 11 },
new Subitem { Code = "SubitemCode4", Price = 51 }
}
});
components.Add(new Item()
{
Code = "ItemCode2",
Name = "Item2Name",
Subitems = new List<Subitem>
{
new Subitem { Code = "SubitemCode5", Price = 11 },
new Subitem { Code = "SubitemCode6", Price = 22 },
new Subitem { Code = "SubitemCode7", Price = 52 },
new Subitem { Code = "SubitemCode8", Price = 63 }
}
});
components.Add(new Item()
{
Code = "ItemCode3",
Name = "Item3Name",
Subitems = new List<Subitem>
{
new Subitem { Code = "SubitemCode9", Price = 11 },
new Subitem { Code = "SubitemCode10", Price = 22 },
new Subitem { Code = "SubitemCode11", Price = 52 },
new Subitem { Code = "SubitemCode12", Price = 63 }
}
});
components.Add(new Item()
{
Code = "ItemCode4",
Name = "Item4Name",
Subitems = new List<Subitem>
{
new Subitem { Code = "SubitemCode13", Price = 11 },
new Subitem { Code = "SubitemCode14", Price = 22 },
new Subitem { Code = "SubitemCode15", Price = 52 },
new Subitem { Code = "SubitemCode16", Price = 63 }
}
});
I want to combine all subitems in model which looks like this:
new { Code = SubitemCode1, Price = 32 }
...
new { Code = SubitemCode8, Price = 63 }
new { Code = "SubitemCode1:SubitemCode5", Price = 43 } //11 + 32
...
new { Code = "SubitemCode1:SubitemCode8", Price = 95 } //32 + 63
new { Code = "SubitemCode2:SubitemCode5", Price = ... }
...
new { Code = "SubitemCode2:SubitemCode8", Price = ... }
#EDIT
new { Code = "SubitemCode1:SubitemCode5:SubitemCode9", Price = 54 } // 11 + 32 + 11
...
new { Code = "SubitemCode1:SubitemCode5:SubitemCode12", Price = 96 } // 11 + 32 + 63
new { Code = "SubitemCode1:SubitemCode6:SubitemCode9", Price = ... }
...
new { Code = "SubitemCode1:SubitemCode6:SubitemCode12", Price = ... }
...
new { Code = "SubitemCode1:SubitemCode8:SubitemCode9", Price = ... }
...
new { Code = "SubitemCode1:SubitemCode8:SubitemCode12", Price = ... }
new { Code = "SubitemCode2:SubitemCode5:SubitemCode9", Price = ... }
...
Can anyone explain to me how to get on with it? There could be 1-5 Item and 1-10 in Subitems in each Item and i need to have all combination of subitems with the addition of prices.
Subitems from Item in which they are, is not combinable, only Subitems from other Item
Thank You in Advance,
Best Regards.
You can simply make join over two different list like
var result = components[0].Subitems
.Join(components[1].Subitems, x => true, y => true, (a, b) => new { Code = a.Code + ":" + b.Code, Price = a.Price + b.Price })
.ToList();
OR you can do this by using linq
var result = from a in components[0].Subitems
from b in components[1].Subitems
select new
{
Code = a.Code + ":" + b.Code,
Price = a.Price + b.Price
};
And finally print the result
foreach (var item in result)
{
Console.WriteLine("Code: " + item.Code + "\t Price: " + item.Price);
}
Output:
Okay, i think that i got this.
The code looks like:
var result = new List<SubItem>(); //list of combined SubItems
var ms = 0; // start index of items from list to combine
var mk = 0; // end index of items from list to combine
for (int i = 0; i < components.Count; i++) // count of all items
{
if (i == 0) //if there is a first item then we don't combine codes and prices
{
for (int mat = 0; mat < components[i].SubItems.Count; mat++)
{
var data = components[i].SubItem[mat];
result.Add(new SubItem { Price = data.Price, Code = data.Code });
mk = mat; // set last index of SubItem to combine
}
continue;
}
for (int j = ms; j < mk + 1; j++) // iterate from first to last SubItem to combine them with new SubItems
{
for (int mat = 0; mat < components[i].SubItems.Count; mat++) // iterate through SubItems
{
result.Add(new SubItem { Code = result[j].Code + ":" + components[i].SubItem[mat].Code, price = result[j].Price + components[i].SubItem[mat].Price }); // Combine last SubItem with now iterating Subitem.
}
}
ms = mk + 1; // update new start index to combine
mk = result.Count - 1; // update new end index to combine
}
I achieved what I wanted. :)
Best Regards!
Related
I'm running out of ideas on how to uncompress an array (request array A[] to response array B[])
Here are my definitions
A is a request class.
class A
{
public string Date { get; set; }
public decimal Price { get; set; }
}
Below is my array of requests of class A with its initalization.
var request = new A[]
{
new A { Date = "14-04-2016", Price = 100 },
new A { Date = "15-04-2016", Price = 100 },
new A { Date = "16-04-2016", Price = 0 },
new A { Date = "17-04-2016", Price = 100 },
new A { Date = "18-04-2016", Price = 100 }
};
B is a respond class.
class B
{
public string Start { get; set; }
public string End { get; set; }
public decimal Price { get; set; }
}
The above requests needs to be converted to an array of B. Something like this
var response = new B[]
{
new B { Start = "14-04-2016", End = "16-04-2016", Price = 100 },
new B { Start = "16-04-2016", End = "17-04-2016", Price = 0 },
new B { Start = "17-04-2016", End = "18-04-2016", Price = 100 }
};
The response is grouped based on the Price and order by date. Its more of like, I need to uncompress the request array A[] into response array B[].
How can I achieve this?
You could do this using GroupBy linq extension, following query returns List<B>objects.
var results = request.Select(s=>
new
{
Price = s.Price,
Date = DateTime.ParseExact(s.Date, "dd-MM-yyyy", null) // convert to Date.
})
.GroupBy(g=>g.Price)
.Select(s=>
new B()
{
Start = s.Min(c=>c.Date).ToString("dd-MM-yyyy"),
End = s.Max(c=>c.Date).ToString("dd-MM-yyyy"),
Price = s.Key
})
.ToList() ;
Update :
As per comments, you don't really require grouping on price. What you need is grouping adjacent items whose price is matching .
We could achieve this with slight modification to above Linq query.
int grp = 0;
decimal prevprice=response.First().Price;
var results = request.Select((s, i)=>
{
grp = s.Price == prevprice? grp : ++grp;
prevprice = s.Price;
return new
{
grp,
Price = s.Price,
Date = DateTime.ParseExact(s.Date, "dd-MM-yyyy", null)
};
})
.GroupBy(g=>g.grp)
.Select(s=>
new B()
{
Start = s.Min(c=>c.Date).ToString("dd-MM-yyyy"),
End = s.Max(c=>c.Date).ToString("dd-MM-yyyy"),
Price = s.First().Price
});
Output
14-04-2016,15-04-2016 ,100
16-04-2016,16-04-2016 ,0
17-04-2016,18-04-2016 ,100
Working Example
Pseudocode (assumes request is ordered by date - if not you can sort it easily):
int lastPrice = -1;
//count the distinct price ranges
int responseSize = 0;
foreach (A requestObj in request) {
if (requestObj.price != lastPrice) {
responseSize++;
lastPrice = requestObj.price;
}
}
//set the initial element
B[] response = new B[responseSize];
response[0].start = request[0].date;
response[0].price = request[0].price;
int responseindex = 0;
//parse the result
foreach (A requestObj in request) {
if (requestObj.price != response[responseindex].price) {
response[responseindex].end = requestObj.date;
responseIndex++;
response[responseindex].price = requestObj.price;
response[responseindex].start= requestObj.date;
}
}
//set the end date of the final object
response[response.length - 1].end = request[request.length - 1].date;
This can also be done with the following:
var response = from reqItem in request
group reqItem by reqItem.Price into g
select new B()
{
Start = g.Min(m => DateTime.Parse(m.Date)).ToString("dd-MM-yyyy"),
End = g.Max(m => DateTime.Parse(m.Date)).ToString("dd-MM-yyyy"),
Price = g.Key
};
I'm trying to create a LINQ query which is a derivative of SelectMany.
I have N items:
new {
{ Text = "Hello", Width = 2 },
{ Text = "Something else", Width = 1 },
{ Text = "Another", Width = 1 },
{ Text = "Extra-wide", Width = 3 },
{ Text = "Random", Width = 1 }
}
I would like the result to be a List<List<object>>(), where:
List<List<object>> = new {
// first "row"
{
{ Text = "Hello", Width = 2 },
{ Text = "Something else", Width = 1 },
{ Text = "Another", Width = 1 }
},
// second "row"
{
{ Text = "Extra-wide", Width = 3 },
{ Text = "Random", Width = 1 }
}
}
So the items are grouped into "rows" where Sum(width) in the internal List is less than or equal to a number (maxWidth - in my instance, 4).
It's kinda a derivative of GroupBy, but the GroupBy is dependent on earlier values in the array - which is where I get stumped.
Any ideas would be appreciated.
We can combine the ideas of LINQ's Aggregate method with a GroupWhile method to group consecutive items while a condition is met to build an aggregate value for the current group to be used in the predicate:
public static IEnumerable<IEnumerable<T>> GroupWhileAggregating<T, TAccume>(
this IEnumerable<T> source,
TAccume seed,
Func<TAccume, T, TAccume> accumulator,
Func<TAccume, T, bool> predicate)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> list = new List<T>() { iterator.Current };
TAccume accume = accumulator(seed, iterator.Current);
while (iterator.MoveNext())
{
accume = accumulator(accume, iterator.Current);
if (predicate(accume, iterator.Current))
{
list.Add(iterator.Current);
}
else
{
yield return list;
list = new List<T>() { iterator.Current };
accume = accumulator(seed, iterator.Current);
}
}
yield return list;
}
}
Using this grouping method we can now write:
var query = data.GroupWhileAggregating(0,
(sum, item) => sum + item.Width,
(sum, item) => sum <= 4);
You can sort of do that with the Batch method from MoreLinq library which is available as a NuGet package. The result is a List<IEnumerable<object>>. Here is the code:
class Obj
{
public string Text {get;set;}
public int Width {get;set;}
}
void Main()
{
var data = new [] {
new Obj { Text = "Hello", Width = 2 },
new Obj { Text = "Something else", Width = 1 },
new Obj { Text = "Another", Width = 1 },
new Obj { Text = "Extra-wide", Width = 3 },
new Obj { Text = "Random", Width = 1 }
};
var maxWidth = data.Max (d => d.Width );
var result = data.Batch(maxWidth).ToList();
result.Dump(); // Dump is a linqpad method
Output
I don't think you can do that with LINQ. One alternative approach would be the following:
var data = ... // original data
var newdata = new List<List<object>>();
int csum = 0;
var crow = new List<object>();
foreach (var o in data) {
if (csum + o.Width > 4) { //check if the current element fits into current row
newdata.Add(crow); //if not add current row to list
csum = 0;
crow = new List<object>(); //and create new row
}
crow.Add(o); //add current object to current row
csum += o.Width;
}
if (crow.Count() > 0) //last row
newData.Add(c);
EDIT: The other answer suggests to use Batch from the MoreLinq Library. In fact, the above source code, is more or less the same, what Batch does, but not only counting the elements in each batch but summing up the desired property. One could possibly generalize my code with a custom selector to be more flexible in terms of "batch size".
I am using NEST (ver 0.12) Elasticsearch (ver 1.0) and I'm facing a problem with the facets.
Basically I'm expecting the results to be something similar to this
Between 18 and 25 (10)
Between 26 and 35 (80)
Greater then 35 (10)
But what I'm actually getting is this
between (99)
and (99)
35 (99)
26 (99)
This is my code
namespace ElasticSerachTest
{
class Program
{
static void Main(string[] args)
{
var setting = new ConnectionSettings(new Uri("http://localhost:9200/"));
setting.SetDefaultIndex("customertest");
var client = new ElasticClient(setting);
var createIndexResult = client.CreateIndex("customertest", new IndexSettings
{
});
// put documents to the index using bulk
var customers = new List<BulkParameters<Customer>>();
for (int i = 1; i < 100; i++)
{
customers.Add(new BulkParameters<Customer>(new Customer
{
Id = i,
AgeBand = GetAgeBand(),
Name = string.Format("Customer {0}", i)
}));
}
var bp = new SimpleBulkParameters()
{
Replication = Replication.Async,
Refresh = true
};
//first delete
client.DeleteMany(customers, bp);
//now bulk insert
client.IndexMany(customers, bp);
// query with facet on nested field property genres.genreTitle
var queryResults = client.Search<Customer>(x => x
.From(0)
.Size(10)
.MatchAll()
.FacetTerm(t => t
.OnField(f => f.AgeBand)
.Size(30))
);
var yearFacetItems = queryResults.FacetItems<FacetItem>(p => p.AgeBand);
foreach (var item in yearFacetItems)
{
var termItem = item as TermItem;
Console.WriteLine(string.Format("{0} ({1})", termItem.Term, termItem.Count));
}
Console.ReadLine();
}
public static string GetAgeBand()
{
Random rnd = new Random();
var age = rnd.Next(18, 50);
if (Between(age, 18, 25))
{
return "Between 18 and 25";
}
else if (Between(age, 26, 35))
{
return "Between 26 and 35";
}
return "Greater then 35";
}
public static bool Between(int num, int lower, int upper)
{
return lower <= num && num <= upper;
}
[ElasticType(Name = "Customer", IdProperty = "id")]
public class Customer
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
[ElasticProperty(Index = FieldIndexOption.not_analyzed)]
public string AgeBand
{
get;
set;
}
}
}
}
Thanks
Based on the output you are seeing, I do not think your FieldIndexOption.not_analyzed is being applied to the AgeBand field. As those facet results look like like analyzed values. You need to apply the mapping during the index creating process as part of your index settings. Please try the following index creation code:
var createIndexResult = client.CreateIndex("customertest", s => s
.AddMapping<Customer>(m => m
.MapFromAttributes()
)
);
Please see the Nest Documentation on Mapping for some other ways to add the mapping to your index as well.
I am trying to create a List of Anonymous types as shown below but I am making mistake somewhere
for (int i = 0; i < 10; i++)
{
var list = new[]
{
new { Number = i, Name = string.Concat("name",i) }
};
}
E.g.
var o1 = new { Id = 1, Name = "Name1" };
var o2 = new { Id = 2, Name = "Name2" };
var list = new[] { o1, o2 }.ToList();
how to do the same at runtime?
no error...but the collection is always 1
That is because your are creating a new list in each iteration
You can try it like:
var list = new[] { new { Number = 0, Name = "Name1" } }.ToList(); //just to create a
//list of anonymous type object
list.Clear();
for (int i = 0; i < 10; i++)
{
list.Add(new { Number = i, Name = string.Concat("name",i) });
}
Or one way to do that would be to use List<Object> like:
List<object> list = new List<object>();
for (int i = 0; i < 10; i++)
{
list.Add(new { Number = i, Name = string.Concat("name",i) });
}
Or you can use Enumerable.Range like
var list = Enumerable.Range(0, 10)
.Select(i => new { Number = i, Name = "SomeName" })
.ToList();
Were you thinking of something like the following (using LINQ):
var anonList = Enumerable
.Range(1, 10)
.Select(i => new {
ID = i,
Name = String.Format("Name{0}", i)
});
You could of course replace Enumerable.Range() with anything that give you a list to select from.
You need a List object to store it.
List<Object> o = new List<Object>();
for (int i = 0; i < 10; i++)
{
o.Add(new { Number = i, Name = string.Concat("name",i) });
}
o.Dump();
Define a list first then store the value in For loop
List<Object> NewList = new List<Object>();
for (int i = 0; i < 10; i++)
{
NewList.Add(
{
new { Number = i, Name = string.Concat("name",i) }
});
}
int i = 0;
while(i < 10)
{
list.Add(new { Number = i, Name = string.Concat("name",i) });
i++;
}
I have the following type:
public class Parts
{
public string PartNo { get; set; }
public decimal Price { get; set; }
}
what I would like to do is to compare the price or each part with the cheapest one and display the difference in percentage.
This is what I have tried so far and it works:
var part1 = new Part {PartNo = "part1", Price = 10};
var part2 = new Part {PartNo = "part1", Price = 8};
var part3 = new Part {PartNo = "part1", Price = 12};
var parts = new List<Part> {part1, part2, part3};
var list = from p in parts
orderby p.Price ascending
select p;
var sb = new StringBuilder();
var counter = 0;
decimal firstPrice=0;
foreach (Part p in list)
{
if (counter == 0)
{
firstPrice = p.Price;
}
sb.Append(p.PartNo + ": " + p.Price + "," + ((p.Price/firstPrice)-1)*100 + Environment.NewLine);
counter++;
}
Console.WriteLine(sb.ToString(), "Parts List");
This outputs the following:
part1: 8, 0
part1: 10, 25.00
part1: 12, 50.0
This shows the price increase for each each part, and that is what I am trying to achieve but I was wondering is there a better way of calculating the price difference in percentage (e.g. with a LINQ query) or in any other way.
Thanks
I would calculate the difference as first step.
var cheapestPrice = parts.Min(p => p.Price);
var list = parts.Select(p => new {
Part = p,
DiffPercentage = ((p.Price - cheapestPrice) / cheapestPrice) * 100
});
foreach (var p in list)
Console.WriteLine("{0}: {1},{2}%", p.Part.PartNo, p.Part.Price, p.DiffPercentage);
// list defined as sorted by price ascending as per the code
var list = parts.OrderBy(p => p.Price); // less verbose way of saying the same
var firstPrice = list.First().Price;
var differences = list.Skip(1).Select(s => new {Part = s, PercentageDiff = (s.Price/firstPrice - 1)*100});
The .Skip(1) is optional. You may not want to compare cheapest price to itself.
Tim beat me to it, but use the select to create your string
void Main()
{
var part1 = new Part {PartNo = "part1", Price = 10};
var part2 = new Part {PartNo = "part1", Price = 8};
var part3 = new Part {PartNo = "part1", Price = 12};
var parts = new List<Part> {part1, part2, part3};
var lowest = parts.Min(p => p.Price );
var result = parts.Select (p => string.Format("Part #:{0} {1} -> {2}", p.PartNo, p.Price, ((p.Price/lowest)-1)*100 ));
result.ToList()
.ForEach(rs => Console.WriteLine (rs));
/*
Part #:part1 10 -> 25.00
Part #:part1 8 -> 0
Part #:part1 12 -> 50.0
*/
}
// Define other methods and classes here
public class Part
{
public string PartNo { get; set; }
public decimal Price { get; set; }
}