5

I'm trying to find C#'s equivalent of Julia's map!() method, which is of void return type and takes a function, a destination and a collection on which the function acts.

The best thing that I could find is C#'s Enumerable.Select(), which takes the function as the third argument, and the collection as the first argument. However, it returns a new collection instead of modifying the one in the "destination". That resembles Julia's map() more.

  • 1
    `.Select()` is a LINQ extension method, which is a query, which does not alter the source by convention. You have to assign the result or do a `.ToList().ForEach(x => ....);` – Falco Alexander Jul 17 '20 at 11:57
  • 5
    @FalcoAlexander `ToList().ForEach` is an abomination. If you need to iterate a query just do `foreach(var x in query)` instead vs iterating the query to create an intermediate list just to then iterate the list and throw it away. – juharr Jul 17 '20 at 12:01
  • you're right, agree!! – Falco Alexander Jul 17 '20 at 12:02
  • Ultimately rather than trying to recreate how Julia works in C#, you'll be better off just learning how to write code how C# works. In this case it seems like `destination = source.Select(func).ToList();` is what you want. This is actually better than `map!()` as you don't need to create the collection up front or make sure it's large enough, two things that I would expect would cause all kinds of bugs. – juharr Jul 17 '20 at 12:42
  • 1
    The reason for having a mutating map function is that you may want to reuse already allocated memory for performance reasons. Allocating a new output array might be wasteful if done in each iteration in a hot loop. Non mutating map is simply `map` in Julia. – Fredrik Bagge Jul 17 '20 at 12:54

2 Answers2

2

There's nothing as standard like this, but you can easily add your own extension method to IEnumerable to add this functionality. For example:

public static void JuliaMap<TFrom, TTo>
(
    this IEnumerable<TFrom> source, 
    IList<TTo> target, 
    Func<TFrom, TTo> selector
)
{
    var next = 0;
    foreach(var value in source)
    {
        var convertedValue = selector(value);
        target[next] = convertedValue;
        next++;
    }
}

How you can say:

var numbers = new[]{1, 2, 3};
var target = new string[3];

numbers.JuliaMap(target, i => (i * 2).ToString());

NOTE: I've left out any error handling. For example, you'll want to make sure that the target list is long enough to take the inserted value.

Sean
  • 60,939
  • 11
  • 97
  • 136
1

Everything in LINQ is by design meant to never modify the underlying collection and to always create a new enumeration that is generally then used to instantiate a new collection.

You can write a helper function like this to achieve what you want:

public static void SelectToDestination<TSource, TResult>(
    Func<TSource, TResult> selector, 
    IEnumerable<TSource> source, 
    IList<TResult> destination)
{
    int i = 0;
    foreach (var item in source.Select(selector))
    {
        destination[i] = item;
        i++;
    }
}

Usage looks like this:

var l1 = new List<int>() { 1, 2, 3, 4 };
var l2 = new List<int>() { 0, 0, 0, 0 };
SelectToDestination(x => x + 2, l1, l2);

foreach(var item in l2)
{
    Console.Write(item + " ");
}

Results in: 3 4 5 6

Since we're using IList<T> in the method's signature, the destination can be an array too and it will work fine:

public static void Main(string[] args)
{
    var l1 = new List<int>() { 1, 2, 3, 4 };
    var l2 = new int[4];
    SelectToDestination(x => x + 2, l1, l2);

    foreach(var item in l2)
    {
        Console.Write(item + " ");
    }
}

It takes advantage of the fact that until you call something like ToArray() or ToList(), LINQ hasn't yet instantiated a new collection, it's just iterating lazily over elements in the source collection. So just don't call ToArray() or ToList(), iterate over the resulting IEnumerable<TResult> and assign it to destination. Note that there are probably more perf friendly ways of doing this if that's a concern for you.

Just like Julia's map method, this will only work if the destination collection is at least as big.

AnLog
  • 796
  • 1
  • 7
  • 15