DEV Community

Khalid Abuhakmeh
Khalid Abuhakmeh

Posted on • Originally published at khalidabuhakmeh.com on

Using LINQ to Build A World Travel Itinerary

I enjoy traveling, having new experiences, and making new friends. With so many locations to get to, it is challenging to keep all of them straight. In this post, I’ll show you how to use Language Integrated Query, or LINQ, to take a list of world locations and link them together into travel legs. This technique is powerful and is useful for other scenarios.

The Problem

Imagine you have a list of locations from around the world. You want to fly to each location and map each leg of your travel. Let’s first look at the sites.

var destinations = new List<string>
{
    "Harrisburg, Pennsylvania, USA",
    "London, England",
    "Amsterdam, Netherlands",
    "Paris, France",
    "Rome, Italy",
    "Shanghai, China",
    "Tokyo, Japan",
    "Sydney, Australia",
    "Maui, Hawaii, USA",
    "Los Angeles, California, USA"
};
Enter fullscreen mode Exit fullscreen mode

We want to generate a list of travel legs from one location to the other. For example, you could start at Harrisburg, Pennsylvania, USA and fly to the next immediate location. In our case, that would be London, England.

[
  {
    legNumber: 1,
    start : "Harrisburg, Pennsylvania, USA",
    end: "London, England"
  },
  {
    legNumber: 2,
    start : "London, England",
    end: "Amsterdam, Netherlands"
  },
  ...
]
Enter fullscreen mode Exit fullscreen mode

Our final destination should bring us back home to the first site. How do we accomplish this in C#?

The Solution: Shift and Zip To The Rescue

The first step to solving our problem is realizing that we need essentially two lists. We can visualize the problem easier if we start with a more straightforward numeric dataset.

[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Given we have the values of 1,2, and 3, we want the following result.

[
  [1, 2],
  [2, 3], 
  [3, 1]
]
Enter fullscreen mode Exit fullscreen mode

To get to our result, we need two lists with identical values, but with the first list shifted by one value.

[1, 2, 3]
[2, 3, 1]
Enter fullscreen mode Exit fullscreen mode

We can accomplish this by using a Shift extension method.

// https://stackoverflow.com/a/18181243
public static class ShiftList
{
    public static List<T> Shift<T>(this List<T> list, int shiftBy)
    {
        if (list.Count <= shiftBy)
        {
            return list;
        }

        var result = list.GetRange(shiftBy, list.Count-shiftBy);
        result.AddRange(list.GetRange(0,shiftBy));
        return result;
    }
}
Enter fullscreen mode Exit fullscreen mode

Once we have two lists, we can use LINQ’s Zip method to pair the two list values together into one enumerable.

var shifted = destinations.Shift(1).ToList();
var legs =
    destinations
        // using ZIP to pair the first and second lists here
        .Zip(shifted, (first, second) => new { first , second })
        .Select((leg, i) => new
        {
            legNumber = i + 1, 
            start = leg.first, 
            end = leg.second
        })
        .ToList();
Enter fullscreen mode Exit fullscreen mode

Let’s look at our results!

1.) Harrisburg, Pennsylvania, USA -> London, England
2.) London, England -> Amsterdam, Netherlands
3.) Amsterdam, Netherlands -> Paris, France
4.) Paris, France -> Rome, Italy
5.) Rome, Italy -> Shanghai, China
6.) Shanghai, China -> Tokyo, Japan
7.) Tokyo, Japan -> Sydney, Australia
8.) Sydney, Australia -> Maui, Hawaii, USA
9.) Maui, Hawaii, USA -> Los Angeles, California, USA
10.) Los Angeles, California, USA -> Harrisburg, Pennsylvania, USA
Enter fullscreen mode Exit fullscreen mode

And here is the full program.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var destinations = new List<string>
            {
                "Harrisburg, Pennsylvania, USA",
                "London, England",
                "Amsterdam, Netherlands",
                "Paris, France",
                "Rome, Italy",
                "Shanghai, China",
                "Tokyo, Japan",
                "Sydney, Australia",
                "Maui, Hawaii, USA",
                "Los Angeles, California, USA"
            };

            var shifted = destinations.Shift(1).ToList();
            var legs =
                destinations
                    .Zip(shifted, (first, second) => new { first , second })
                    .Select((leg, i) => new
                    {
                        legNumber = i + 1, 
                        start = leg.first, 
                        end = leg.second
                    })
                    .ToList();

            foreach (var leg in legs)
            {
                Console.WriteLine($"{leg.legNumber}.) {leg.start} -> {leg.end}");
            }
        }
    }

    // https://stackoverflow.com/a/18181243
    public static class ShiftList
    {
        public static List<T> Shift<T>(this List<T> list, int shiftBy)
        {
            if (list.Count <= shiftBy)
            {
                return list;
            }

            var result = list.GetRange(shiftBy, list.Count-shiftBy);
            result.AddRange(list.GetRange(0,shiftBy));
            return result;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

You can use LINQ’s Zip method to tie to lists together into something new. In our case, we wanted to travel from one item to the other in a connected fashion. This technique is robust for different use cases as well. Recently, I used this technique to get the distance between two headers in a command-line output. I hope you found this post a fun exploration into LINQ and playing with collections.

Top comments (0)