Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to return part of a list by reference?

Tags:

c#

list

My situation is that I want to assign a list only part of another list. And that if possible by reference.

What I have done so far is this here:

List<string> partialList = originalList.Skip(start).Take(end-start).ToList();

Example: A list with 6 elements and start being 2 and and end being 4.

In New List   Element
N             0
N             1
Y             2
Y             3
Y             4
N             5

Now as far as I understand .ToList() it creates a copy of the original results. Thus this would be by value and not by reference. So my question is: Is there any "by reference" way to achive the result I want?

like image 976
Thomas Avatar asked Sep 24 '15 08:09

Thomas


3 Answers

You could write your own slice class easily enough:

public class ReadOnlyListSlice<T> : IReadOnlyList<T>
{
    private readonly IReadOnlyList<T> _list;
    private readonly int _start;
    private readonly int _exclusiveEnd;

    public ReadOnlyListSlice(IReadOnlyList<T> list, int start, int exclusiveEnd)
    {
        _list = list;
        _start = start;
        _exclusiveEnd = exclusiveEnd;
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = _start; i <= _exclusiveEnd; ++i)
            yield return _list[i];
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int Count
    {
        get { return _exclusiveEnd - _start; }
    }

    public T this[int index]
    {
        get { return _list[index+_start]; }
    }
}

Usage:

List<int> ints = Enumerable.Range(1, 10).ToList();
var test = new ReadOnlyListSlice<int>(ints, 4, 7);

foreach (var i in test)
    Console.WriteLine(i); // 5, 6, 7, 8

Console.WriteLine();

for (int i = 1; i < 3; ++i)
    Console.WriteLine(test[i]); // 6, 7

You could also write a writable version, but then if you make it implement IList<T> you'll end up having to implement a LOT of methods that you'll probably not need to use.

However, if you don't mind it only implementing IReadOnlyList<T> (and by implication IEnumerable<T>) it's not so hard:

public class ListSlice<T> : IReadOnlyList<T>
{
    private readonly List<T> _list;
    private readonly int _start;
    private readonly int _exclusiveEnd;

    public ListSlice(List<T> list, int start, int exclusiveEnd)
    {
        _list = list;
        _start = start;
        _exclusiveEnd = exclusiveEnd;
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = _start; i <= _exclusiveEnd; ++i)
            yield return _list[i];
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int Count
    {
        get { return _exclusiveEnd - _start; }
    }

    public T this[int index]
    {
        get { return _list[index+_start]; }
        set { _list[index+_start] = value; }
    }
}

And to use:

List<int> ints = Enumerable.Range(1, 10).ToList();
var test = new ListSlice<int>(ints, 4, 7);

foreach (var i in test)
    Console.WriteLine(i); // 5, 6, 7, 8

Console.WriteLine();

test[2] = -1;

for (int i = 1; i < 4; ++i)
    Console.WriteLine(test[i]); // 6, -1, 8

Of course, the drawback of not implementing IList<T> is that you won't be able to pass a ListSlice<T> to a method expecting an IList<T>.

I leave the full implementation of public class ListSlice<T> : IList<T> to the proverbial "Interested Reader".


If you wanted to implement the equivalent of List<T>.FIndIndex() it's also quite simple. Just add this to either class:

public int FindIndex(int startIndex, int count, Predicate<T> match)
{
    for (int i = startIndex; i < startIndex + count; ++i)
        if (match(this[i]))
            return i;

    return -1;
}

Here's a complete compilable console app:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Demo
{
    public class ReadOnlyListSlice<T> : IReadOnlyList<T>
    {
        private readonly IReadOnlyList<T> _list;
        private readonly int _start;
        private readonly int _exclusiveEnd;

        public ReadOnlyListSlice(IReadOnlyList<T> list, int start, int exclusiveEnd)
        {
            _list = list;
            _start = start;
            _exclusiveEnd = exclusiveEnd;
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = _start; i <= _exclusiveEnd; ++i)
                yield return _list[i];
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public int Count
        {
            get { return _exclusiveEnd - _start; }
        }

        public T this[int index]
        {
            get { return _list[index + _start]; }
        }
    }

    public class ListSlice<T> : IReadOnlyList<T>
    {
        private readonly IList<T> _list;
        private readonly int _start;
        private readonly int _exclusiveEnd;

        public ListSlice(IList<T> list, int start, int exclusiveEnd)
        {
            _list = list;
            _start = start;
            _exclusiveEnd = exclusiveEnd;
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = _start; i <= _exclusiveEnd; ++i)
                yield return _list[i];
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public int Count
        {
            get { return _exclusiveEnd - _start; }
        }

        public T this[int index]
        {
            get { return _list[index+_start]; }
            set { _list[index+_start] = value; }
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var ints = Enumerable.Range(1, 10).ToList();

            Console.WriteLine("Readonly Demo\n");
            demoReadOnlySlice(ints);

            Console.WriteLine("\nWriteable Demo\n");
            demoWriteableSlice(ints);
        }

        private static void demoReadOnlySlice(List<int> ints)
        {
            var test = new ReadOnlyListSlice<int>(ints, 4, 7);

            foreach (var i in test)
                Console.WriteLine(i); // 5, 6, 7, 8

            Console.WriteLine();

            for (int i = 1; i < 4; ++i)
                Console.WriteLine(test[i]); // 6, 7, 8
        }

        private static void demoWriteableSlice(List<int> ints)
        {
            var test = new ListSlice<int>(ints, 4, 7);

            foreach (var i in test)
                Console.WriteLine(i); // 5, 6, 7, 8

            Console.WriteLine();

            test[2] = -1;

            for (int i = 1; i < 4; ++i)
                Console.WriteLine(test[i]); // 6, -1, 8
        }
    }
}
like image 161
Matthew Watson Avatar answered Oct 16 '22 16:10

Matthew Watson


It's possible using reflection and the ArraySegment class:

var originalList = Enumerable.Range(0, 6).ToList();

var innerArray = (int[])originalList.GetType().GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(originalList);
var partialList = (IList<int>)new ArraySegment<int>(innerArray, 2, 3);

partialList[0] = -99;
partialList[1] = 100;
partialList[2] = 123;

Console.WriteLine(String.Join(", ", originalList));

Output:

0, 1, -99, 100, 123, 5

Note that this depends on implemention details (the private _items field in the List<> class), so it's not future proof to use. Also, this will fail if you add a couple items to the original list (the _items member will get replaced with a new array). Thanks @IvanStoev for mention it.

This wouldn't be an issue if your original collection would be a plain array.

like image 23
sloth Avatar answered Oct 16 '22 15:10

sloth


You can simply use Where method with lambda that accepts item index as its second parameter :

    var arr = Enumerable.Range(0, 60);

    var subSequence = arr.Where((e, i) => i >= 20 && i <= 27);

    foreach (var item in subSequence) Console.Write(item + "  ");

Output: 20, 21, 22, 23, 24, 25, 26, 27

like image 2
Fabjan Avatar answered Oct 16 '22 15:10

Fabjan