Really complicated no for or foreach loop LINQ question

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

John BolterAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Fernando SotoRetiredCommented:
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
it_saigeDeveloperCommented:
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
John BolterAuthor Commented:
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
Python 3 Fundamentals

This course will teach participants about installing and configuring Python, syntax, importing, statements, types, strings, booleans, files, lists, tuples, comprehensions, functions, and classes.

it_saigeDeveloperCommented:
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
John BolterAuthor Commented:
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
John BolterAuthor Commented:
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
it_saigeDeveloperCommented:
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
John BolterAuthor Commented:
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
it_saigeDeveloperCommented:
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
it_saigeDeveloperCommented:
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
Michael O'SheaHonorary Research Associate, Division of Surgery & Interventional ScienceCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
it_saigeDeveloperCommented:
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
Fernando SotoRetiredCommented:
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
John BolterAuthor Commented:
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
Fernando SotoRetiredCommented:
Hi John;

The code I posted is only 7 lines more then your original post.
0
John BolterAuthor Commented:
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
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C#

From novice to tech pro — start learning today.