LINQ Searching ordered list and appending occurence number - linq

I have the following custom class
public class Album
{
public string PhotoName { get; set; }
public string Location { get; set; }
public DateTime DateTime { get; set; }
}
and I have the following string:
#"photo.jpg, Warsaw, 2013-09-05 14:08:15
john.png, London, 2015-06-20 15:13:22
myFriends.png, Warsaw, 2013-09-05 14:07:13
Eiffel.jpg, Paris, 2015-07-23 08:03:02
pisatower.jpg, Paris, 2015-07-22 23:59:59
BOB.jpg, London, 2015-08-05 00:02:03"
and I need to write a function that will append the order number beside the Location based on the timestamp thus the resulting StringBuilder must be
Warsaw01.jpg
London01.jpg
Warsaw02.jpg
Paris01.jpg
Paris02.jpg
London02.jpg
What I have done so far?
I have a List of that type that I sorted by Location then by DateTime
List<Album> SortedList = list
.OrderBy(o => o.Location)
.ThenBy(o => o.DateTime)
.ToList();
now I need to have a StringBuilder that will append the index number beside the location.
This is my complete method with the part and I am stuck on how should I search the ordered list. Question is: how can I write the LINQ for searching through the list?:
public static string Solution(string S)
{
string[] group = S.Split("\r\n");
List<Album> list = new List<Album>();
StringBuilder sb = new StringBuilder();
//added each line of the string to list
foreach (string g in group)
{
string[] album = g.Split(',');
Album a = new Album();
a.PhotoName = album[0];
a.Location = album[1];
a.DateTime = DateTime.Parse(album[2]);
list.Add(a);
}
//ordered the list
List<Album> SortedList = list.OrderBy(o => o.Location).ThenBy(o => o.DateTime).ToList();
//then foreach line, append index number by searching through the list
foreach (string g in group)
{
string[] album = g.Split(',');
Album a = new Album();
a.PhotoName = album[0];
string[] photodetails = a.PhotoName.Split('.');
a.Location = album[1];
a.DateTime = DateTime.Parse(album[2]);
//this is the part where I must figure out how to build the string. I am stuck here
// var query = SortedList.IndexOf(list.SingleOrDefault(i => i.DateTime == a.DateTime));
sb.AppendLine(a.Location + query + "." + photodetails[1]);
}
string res = sb.ToString();
return res;
}
Appreciate the responses.
Update Warsaw2 must appear before Warsaw1 since the timestamp of Warsaw2 is later than Warsaw1
Warsaw02.jpg
London01.jpg
Warsaw01.jpg
Paris01.jpg
Paris02.jpg
London02.jpg

I have just added a order in the Album class
public class Album
{
public string PhotoName { get; set; }
public string Location { get; set; }
public DateTime DateTime { get; set; }
public int Order { get; set; }
}
public static string Solution(string S)
{
string[] stringSeparators = new string[] { "\r\n" };
string[] group = S.Split(stringSeparators, StringSplitOptions.None);
List<Album> list = new List<Album>();
StringBuilder sb = new StringBuilder();
//added each line of the string to list
for (int i = 0; i < group.Length; i++)
{
string[] album = group[i].Split(',');
Album a = new Album();
a.PhotoName = album[0];
a.Location = album[1];
a.DateTime = DateTime.Parse(album[2]);
a.Order = i;
list.Add(a);
}
//ordered the list
var groupedByLocation = list.GroupBy(o => o.Location).ToList();
for (int i = 0; i < groupedByLocation.Count; i++)
{
int indexValue = 01;
foreach (var item in groupedByLocation[i])
{
item.PhotoName = string.Format("{0}{1}.jpg", groupedByLocation[i].Key, indexValue);
indexValue++;
}
}
//then foreach line, append index number by searching through the list
var locations = groupedByLocation
.SelectMany(g => g.Select(h => h))
.ToList()
.OrderBy(y => y.Order)
.Select(g => g.PhotoName);
return string.Join("\r\n", locations);
}

Just for the fun - an alternative approach:
For the task at hand, there's no need to have an Album class, a list and two loops.
We can go over the lines once, and use a dictionary to hold the counters for us.
class PhotoLocationsCounter
{
private readonly Dictionary<string, int> locationsCounter = new Dictionary<string, int>();
public string GetLocationsWithCounters(string source)
{
string[] lines = source.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
var locations = lines.Select(this.LineToLocationWithCounter);
return string.Join("\n", locations);
}
private string LineToLocationWithCounter(string line)
{
string[] album = line.Split(',');
var location = album[1].Trim();
var ext = album[0].Split('.')[1];
var counter = this.GetAndIncreaseLocationCounter(location);
return $"{location}{counter.ToString("D2")}.{ext}";
}
private int GetAndIncreaseLocationCounter(string location)
{
if (!this.locationsCounter.TryGetValue(location, out int counter))
{
this.locationsCounter.Add(location, 0);
}
return ++this.locationsCounter[location];
}
}
And you call it:
string data = #"photo.jpg, Warsaw, 2013-09-05 14:08:15
john.png, London, 2015-06-20 15:13:22
myFriends.png, Warsaw, 2013-09-05 14:07:13
Eiffel.jpg, Paris, 2015-07-23 08:03:02
pisatower.jpg, Paris, 2015-07-22 23:59:59
BOB.jpg, London, 2015-08-05 00:02:03";
var locations = new PhotoLocationsCounter().GetLocationsWithCounters(data);

Related

keeping track of previous elements in foreach loop

Lets say I have a list of asteroid objects like so:
9_Amphitrite
24_Themis
259_Aletheia
31_Euphrosyne
511_Davida
87_Sylvia
9_Metis
41_Daphne
Each asteroid has a title, a StartRoationPeriod, and a EndRoationPeriod.
I need to concatenate their names based on how close the current asteroid StartRoationPeriod and previous asteroid EndRoationPeriod are to an orbital constant and then spit out the concatenated title.
So with the above list, the final objects may look like this:
9_Amphitrite
24_Themis;259_Aletheia
31_Euphrosyne;511_Davida;87_Sylvia
9_Metis
41_Daphne
This requires me to keep track of both the current and previous asteroids.
I started to write the loop, but I'm unsure of where or even how to check the current asteroids start rotation period against the previous asteroids end rotation period...basically, it just gets messy fast...
string asteroid_title = string.Empty;
Asteroid prev_asteroid = null;
foreach (var asteroid in SolarSystem)
{
if (prev_asteroid != null)
{
if (asteroid.StartRoationPeriod + OrbitalConstant >= prev_asteroid.EndRoationPeriod)
{
asteroid_title = asteroid_title + asteroid.Title;
} else {
asteroid_title = asteroid.Title;
yield return CreateTitle();
}
}
prev_evt = evt;
}
I think this should work for you (If aggregate looks too complex try to convert it to a foreach,it's easy)
using System;
using System.Collections.Generic;
using System.Linq;
namespace Program
{
class Asteroid
{
public int EndRoationPeriod { get; internal set; }
public string Name { get; internal set; }
public int StartRoationPeriod { get; internal set; }
}
class AsteroidGroup
{
public int EndRoationPeriod { get; internal set; }
public string Names { get; internal set; }
}
internal class Program
{
private static void Main(string[] args)
{
int OrbitalConstant = 10;
List<Asteroid> SolarSystem = new List<Asteroid>()
{
new Asteroid() { Name= "9_Amphitrite" ,StartRoationPeriod=10 ,EndRoationPeriod=50},
new Asteroid() { Name= "24_Themis" ,StartRoationPeriod=45,EndRoationPeriod=100},
new Asteroid() { Name= "259_Aletheia",StartRoationPeriod=40 ,EndRoationPeriod=150},
new Asteroid() { Name= "31_Euphrosyne" ,StartRoationPeriod=60,EndRoationPeriod=200},
new Asteroid() { Name= "511_Davida" ,StartRoationPeriod=195,EndRoationPeriod=250},
new Asteroid() { Name= "87_Sylvia" ,StartRoationPeriod=90,EndRoationPeriod=300},
new Asteroid() { Name= "9_Metis" ,StartRoationPeriod=100,EndRoationPeriod=350},
new Asteroid() { Name= "41_Daphne" ,StartRoationPeriod=110,EndRoationPeriod=400},
};
var result = //I skip the first element because I initialize a new list with that element in the next step
SolarSystem.Skip(1)
//The first argument of Aggregate is a new List with your first element
.Aggregate(new List<AsteroidGroup>() { new AsteroidGroup { Names = SolarSystem[0].Name, EndRoationPeriod = SolarSystem[0].EndRoationPeriod } },
//foreach item in your list this method is called,l=your list and a=the current element
//the method must return a list
(l, a) =>
{
//Now this is your algorithm
//Should be easy to undrestand
var last = l.LastOrDefault();
if (a.StartRoationPeriod + OrbitalConstant >= last.EndRoationPeriod)
{
last.Names += " " + a.Name;
last.EndRoationPeriod = a.EndRoationPeriod;
}
else
l.Add(new AsteroidGroup { Names = a.Name, EndRoationPeriod = a.EndRoationPeriod });
//Return the updated list so it can be used in the next iteration
return l;
});
A more compact solution
var result = SolarSystem
.Skip(1)
.Aggregate( SolarSystem.Take(1).ToList(),
(l, a) => (a.StartRoationPeriod + OrbitalConstant >= l[l.Count - 1].EndRoationPeriod) ?
(l.Take(l.Count - 1)).Concat(new List<Asteroid> { new Asteroid() { Name = l[l.Count - 1].Name += " " + a.Name, EndRoationPeriod = a.EndRoationPeriod } }).ToList() :
l.Concat(new List<Asteroid> { a }).ToList()
);

Linq Split properties of Class and assign it to another Custom Class

I have a Complex Situation now and i am terribly stuck. Kindly Let me know if you can share some light to it.
I have a
List Which will have the Following properties
public class Categories
{
public string DisplayName { get; set; }
public string ValueCode { get; set; }
public string Count { get; set; }
}
This will have Values like
Category1/SubCategory1
cat1/sc1
5
Category1/SubCategory2
cat1/sc2
4
Category 2/Subcategory1
cat2/sc1
5
Category 2/Subcategory2
cat2/sc2
23
I created a Custom Class to fill in the values
public class JobCateogry
{
public string DisplayName { get; set; }
public string ValueCode { get; set; }
public string Count { get; set; }
public List<JobCateogry> SubCategories { get; set; }
}
I have to Split the String in the Code Value and assign it to the SubCategory.
Like My Final out of jobCategory would be
Category1
Cat1
9
SubCategory1
sub1
5
SubCateogry2
sub2
4
I tried to Split the string and assign it to the new class in two step first by splitting and then by assiging. But i am sure i am doing it the wrong way, because the moment i split, i loose the count .
var lstCategory = Categories
.Where(i => i.count > 0)
.Select(item => item.valueCode.Split('/')
.Select(k =>(k)).ToList();
List<JobCategories> jobcategories = lstCategory
.Select(item => item.Split(QueryStringConstants.CAT_SEPERATOR.ToCharArray()[0]))
.GroupBy(tokens => tokens[0].Trim(), tokens => tokens[1])
.Select(g => new JobCategories(g.Key, g.DisplayName,g.ToList(),)).ToList();
Can you please help?
A bit weird task
It might not be the best solution and it only works with the two layers :-), and i tried keeping a lot of linq for the fun of it
anyway hope it can get you moving forward.
full code snippet https://gist.github.com/cbpetersen/db698def9a04ebb2abbc
static void Main(string[] args)
{
var cats = new[]
{
new Categories { Count = "5", ValueCode = "cat1/sc1", DisplayName = "Category1/SubCategory1" },
new Categories { Count = "4", ValueCode = "cat1/sc2", DisplayName = "Category1/SubCategory2" },
new Categories { Count = "5", ValueCode = "cat2/sc1", DisplayName = "Category2/Subcategory1" },
new Categories { Count = "23", ValueCode = "cat2/sc2", DisplayName = "Category2/Subcategory2" }
};
var categories = cats.Select(x => x.DisplayName.Split('/')[0]).Distinct();
var list = new List<JobCateogries>();
foreach (var category in categories)
{
var a = new JobCateogries
{
ValueCode = cats.Where(x => x.DisplayName.Split('/')[0] == category)
.Select(x => x.ValueCode.Split('/')[0]).FirstOrDefault(),
DisplayName = category,
SubCategories = cats.Where(x => x.DisplayName.Split('/')[0] == category)
.Select(x => new JobCateogries
{
SubCategories = new List<JobCateogries>(),
Count = x.Count,
DisplayName = x.DisplayName.Split('/')[1],
ValueCode = x.ValueCode.Split('/')[1]
}).ToList(),
};
a.Count = a.SubCategories.Select(x => int.Parse(x.Count)).Sum().ToString();
list.Add(a);
}
list.ForEach(x => Print(x));
Console.ReadKey();
}
public static void Print(JobCateogries category, int indent = 0)
{
var prefix = string.Empty.PadLeft(indent);
Console.WriteLine(prefix + category.DisplayName);
Console.WriteLine(prefix + category.ValueCode);
Console.WriteLine(prefix + category.Count);
category.SubCategories.ForEach(x => Print(x, indent + 4));
}

ProtoBuf-Linq error message “ Invalid field in source data: 0”

I've encountered the following issue while using protobuf-linq:
using (var stream = new MemoryStream())
{
SerializeMultiple(PrepareData(), stream);
}
private static void SerializeMultiple(IEnumerable<Person> persons, Stream stream)
{
foreach (var person in persons)
{
Serializer.Serialize(stream, person);
}
stream.Position = 0;
var q = RuntimeTypeModel.Default.AsQueryable<Person>(stream,null);
var results = from e in q
where e.Id % 2 == 0
select new { e.Id, e.Name };
Console.WriteLine("first : " + results.First().Id);
Console.ReadLine();
}
static IEnumerable<Person> PrepareData()
{
for (int i = 0; i < (int) 1e+04; i++)
{
yield return new Person {Id = i, Name= "John" + i, Address = "Address" + i*i};
}
}
[ProtoContract]
class Person
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public string Address { get; set; }
}
The AsQueryable line throws the aforementioned exception:
Invalid field in source data: 0
Any thoughts on this matter?
It's not protobuf-linq error. When serializing items into a stream, you should use SerializeWithLengthPrefix to prefix every message with its length, to allow separate them. By default, protobuf-linq uses PrefixStyle.Base128. Below you can find a snippet making it right:
Serializer.SerializeWithLengthPrefix(stream, person, PrefixStyle.Base128);

PaginatedList for pagination for MVC 3 application? Error: has some invalid arguments

I have the following code, I can figure why its invalid argument:
AuditDAL ad = new AuditDAL();
var agencies = ad.SearchAgencies("Ak001", "");
string col = param.sColumns.Split(',')[param.iSortCol_0];
string orderby = col + " " + param.sSortDir_0;
// The best overloaded method match for 'AMS.Helper.PaginatedList.PaginatedList(System.Linq.IQueryable, int, int)' has some invalid arguments C:\NexGen\AMS\DEV\Source\AMS\Controllers\AuditController.cs
var qry = new PaginatedList<AuditAgency>(agencies, param.iDisplayStart, param.iDisplayLength);
PaginatedList Code:
namespace AMS.Helper
{
public class PaginatedList<T> : List<T> {
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
public int TotalCount { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize) {
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize);
this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
}
public bool HasPreviousPage {
get {
return (PageIndex > 0);
}
}
public bool HasNextPage {
get {
return (PageIndex+1 < TotalPages);
}
}
}
}
Search Agencies Code:
public IEnumerable<AuditAgency> SearchAgencies(string ori, string name)
{
List<AuditAgency> agencies = new List<AuditAgency>();
using (var conn = new SqlConnection(_connectionString))
{
var com = new SqlCommand();
com.Connection = conn;
com.CommandType = CommandType.StoredProcedure;
string term = "Ori";
if (!String.IsNullOrEmpty(ori))
{
term = "Ori";
com.Parameters.Add(new SqlParameter
{
ParameterName = "#ORI",
Value = ori
});
}
if (!String.IsNullOrEmpty(name))
{
term = "legal_name";
com.Parameters.Add(new SqlParameter
{
ParameterName = "#Name",
Value = name
});
}
com.CommandText = "Audit_Get_Agency_List";
var adapt = new SqlDataAdapter();
adapt.SelectCommand = com;
var dataset = new DataSet();
adapt.Fill(dataset);
agencies = (from c in dataset.Tables[0].AsEnumerable()
select new AuditAgency()
{
Agency_ID = Convert.ToInt32(c["Agency_Id"]),
Agency_Name = c["Agency_Name"].ToString(),
Agency_Ori = c["ORI"].ToString(),
COPSAuditNumber = c["COPSAuditNumber"].ToString(),
OIGAuditNumber = c["OIGAuditNumber"].ToString()
}).ToList<AuditAgency>();
return agencies;
}
}
The error should tell you where to start.
If you fire up the debugger, I think you'll find agencies is an IEnumberable, but not an IQueryable
correct it by changing the return type of SearchAgencies from IQueryable to IEnumerable
or alternatively, you can change the type of the PaginatedList to accept IEnumberables instead of IQueryables. this may be safer as IQueryable inherits from IEnumerable
(see
http://msdn.microsoft.com/en-us/library/system.linq.iqueryable.aspx or
Differences between IQueryable, List, IEnumerator?
for the difference between the two)

Linq Unique Values

i have a list of generic class which consists of 2 string property and 1 List as a property
code snipnets is as follows:
public Class abc
{
public int ID { get; set; }
public String Name { get; set; }
List<String> myList;
public List<String> Subjects
{
get
{
if (myList == null)
{
myList = new List<string>();
}
return myList;
}
}
public abc()
{
}
public abc(int id, String name, params string[] subjects)
{
Subjects.AddRange(subjects.AsEnumerable<String>());
ID = id;
Name = name;
}
}
List<abc> myList = new List<abc>();
myList.Add(new abc(1, "p1", "Maths", "Science"));
myList.Add(new abc(2, "p2", "Maths", "Art"));
myList.Add(new abc(3, "p3", "Art", "Science"));
myList.Add(new abc(4, "p4", "Geometry", "Maths"));
I need the output as
Subject Count Person
Maths 3 p1,p2,p4
Science 2 p1,p3
Art 2 p2,p3
Geometry 1 p4
Looks like you want something like:
var query = from item in myList
from subject in item.Subjects
group item.Name by subject into g
select new { Subject = g.Key,
Count = g.Count(),
Person = string.Join(",", g) };
(Change g into g.ToArray() in the string.Join call if you're using .NET 3.5.)
var result =myList.SelectMany(p => p.Subjects
.Select(q => new{Person = p.Name, Subject = q, ID = p.ID}))
.GroupBy(p => p.Subject)
.Select(p => new {Name = p.Key, Count = p.Count(), Persons = p
.Aggregate("", (a, b) => a + b.Person
+ ",").TrimEnd(',')}).OrderBy( p => p.Count);
Iterate over this collection, and print result as needed - properties of a result are Name, Count, Persons

Resources