11

I am trying to process some list with a functional approach in C#.

The idea is that I have a collection of Tuple<T,double> and I want to change the Item 2 of some element T.

The functional way to do so, as data is immutable, is to take the list, filter for all elements where the element is different from the one to change, and the append a new tuple with the new values.

My problem is that I do not know how to append the element at the end. I would like to do:

public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
   return collection.Where(x=>!x.Item1.Equals(term)).Append(Tuple.Create(term,value));
}

But there is no Append method. Is there something else?

SRKX
  • 1,806
  • 1
  • 21
  • 42
  • possible duplicate of [Using a linq or lambda expression in C# return a collection plus a single value](http://stackoverflow.com/questions/2596317/using-a-linq-or-lambda-expression-in-c-sharp-return-a-collection-plus-a-single-v) – AakashM Nov 18 '11 at 11:16
  • see also http://stackoverflow.com/questions/521893/whats-the-best-name-for-a-non-mutating-add-method-on-an-immutable-collection – AakashM Nov 18 '11 at 11:19
  • agreed. Although I was expecting a solution without the overhead of creating a new list. – SRKX Nov 18 '11 at 11:59

7 Answers7

11

I believe you are looking for the Concat operator.

It joins two IEnumerable<T> together, so you can create one with a single item to join.

public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
   var newItem = new List<Tuple<T,double>>();
   newItem.Add(new Tuple<T,double>(term,value));
   return collection.Where(x=>!x.Item1.Equals(term)).Concat(newItem).ToList();
}
Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • but concat requires another IEnumerable, which means instantiating a new list which is a bit of an overkill isn't it? – SRKX Nov 18 '11 at 09:46
  • You need to make the Method Generic, and also call ToList in the return statement. Works fine after that. – Ray Nov 18 '11 at 09:57
6

It seems that .NET 4.7.1 adds Append LINQ operator, which is exactly what you want. Unlike Concat it takes a single value.

By the way, if you declare a generic method you should include type parameter(s) after its name:

public List<Tuple<T, double>> Replace<T>(List<Tuple<T, double>> collection, T term, double value)                                     
{
    return collection.Where(x => !x.Item1.Equals(term))
                     .Append(Tuple.Create(term, value))
                     .ToList();
}
w.b
  • 11,026
  • 5
  • 30
  • 49
4

LINQ is not for mutation. Functional programming avoid mutation.

Thus:

public IEnumerable<Tuple<T,double>> Extend(IEnumerable<Tuple<T,double>> collection, 
   T term,double value)
{
   foreach (var x in collection.Where(x=>!x.Item1.Equals(term)))
   {
     yield return x;
   }
   yield return Tuple.Create(term,value);
}
leppie
  • 115,091
  • 17
  • 196
  • 297
  • 2
    my `Append` function is not looking to mutate the list. It's just appending the element to a new list and returns the new list. – SRKX Nov 18 '11 at 09:47
3

If you're willing to use an additional package, check out MoreLinq, available on Nuget. This provides a new overload to the Concat-Function:

public static IEnumerable<T> Concat<T>(this IEnumerable<T> head, T tail);

This function does exactly what was asked for, e.g. you could do

var myEnumerable = Enumerable.Range(10, 3); // Enumerable of values 10, 11, 12
var newEnumerable = myEnumerable.Concat(3); // Enumerable of values 10, 11, 12, 3

And, if you like LINQ, you will probably like a lot of the other new functions, too!

Additionally, as pointed out in a discussion on the MoreLinq Github-page, the function

public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element);

with a different name but the same functionality is available in .NET Core, so it might be possible that we will see it for C# in the future.

Timitry
  • 2,646
  • 20
  • 26
1

One way is to use .Concat(), but you need to have a enumerable rather than a single item as the second argument. To create an array with a single element does work, but is combersome to write.

It is better to write an custom extension method to do so.

One method is to create a new List<T> and add the items from the first list and then the items from the second list. However, it is better to use the yield-keyword instead, so you do not need to create an list and the enumerable will be evaluated in a lazy fashion:

public static class EnumerableExtensions
{
    public static IEnumerable<T> Concat<T>(this IEnumerable<T> list, T item)
    {
        foreach (var element in list)
        {
            yield return element;
        }
        yield return item;
    }
}
MovGP0
  • 7,267
  • 3
  • 49
  • 42
1

This should do what you want (although it uses mutation inside, it feels functional from a callers perspective):

public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
  var result = collection.Where(x => !x.Item1.Equals(term)).ToList();
  result.Add(Tuple.Create(term, value));
  return result;
}

A alternative way to do it is to use "map" (select in LINQ):

public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
  return collection.Select(x => 
    Tuple.Create(
      x.Item1, 
      x.Item1.Equals(term) ? value : x.Item2)).ToList();
}

But it might give you different results than your original intention. Although, to me, that's what I think when I see a method called Replace, which is, replace-in-place.

UPDATE

You can also create what you want like this:

public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
  return collection.
    Where(x => !x.Item1.Equals(term)).
    Append(Tuple.Create(term, value)).
    ToList();
}

Using Concat, as mentioned by Oded:

public static class EnumerableEx {
  public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item) {
    return source.Concat(new T[] { item });
  }
}
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • why is it not functional? your answer certainly isn't I but what's wrong with my ideal `Append` method I mentioned in the question? – SRKX Nov 18 '11 at 15:40
  • 1
    OK, I get it, your append is just creating a new list with a new element at the end. That's functional all right. I'll just remove my comment about "more functional", will change it to "an alternative". Thanks for clarifying. – Jordão Nov 18 '11 at 15:42
0

The closest answer I could find came from this post and is:

return collection.Where(x=>!x.Item1.Equals(term)).Concat(new[]{Tuple.Create(term,value)});
Community
  • 1
  • 1
SRKX
  • 1,806
  • 1
  • 21
  • 42