Avatar of artemkorsakov

artemkorsakov's solution

to List Ops in the C# Track

Published at May 03 2019 · 0 comments
Instructions
Test suite
Solution

Implement basic list operations.

In functional languages list operations like length, map, and reduce are very common. Implement a series of basic list operations, without using existing functions.

The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include:

  • append (given two lists, add all items in the second list to the end of the first list);
  • concatenate (given a series of lists, combine all items in all lists into one flattened list);
  • filter (given a predicate and a list, return the list of all items for which predicate(item) is True);
  • length (given a list, return the total number of items within it);
  • map (given a function and a list, return the list of the results of applying function(item) on all items);
  • foldl (given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using function(accumulator, item));
  • foldr (given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using function(item, accumulator));
  • reverse (given a list, return a list with all the original items, but in reversed order);

Hints

The Foldl and Foldr methods are "fold" functions, which is a concept well-known in the functional programming world, but less so in the object-oriented one. If you'd like more background information, check out this fold page.

Running the tests

To run the tests, run the command dotnet test from within the exercise directory.

Initially, only the first test will be enabled. This is to encourage you to solve the exercise one step at a time. Once you get the first test passing, remove the Skip property from the next test and work on getting that test passing. Once none of the tests are skipped and they are all passing, you can submit your solution using exercism submit ListOps.cs

Further information

For more detailed information about the C# track, including how to get help if you're having trouble, please visit the exercism.io C# language page.

ListOpsTest.cs

// This file was auto-generated based on version 2.4.0 of the canonical data.

using System;
using System.Collections.Generic;
using Xunit;

public class ListOpsTest
{
    [Fact]
    public void Append_entries_to_a_list_and_return_the_new_list_empty_lists()
    {
        var list1 = new List<int>();
        var list2 = new List<int>();
        Assert.Empty(ListOps.Append(list1, list2));
    }

    [Fact(Skip = "Remove to run test")]
    public void Append_entries_to_a_list_and_return_the_new_list_empty_list_to_list()
    {
        var list1 = new List<int>();
        var list2 = new List<int> { 1, 2, 3, 4 };
        var expected = new List<int> { 1, 2, 3, 4 };
        Assert.Equal(expected, ListOps.Append(list1, list2));
    }

    [Fact(Skip = "Remove to run test")]
    public void Append_entries_to_a_list_and_return_the_new_list_non_empty_lists()
    {
        var list1 = new List<int> { 1, 2 };
        var list2 = new List<int> { 2, 3, 4, 5 };
        var expected = new List<int> { 1, 2, 2, 3, 4, 5 };
        Assert.Equal(expected, ListOps.Append(list1, list2));
    }

    [Fact(Skip = "Remove to run test")]
    public void Concatenate_a_list_of_lists_empty_list()
    {
        var lists = new List<List<int>>();
        Assert.Empty(ListOps.Concat(lists));
    }

    [Fact(Skip = "Remove to run test")]
    public void Concatenate_a_list_of_lists_list_of_lists()
    {
        var lists = new List<List<int>> { new List<int> { 1, 2 }, new List<int> { 3 }, new List<int>(), new List<int> { 4, 5, 6 } };
        var expected = new List<int> { 1, 2, 3, 4, 5, 6 };
        Assert.Equal(expected, ListOps.Concat(lists));
    }

    [Fact(Skip = "Remove to run test")]
    public void Concatenate_a_list_of_lists_list_of_nested_lists()
    {
        var lists = new List<List<List<int>>> { new List<List<int>> { new List<int> { 1 }, new List<int> { 2 } }, new List<List<int>> { new List<int> { 3 } }, new List<List<int>> { new List<int>() }, new List<List<int>> { new List<int> { 4, 5, 6 } } };
        var expected = new List<List<int>> { new List<int> { 1 }, new List<int> { 2 }, new List<int> { 3 }, new List<int>(), new List<int> { 4, 5, 6 } };
        Assert.Equal(expected, ListOps.Concat(lists));
    }

    [Fact(Skip = "Remove to run test")]
    public void Filter_list_returning_only_values_that_satisfy_the_filter_function_empty_list()
    {
        var list = new List<int>();
        var function = new Func<int, bool>((x) => x % 2 == 1);
        Assert.Empty(ListOps.Filter(list, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Filter_list_returning_only_values_that_satisfy_the_filter_function_non_empty_list()
    {
        var list = new List<int> { 1, 2, 3, 5 };
        var function = new Func<int, bool>((x) => x % 2 == 1);
        var expected = new List<int> { 1, 3, 5 };
        Assert.Equal(expected, ListOps.Filter(list, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Returns_the_length_of_a_list_empty_list()
    {
        var list = new List<int>();
        Assert.Equal(0, ListOps.Length(list));
    }

    [Fact(Skip = "Remove to run test")]
    public void Returns_the_length_of_a_list_non_empty_list()
    {
        var list = new List<int> { 1, 2, 3, 4 };
        Assert.Equal(4, ListOps.Length(list));
    }

    [Fact(Skip = "Remove to run test")]
    public void Return_a_list_of_elements_whose_values_equal_the_list_value_transformed_by_the_mapping_function_empty_list()
    {
        var list = new List<int>();
        var function = new Func<int, int>((x) => x + 1);
        Assert.Empty(ListOps.Map(list, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Return_a_list_of_elements_whose_values_equal_the_list_value_transformed_by_the_mapping_function_non_empty_list()
    {
        var list = new List<int> { 1, 3, 5, 7 };
        var function = new Func<int, int>((x) => x + 1);
        var expected = new List<int> { 2, 4, 6, 8 };
        Assert.Equal(expected, ListOps.Map(list, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Folds_reduces_the_given_list_from_the_left_with_a_function_empty_list()
    {
        var list = new List<int>();
        var initial = 2;
        var function = new Func<int, int, int>((x, y) => x * y);
        Assert.Equal(2, ListOps.Foldl(list, initial, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Folds_reduces_the_given_list_from_the_left_with_a_function_direction_independent_function_applied_to_non_empty_list()
    {
        var list = new List<int> { 1, 2, 3, 4 };
        var initial = 5;
        var function = new Func<int, int, int>((x, y) => x + y);
        Assert.Equal(15, ListOps.Foldl(list, initial, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Folds_reduces_the_given_list_from_the_left_with_a_function_direction_dependent_function_applied_to_non_empty_list()
    {
        var list = new List<int> { 2, 5 };
        var initial = 5;
        var function = new Func<int, int, int>((x, y) => x / y);
        Assert.Equal(0, ListOps.Foldl(list, initial, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Folds_reduces_the_given_list_from_the_right_with_a_function_empty_list()
    {
        var list = new List<int>();
        var initial = 2;
        var function = new Func<int, int, int>((x, y) => x * y);
        Assert.Equal(2, ListOps.Foldr(list, initial, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Folds_reduces_the_given_list_from_the_right_with_a_function_direction_independent_function_applied_to_non_empty_list()
    {
        var list = new List<int> { 1, 2, 3, 4 };
        var initial = 5;
        var function = new Func<int, int, int>((x, y) => x + y);
        Assert.Equal(15, ListOps.Foldr(list, initial, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Folds_reduces_the_given_list_from_the_right_with_a_function_direction_dependent_function_applied_to_non_empty_list()
    {
        var list = new List<int> { 2, 5 };
        var initial = 5;
        var function = new Func<int, int, int>((x, y) => x / y);
        Assert.Equal(2, ListOps.Foldr(list, initial, function));
    }

    [Fact(Skip = "Remove to run test")]
    public void Reverse_the_elements_of_the_list_empty_list()
    {
        var list = new List<int>();
        Assert.Empty(ListOps.Reverse(list));
    }

    [Fact(Skip = "Remove to run test")]
    public void Reverse_the_elements_of_the_list_non_empty_list()
    {
        var list = new List<int> { 1, 3, 5, 7 };
        var expected = new List<int> { 7, 5, 3, 1 };
        Assert.Equal(expected, ListOps.Reverse(list));
    }

    [Fact(Skip = "Remove to run test")]
    public void Reverse_the_elements_of_the_list_list_of_lists_is_not_flattened()
    {
        var list = new List<List<int>> { new List<int> { 1, 2 }, new List<int> { 3 }, new List<int>(), new List<int> { 4, 5, 6 } };
        var expected = new List<List<int>> { new List<int> { 4, 5, 6 }, new List<int>(), new List<int> { 3 }, new List<int> { 1, 2 } };
        Assert.Equal(expected, ListOps.Reverse(list));
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

public static class ListOps
{
    public static int Length<T>(List<T> input) => input.Count;

    public static List<T> Reverse<T>(List<T> input)
    {
        var result = new List<T>(input);
        result.Reverse();
        return result;
    }

    public static List<TOut> Map<TIn, TOut>(List<TIn> input, Func<TIn, TOut> map) =>
        input.Select(i => map(i)).ToList();

    public static List<T> Filter<T>(List<T> input, Func<T, bool> predicate) =>
        input.Where(i => predicate(i)).ToList();

    public static TOut Foldl<TIn, TOut>(List<TIn> input, TOut start, Func<TOut, TIn, TOut> func)
    {
        foreach (var current in input)
        {
            start = func(start, current);
        }

        return start;
    }

    public static TOut Foldr<TIn, TOut>(List<TIn> input, TOut start, Func<TIn, TOut, TOut> func)
    {
        input.Reverse();
        foreach (var current in input)
        {
            start = func(current, start);
        }

        return start;
    }

    public static List<T> Concat<T>(List<List<T>> input)
    {
        var result = new List<T>();
        foreach (var value in input)
        {
            result.AddRange(value);
        }
        return result;
    }

    public static List<T> Append<T>(List<T> left, List<T> right)
    {
        var result = new List<T>(left);
        result.AddRange(right);
        return result;
    }
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?