Start learning free

LESSON 273 OF 674 · TUPLES, SORTING & BUILT-INS

Zip and Indexes in C#: Select with an Index and Zip

Lesson Read the full explanation

TL ; DR:

Select can hand you each item's index. Zip walks two lists side by side.


List<string> fruits = new List<string>(new[] {"apple", "kiwi", "plum"});

List<int> prices = new List<int>(new[] {3, 1, 2});

List<string> numbered = fruits.Select((fruit, i) => (i + 1) + ". " + fruit).ToList();
// numbered holds "1. apple" and "2. kiwi" and "3. plum"

foreach (var (fruit, price) in fruits.Zip(prices)) {
    Console.WriteLine(fruit + " " + price);   // apple 3, kiwi 1, plum 2
}


Full lesson:

You have numbered items with a for loop and i + 1. LINQ has two shortcuts for this kind of work.

You met Select in LINQ: Where & Select. Its lambda can take a second parameter. That one is the item's index.

List<string> numbered = fruits.Select((fruit, i) => (i + 1) + ". " + fruit).ToList();

The item comes first and the index second. The index starts at 0. Add 1 for a list that people read.

a.Zip(b) walks two lists side by side. It pairs the first item of a with the first item of b. Then it pairs the second items, and so on.

Each pair is a tuple. Its parts are named First and Second. A foreach can unpack it into two names, as you did with tuples.

foreach (var (fruit, price) in fruits.Zip(prices)) {
    Console.WriteLine(fruit + " costs " + price);
}

Zip can also build the new values for you. Give it a lambda with two parameters, one from each list.

List<string> tags = fruits.Zip(prices, (fruit, price) => fruit + " " + price).ToList();

Like Select, Zip gives a sequence, not a list. .ToList() turns it into a List.

The two work together. Zip first, then number the results with Select and its index.

Zip stops at the end of the shorter list. The extra items are skipped, with no error.


Your task

A quiz show keeps its players in the List players. Their points are in scores, in the same order.

First, build numbered with each player and a number from 1, like 1. Ana. Use Select with the index. The code already prints numbered.

Then loop over players.Zip(scores). Print each player with their points, like Ana has 12 points.

Expected output:

1. Ana
2. Ben
3. Cleo
Ana has 12 points
Ben has 9 points
Cleo has 15 points
STEP 1 OF 3
    Ctrl + Enter to check

    C# · TUPLES, SORTING & BUILT-INS

    Enumerate And Zip in C#, explained

    Two LINQ shortcuts

    Two jobs come up when you work with lists: numbering the items, and walking through two lists at the same time. You could write both with a for loop and an index. LINQ has shorter forms.

    Select with an index

    The lambda you give to Select can take a second parameter. The first is the item, the second is its position in the list:

    List<string> fruits = new List<string>(new[] {"apple", "kiwi", "plum"});
    
    List<string> numbered = fruits.Select((fruit, i) => (i + 1) + ". " + fruit).ToList();
    

    Now numbered holds 1. apple, 2. kiwi and 3. plum. The item comes first and the index second. The index starts at 0, so add 1 for a list that people read. Two parameters mean the lambda needs parentheses: (fruit, i) => ....

    Zip: two lists side by side

    a.Zip(b) pairs the first item of a with the first item of b, then the second items, and so on. Each pair is a tuple with parts named First and Second, and a foreach can unpack it into two names, the way you unpack any tuple:

    List<int> prices = new List<int>(new[] {3, 1, 2});
    
    foreach (var (fruit, price) in fruits.Zip(prices)) {
        Console.WriteLine(fruit + " " + price);
    }
    
    apple 3
    kiwi 1
    plum 2
    

    Without unpacking, each pair prints like a tuple, for example (apple, 3), and its parts are pair.First and pair.Second.

    Zip can also build the new values for you. Give it a second argument, a lambda with one parameter from each list:

    List<string> tags = fruits.Zip(prices, (fruit, price) => fruit + " " + price).ToList();
    

    That gives apple 3, kiwi 1 and plum 2 as a list of strings. As with Select, the result is a sequence, so ToList() turns it into a List.

    The two methods work well together. Zip first to build pairs, then number the results with Select and its index. That is the pattern behind a numbered list of names and values, such as 1. apple: 3.

    Both calls are lazy, like all LINQ chains: nothing is computed until a loop or ToList() reads the result. That is why each example ends with one of them.

    Gotchas

    • Zip stops at the shorter list. Zipping the three fruits with {1, 2} gives only (apple, 1) and (kiwi, 2). The extra fruit is skipped and nothing is thrown.
    • Index zero. The first item has index 0. Numbering from 1 means i + 1.
    • Parentheses. A foreach over pairs unpacks with var (a, b) in parentheses, not var a, b.

    The lesson's task

    A quiz show keeps its players in players and their points in scores, in the same order. First build numbered with Select and the index, like 1. Ana. The code already prints it. Then loop over players.Zip(scores), unpack each pair into player and points, and print Ana has 12 points. No index is needed in the second loop, because Zip keeps each player with their own points.

    class Solution {
        public void MainFunction() {
            List<string> players = new List<string>(new[] {"Ana", "Ben", "Cleo"});
            List<int> scores = new List<int>(new[] {12, 9, 15});
    
            List<string> numbered = players.Select((player, i) => (i + 1) + ". " + player).ToList();
            foreach (string line in numbered) {
                Console.WriteLine(line);
            }
    
            foreach (var (player, points) in players.Zip(scores)) {
                Console.WriteLine(player + " has " + points + " points");
            }
        }
    }
    

    Running it prints:

    1. Ana
    2. Ben
    3. Cleo
    Ana has 12 points
    Ben has 9 points
    Cleo has 15 points
    

    Step by step

    1. Build numbered with players.Select((player, i) => ...), giving i + 1, a dot, a space and the player, then ToList().
    2. Write the second foreach over players.Zip(scores), with var (player, points) to unpack each pair.
    3. Print the player, the word has, the points and the word points.

    Key takeaways

    • Select((item, i) => ...) gives each item and its index, starting at 0.
    • a.Zip(b) pairs items by position, and var (x, y) unpacks each pair.
    • Zip ends with the shorter list, and both methods need .ToList() when you want a list.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free