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"
};
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"
},
...
]
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]
Given we have the values of 1,2, and 3, we want the following result.
[
[1, 2],
[2, 3],
[3, 1]
]
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]
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;
}
}
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();
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
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;
}
}
}
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.