2

I have the following LINQ statement:

IEnumerable<Statement> statement = bookmarkCollection.AsEnumerable().Select(
bookmark => new Statement()
{
    Title = bookmark.Title,
    PageNumber = bookmark.PageNumber
});

Statement has another attribute called NextPageNumber that I need to be able to populate. NextPageNumber is equal to the PageNumber of the next record minus 1. Esentially, something like this:

IEnumerable<Statement> statement = bookmarkCollection.AsEnumerable().Select(
bookmark => new Statement()
{
    Title= bookmark.Title,
    PageNumber = bookmark.PageNumber,
    NextPageNumber = ???
});

UPDATE:

I attempted some of the solutions provided, but I am stil on .NET 3.5 so the Tuple method is out. The Zip operation works (I have extension methods that simulate Zip for 3.5), but it does not create a Statement for the last Bookmark. The NextPageNumber for the last bookmark would simply be the number of pages in the PDF.

FINAL UPDATE:

Many thanks to everyone. With your help, I was able to get this working appropriately.

user2122092
  • 71
  • 1
  • 8
  • What should the last one be, or is the page number list infinite? – Patrick May 22 '13 at 20:01
  • No not with linq. Use *for/foreach* loop. – I4V May 22 '13 at 20:02
  • I am using the Aspose.PDF dll and ripping through a collection of Bookmarks. I am selecting that into my own Statment object. The NextPageNumber of the very last record would just be the total count of all pages in the PDF. – user2122092 May 22 '13 at 20:05
  • 1
    You should be able to do this with a function taking and returning an IEnumerable...you'd use a for/forEach, but you'd skip the first one and keep track of the 'previous' value during the loop. – Chris Pfohl May 22 '13 at 20:08
  • Then you could consume your own function as an IEnumerable source. – Chris Pfohl May 22 '13 at 20:08

5 Answers5

4

Here is a helper function that maps a sequence into a sequence of pairs where each pair is each item paired with the one that follows it.

public static IEnumerable<Tuple<T, T>> WithNext<T>(this IEnumerable<T> source)
{
    using (var iterator = source.GetEnumerator())
    {
        if(!iterator.MoveNext())
            yield break;

        T previous = iterator.Current;
        while (iterator.MoveNext())
        {
            yield return Tuple.Create(previous, iterator.Current);
            previous = iterator.Current;
        }

        yield return Tuple.Create(previous, default(T));
    }
}

Now you can do:

var query = bookmarkCollection.AsEnumerable()
.WithNext()
.Select(pair => new Statement(){
    Title= pair.Item1.Title,
    PageNumber = pair.Item1.PageNumber,
    NextPageNumber = pair.Item2.PageNumber - 1, //note you'll need to null check for the last item
});
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Your `WithNext()` is traditionally called `Pairwise()`, and I prefer to implement it using a two-argument selector rather than a tuple: http://stackoverflow.com/a/1581482/54249 – dahlbyk May 22 '13 at 20:17
  • @dahlbyk: Isn't the question you linked to a duplicate? – Patrick May 22 '13 at 20:22
  • @dahlbyk Sure, if you prefer there's nothing wrong with that. I find both overloads helpful actually. If I want to provide a selector I will, but it's sometimes nice to have the option of omitting it and getting a `Tuple`. – Servy May 22 '13 at 20:23
  • Tuple is not available in .NET 3.5, so this method is out. – user2122092 May 22 '13 at 22:28
2

It's probably better to use a for loop, but you can cobble together something using .Zip if you're really set on linq:

var strings = new[] { "one", "two", "three", "four", "five" };

var result = strings.Zip(
    strings.Skip(1).Concat(Enumerable.Repeat("last", 1)), 
    (a, b) => new { a, b }
);

Result

one   two 
two   three 
three four 
four  five 
five  last 
recursive
  • 83,943
  • 34
  • 151
  • 241
  • 3
    Note that this iterates the source sequence twice. For some sequences that won't be a problem, for others it will be highly inefficient (i.e. one performing IO for each iteration), and for still others it won't work at all (anything that's non-deterministic, i.e. a sequence of random numbers). – Servy May 22 '13 at 20:20
2
var bc = bookmarkCollection.AsEnumerable();
IEnumerable<Statement> statement = bc.Zip(bc.Skip(1), 
    (b1,b2) => new Statement()
    {
        Title= b1.Title,
        PageNumber = b1.PageNumber,
        NextPageNumber = b2.PageNumber - 1
    });

EDIT: (per comment below): If you need to include the last item as well, then you'd best use @Servy's helper method.

You could do this...

var bc = bookmarkCollection.AsEnumerable();
IEnumerable<Statement> statement = bc.Zip(bc.Skip(1).Concat(new Bookmark[] { null }), 
    (b1,b2) => new Statement()
    {
        Title= b1.Title,
        PageNumber = b1.PageNumber,
        NextPageNumber = b2 == null ? 0 : b2.PageNumber - 1
    });

...however, I originally suggested Zip only because it was quick and easy -- now it's getting a bit harder to interpret. Therefore, I'd suggest you use @Servy's method with a slight modification to include a selector function:

public static IEnumerable<TResult> WithNext<T, TResult>(this IEnumerable<T> source, Func<T, T, TResult> selector)
{
    using (var e = source.GetEnumerator())
    {
        if (!e.MoveNext()) yield break;

        T previous = e.Current;
        while (e.MoveNext())
        {
            yield return selector(previous, e.Current);
            previous = e.Current;
        }

        yield return selector(previous, default(T));
    }
}

and use it like:

IEnumerable<Statement> statement = bc.WithNext(
   (b1, b2) => new Statement()
   {
       Title = b1.Title,
       PageNumber = b1.PageNumber,
       NextPageNumber = b2 == null ? 0 : b2.PageNumber - 1
   }).ToList();
Eren Ersönmez
  • 38,383
  • 7
  • 71
  • 92
  • This is creating one less statement than the PDF has bookmarks. I need to create a Statement for each bookmark within bookmarkCollection – user2122092 May 22 '13 at 21:31
0

LINQ doesn't provide an easy way to do this, short of capturing the result with ToList() or ToArray() and iterating over the list to update each successive record, e.g.:

var statements = bookmarkCollection.AsEnumerable().Select(
    bookmark => new Statement()
    {
        Title= bookmark.Title,
        PageNumber = bookmark.PageNumber,
    }).ToList();

for (var i = 0; i < statements.Length - 1; i++)
    statements[i].NextPageNumber = statements[i+1].PageNumber - 1;

In theory you could also use the Select() overload that takes a Func<T, int, R>, but the mechanics would be the same.

dahlbyk
  • 75,175
  • 8
  • 100
  • 122
0

You can try use Linq for this problem, try an indexed Select, something like this:

var statement = bookmarkCollection.AsEnumerable().Select(
    (bookmark, index) => new Statement()
{
    Title = bookmark.Title,
    PageNumber = bookmark.PageNumber,
    NextPageNumber = index < bookmarkCollection.Count -1 ? bookmarkCollection[index + 1].PageNumber - 1 : -1
});

The code above is setting NextPageNumber to -1 when there is no next record.

A good reference for Linq is 101 LINQ Samples, where you can see other indexed Select sample: http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b

giacomelli
  • 7,287
  • 2
  • 27
  • 31