Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

Really complicated no for or foreach loop LINQ question

Posted on 2014-12-08
16
Medium Priority
?
156 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 6
  • 6
  • 3
  • +1
16 Comments
 
LVL 64

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 34

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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 34

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 34

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
 
LVL 34

Assisted Solution

by:it_saige
it_saige earned 664 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 34

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 668 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 34

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 64

Assisted Solution

by:Fernando Soto
Fernando Soto earned 668 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 64

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

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Entity Framework is a powerful tool to help you interact with the DataBase but still doesn't help much when we have a Stored Procedure that returns more than one resultset. The solution takes some of out-of-the-box thinking; read on!
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
This course is ideal for IT System Administrators working with VMware vSphere and its associated products in their company infrastructure. This course teaches you how to install and maintain this virtualization technology to store data, prevent vuln…
In this video, Percona Solution Engineer Dimitri Vanoverbeke discusses why you want to use at least three nodes in a database cluster. To discuss how Percona Consulting can help with your design and architecture needs for your database and infras…

704 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