Solved

Really complicated no for or foreach loop LINQ question

Posted on 2014-12-08
16
144 Views
Last Modified: 2016-02-15
Hi, I need help with some LINQ. I can do this myself with for or foreach loops but I want to be better skilled in LINQ so want to see how it is done only with LINQ.
All help really appreciated.
Thank you
John
using System;
using System.Collections.Generic;

namespace ConsoleApplication30
{
    class Country
    {
        public String Name { get; set; }
        public String Currency { get; set; }
    }

    class Program
    {
        public void Run()
        {
            List<Country> topCountries = new List<Country> { 
                                                             new Country{ Name =" USA", Currency = "$" }, 
                                                             new Country{ Name = "UK" },
                                                             new Country{ Name = "China", Currency = "Y" }
                                                           };

            List<Country> otherCountries = new List<Country> { new Country { Name = "Germany" }, 
                                                               new Country { Name = "Austria" }, 
                                                               new Country { Name = "Switzerland" }, 
                                                               new Country { Name = "Austria" } ,           //second copy
                                                               new Country { Name = "France" } ,
                                                               new Country { Name = "Spain" } ,
                                                               new Country { Name = "Italy" } ,
                                                               new Country { Name = "Portugal" } ,
                                                               new Country { Name = "Belgium" } ,
                                                               new Country{Name = "USA", Currency="USD" }   //USA already exists, with different 
                                                                       //currency, but I don't care which one I get back
                                                              };
            //What I want, without using foreach or for loops, is a solely LINQ solution that
            //will create new list that contains the following in the following order


            //the bit I can't write but I think it may use LINQ extension methods .Except and .Union and .Sort and .Distinct
            List<Country> countryYouLiveIn = null; // <--- I CAN'T WRITE THIS BIT

            foreach(var c in countryYouLiveIn)
                Console.WriteLine(c.Name);
            // Would print out the following in this order
            //
            //      UK
            //      USA
            //      Austria
            //      Belguim
            //      Italy
            //      France
            //      Germany
            //      Portugal
            //      Spain
            //      Switzerland
            //
            //So topcountries if they exist are ordered alphabetically but first
            //and otherCountries are added afterwards, also alphabetised
        }

        static void Main(string[] args)
        {
            Program program = new Program();
            program.Run();
        }
    }
}

Open in new window

0
Comment
Question by:John Bolter
  • 6
  • 6
  • 3
  • +1
16 Comments
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 40487185
Hi John;

I don't see a pattern in which you use the two list, topCountries and  otherCountries, to create the new list countryYouLiveIn. Can you explain how to combine the two lists to get the new third list in words. Thanks
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40487242
Something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EE_Q28576422
{
	class Country
	{
		public String Name { get; set; }
		public String Currency { get; set; }
	}


	class Program
	{
		private static List<Country> topCountries = new List<Country> 
		{
			new Country{ Name =" USA", Currency = "$" }, 
			new Country{ Name = "UK" },
			new Country{ Name = "China", Currency = "Y" }
		};

		private static List<Country> otherCountries = new List<Country> 
		{ 
			new Country { Name = "Germany" }, 
			new Country { Name = "Austria" }, 
			new Country { Name = "Switzerland" }, 
			new Country { Name = "Austria" } ,           //second copy
			new Country { Name = "France" } ,
			new Country { Name = "Spain" } ,
			new Country { Name = "Italy" } ,
			new Country { Name = "Portugal" } ,
			new Country { Name = "Belgium" } ,
			new Country {Name = "USA", Currency="USD" }
		};

		static void Main(string[] args)
		{
			var countryYouLiveIn = (from country in topCountries.Concat(otherCountries)
					    group country by country.Name.Trim() into g
					    where g != null
					    select new Country() { Name = g.First().Name, Currency = g.First().Currency });
			foreach (var country in countryYouLiveIn)
				Console.WriteLine(country.Name);
			Console.ReadLine();
		}
	}
}

Open in new window


Produces the following output -Capture.JPG
-saige-
0
 

Author Comment

by:John Bolter
ID: 40487250
Hi Fernando
It is going to be a dropdown list to enable people to select the country they live in.
I want it alphabetically for all countries, but I want topCountries to appear at the top, regardless.
It's like when you go to Amazon to buy something. They list all the countries, but the USA and UK are always at the top.
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40487264
Well since you want ordering in the second list, we could do this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EE_Q28576422
{
	class Country
	{
		public String Name { get; set; }
		public String Currency { get; set; }
	}


	class Program
	{
		private static List<Country> topCountries = new List<Country> 
		{
			new Country{ Name =" USA", Currency = "$" }, 
			new Country{ Name = "UK" },
			new Country{ Name = "China", Currency = "Y" }
		};

		private static List<Country> otherCountries = new List<Country> 
		{ 
			new Country { Name = "Germany" }, 
			new Country { Name = "Austria" }, 
			new Country { Name = "Switzerland" }, 
			new Country { Name = "Austria" } ,           //second copy
			new Country { Name = "France" } ,
			new Country { Name = "Spain" } ,
			new Country { Name = "Italy" } ,
			new Country { Name = "Portugal" } ,
			new Country { Name = "Belgium" } ,
			new Country {Name = "USA", Currency="USD" }
		};

		static void Main(string[] args)
		{
			var countryYouLiveIn = (from country in topCountries.Concat(otherCountries.OrderBy(country => country.Name))
					    group country by country.Name.Trim() into g
					    where g != null
					    select g.FirstOrDefault());
			foreach (var country in countryYouLiveIn)
				Console.WriteLine(country.Name);
			Console.ReadLine();
		}
	}
}

Open in new window


Now produces the following output -Capture.JPG
0
 

Author Comment

by:John Bolter
ID: 40487279
Hi Saige, almost.

Only countries that are in the otherCountries list should be in List<String>. So China shouldn't be there because it isn't. The topCountries is only used for ordering of the first countries, so it would be (not China), then UK, then USA, then all the other countries alphabetised.
0
 

Author Comment

by:John Bolter
ID: 40487286
Saige, I just hit submit and then your second reply came through. It is absolutely what I want ordered now, except that China is in there. As China isn't in the otherCountries list, it shouldn't appear.
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40487288
Ok so that I understand.  The top countries should only appear if they are in the other countries list.  The other countries list is then ordered (by Name) and the top countries are subtracted from the other countries list.

Is that the gist of it?

-saige-
0
 

Author Comment

by:John Bolter
ID: 40487295
Absolutely, that is it!

When someone buys something on a website, I want to give them the option in a dropdown of entering any country, but Afghanistan, Albania, Algeria etc shouldn't be at the top, the UK and USA should be, alphabetised, and then all the other countries that are in the topCountries list should listed underneath, alphabetised too.
0
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 32

Assisted Solution

by:it_saige
it_saige earned 166 total points
ID: 40487335
Here you go.  (Note: According to your stipulation UK also will not be in the list as it does not appear in the second list):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EE_Q28576422
{
	class Country
	{
		public String Name { get; set; }
		public String Currency { get; set; }

		#region Overriden Methods
		public override bool Equals(object obj)
		{
			if (obj == null || (obj.GetType() != GetType()))
				return false;

			return this == (obj as Country);
		}

		public bool Equals(Country country)
		{
			if (object.ReferenceEquals(country, null))
				return false;

			return this == country;
		}

		public override int GetHashCode()
		{
			return Name != null ? Name.GetHashCode() : 0;
		}
		#endregion

		#region Operator Overloads
		public static bool operator ==(Country lhs, Country rhs)
		{
			if (object.ReferenceEquals(lhs, rhs))
				return true;

			if (object.ReferenceEquals(lhs, null) || object.ReferenceEquals(rhs, null))
				return false;

			return (lhs.Name != null && rhs.Name != null ? string.Compare(lhs.Name.Trim(), rhs.Name.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0 : false);
		}

		public static bool operator !=(Country lhs, Country rhs)
		{
			return !(lhs == rhs);
		}
		#endregion
	}


	class Program
	{
		private static List<Country> topCountries = new List<Country> 
		{
			new Country{ Name ="USA", Currency = "$" }, 
			new Country{ Name = "UK" },
			new Country{ Name = "China", Currency = "Y" }
		};

		private static List<Country> otherCountries = new List<Country> 
		{ 
			new Country { Name = "Germany" }, 
			new Country { Name = "Austria" }, 
			new Country { Name = "Switzerland" }, 
			new Country { Name = "Austria" } ,           //second copy
			new Country { Name = "France" } ,
			new Country { Name = "Spain" } ,
			new Country { Name = "Italy" } ,
			new Country { Name = "Portugal" } ,
			new Country { Name = "Belgium" } ,
			new Country {Name = "USA", Currency="USD" }
		};

		static void Main(string[] args)
		{
			var countryYouLiveIn = (from country in topCountries.Union(otherCountries.OrderBy(country => country.Name))
							    where otherCountries.Contains(country)
							    group country by country.Name into g
							    where g != null
							    select g.FirstOrDefault());
			foreach (var country in countryYouLiveIn)
				Console.WriteLine(country.Name);
			Console.ReadLine();
		}
	}
}

Open in new window


Produces the following output -Capture.JPG
-saige-
0
 
LVL 32

Expert Comment

by:it_saige
ID: 40487339
I modified the code to add UK to the second list and also added an ordering on the first list as well:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EE_Q28576422
{
	class Country
	{
		public String Name { get; set; }
		public String Currency { get; set; }

		#region Overriden Methods
		public override bool Equals(object obj)
		{
			if (obj == null || (obj.GetType() != GetType()))
				return false;

			return this == (obj as Country);
		}

		public bool Equals(Country country)
		{
			if (object.ReferenceEquals(country, null))
				return false;

			return this == country;
		}

		public override int GetHashCode()
		{
			return Name != null ? Name.GetHashCode() : 0;
		}
		#endregion

		#region Operator Overloads
		public static bool operator ==(Country lhs, Country rhs)
		{
			if (object.ReferenceEquals(lhs, rhs))
				return true;

			if (object.ReferenceEquals(lhs, null) || object.ReferenceEquals(rhs, null))
				return false;

			return (lhs.Name != null && rhs.Name != null ? string.Compare(lhs.Name.Trim(), rhs.Name.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0 : false);
		}

		public static bool operator !=(Country lhs, Country rhs)
		{
			return !(lhs == rhs);
		}
		#endregion
	}


	class Program
	{
		private static List<Country> topCountries = new List<Country> 
		{
			new Country{ Name ="USA", Currency = "$" }, 
			new Country{ Name = "UK" },
			new Country{ Name = "China", Currency = "Y" }
		};

		private static List<Country> otherCountries = new List<Country> 
		{ 
			new Country { Name = "Germany" }, 
			new Country { Name = "Austria" }, 
			new Country { Name = "Switzerland" }, 
			new Country { Name = "Austria" } ,           //second copy
			new Country { Name = "France" } ,
			new Country { Name = "Spain" } ,
			new Country { Name = "UK" } ,
			new Country { Name = "Italy" } ,
			new Country { Name = "Portugal" } ,
			new Country { Name = "Belgium" } ,
			new Country {Name = "USA", Currency="USD" }
		};

		static void Main(string[] args)
		{
			var countryYouLiveIn = (from country in topCountries.OrderBy(country => country.Name).Union(otherCountries.OrderBy(country => country.Name))
					    where otherCountries.Contains(country)
					    group country by country.Name.Trim() into g
					    where g != null
					    select g.FirstOrDefault());
			foreach (var country in countryYouLiveIn)
				Console.WriteLine(country.Name);
			Console.ReadLine();
		}
	}
}

Open in new window


Now produces the following output -Capture.JPG
-saige-
0
 
LVL 1

Accepted Solution

by:
Michael O'Shea earned 167 total points
ID: 40487477
John, this is not a trivial LINQ example. It has taken me more than 2 hours to write the few lines below. I am still not totally happy with my solution but am prepared to accept the wrath of others to move you along, or serve as prototype code for others to improve upon.

The key to the whole solution is:

an outer join on the country name between the two different country lists
prepending a string with '\0' or something else (greater that '\0') in an outer join for country name sorting ascending
a FirstOrDefault to remove duplicates

Regards

Mike

            var countryYouLiveIn = (
                                     from oc in otherCountries
                                       join tc in topCountries on oc.Name equals tc.Name into z
                                         from tc in z.DefaultIfEmpty()
                                           orderby (null == tc ? '\0' : '\t') + (tc ?? oc).Name ascending
                                             group oc by new { oc.Name }
                                               into groupDistinctOtherCountries
                                                 select groupDistinctOtherCountries.FirstOrDefault()
                                   );

Open in new window

0
 
LVL 32

Expert Comment

by:it_saige
ID: 40487558
One other edit in case you don't require "USA" or "UK" in the second list but always require them in the finished list:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EE_Q28576422
{
	class Country
	{
		public String Name { get; set; }
		public String Currency { get; set; }

		#region Overriden Methods
		public override bool Equals(object obj)
		{
			if (obj == null || (obj.GetType() != GetType()))
				return false;

			return this == (obj as Country);
		}

		public bool Equals(Country country)
		{
			if (object.ReferenceEquals(country, null))
				return false;

			return this == country;
		}

		public override int GetHashCode()
		{
			return Name != null ? Name.GetHashCode() : 0;
		}
		#endregion

		#region Operator Overloads
		public static bool operator ==(Country lhs, Country rhs)
		{
			if (object.ReferenceEquals(lhs, rhs))
				return true;

			if (object.ReferenceEquals(lhs, null) || object.ReferenceEquals(rhs, null))
				return false;

			return (lhs.Name != null && rhs.Name != null ? string.Compare(lhs.Name.Trim(), rhs.Name.Trim(), StringComparison.InvariantCultureIgnoreCase) == 0 : false);
		}

		public static bool operator !=(Country lhs, Country rhs)
		{
			return !(lhs == rhs);
		}
		#endregion
	}


	class Program
	{
		private static List<Country> topCountries = new List<Country> 
		{
			new Country{ Name =" USA", Currency = "$" }, 
			new Country{ Name = "UK" },
			new Country{ Name = "China", Currency = "Y" }
		};

		private static List<Country> otherCountries = new List<Country> 
		{ 
			new Country { Name = "Germany" }, 
			new Country { Name = "Austria" }, 
			new Country { Name = "Switzerland" }, 
			new Country { Name = "Austria" } ,           //second copy
			new Country { Name = "France" } ,
			new Country { Name = "Spain" } ,
			new Country { Name = "Italy" } ,
			new Country { Name = "Portugal" } ,
			new Country { Name = "Belgium" } ,
			new Country {Name = "USA", Currency="USD" }
		};

		static void Main(string[] args)
		{
			var countryYouLiveIn = (from country in topCountries.OrderBy(country => country.Name).Union(otherCountries.OrderBy(country => country.Name))
					    where country.Name.Trim().Equals("USA") || country.Name.Trim().Equals("UK") || otherCountries.Contains(country)
					    group country by country.Name.Trim() into g
					    where g != null
					    select g.FirstOrDefault());
			foreach (var country in countryYouLiveIn)
				Console.WriteLine(country.Name);
			Console.ReadLine();
		}
	}
}

Open in new window


Produces the following output -Capture.JPG
-saige-
0
 
LVL 62

Assisted Solution

by:Fernando Soto
Fernando Soto earned 167 total points
ID: 40487732
Hi John;

Try using this code to see if it gives you what you are looking for.

List<Country> topCountries = new List<Country> { 
                             new Country{ Name ="USA", Currency = "$" }, 
                             new Country{ Name = "UK" },
                             new Country{ Name = "China", Currency = "Y" }
                           };

List<Country> otherCountries = new List<Country> { 
                               new Country { Name = "Germany" }, 
                               new Country { Name = "Austria" }, 
                               new Country { Name = "Switzerland" }, 
                               new Country { Name = "UK" } ,         
                               new Country { Name = "France" } ,
                               new Country { Name = "Spain" } ,
                               new Country { Name = "Italy" } ,
                               new Country { Name = "Portugal" } ,
                               new Country { Name = "Belgium" } ,
                               new Country { Name ="USA", Currency = "$" }  
                              };

List<Country> modifyCountries1 = otherCountries.Except(topCountries).OrderBy(c => c.Name).ToList();   
List<Country> modifyCountries2 = topCountries.Intersect(otherCountries).OrderBy(c => c.Name).ToList();
List<Country> countryYouLiveIn = modifyCountries2.Concat(modifyCountries1).ToList();                  

foreach (var country in countryYouLiveIn)
    Console.WriteLine(country.Name);

Console.ReadLine();


// Modify your class as follows.
class Country : IEquatable<Country>
{
    public String Name { get; set; }
    public String Currency { get; set; }

    public bool Equals(Country other)
    {
        return this.Name == other.Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

Open in new window


Results
0
 

Author Comment

by:John Bolter
ID: 40487762
I understand your code Fernando and Saige but Mike's is the most simple under 10 lines long also looking like SQL. I just can't understand it though.
All of your codes work, Fernando, Saige, and Mike, thank you.
0
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 40487816
Hi John;

The code I posted is only 7 lines more then your original post.
0
 

Author Closing Comment

by:John Bolter
ID: 40487834
Yes, it is Fernando, 7 lines verses 10 lines.  I understand what your C# is doing too just by intuitively looking at your code, but Mike's all LINQ code does look really nice and concise doesn't it? It even looks simple, but I don't think it is. I couldn't have written this all LINQ example even though it is what I wanted and I still don't understand it.
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Introduction Although it is an old technology, serial ports are still being used by many hardware manufacturers. If you develop applications in C#, Microsoft .NET framework has SerialPort class to communicate with the serial ports.  I needed to…
We all know that functional code is the leg that any good program stands on when it comes right down to it, however, if your program lacks a good user interface your product may not have the appeal needed to keep your customers happy. This issue can…
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now