当我在for循环中不使用ToList方法时,为什么会失败?

时间:2017-12-01 04:37:47

标签: c# functional-programming ienumerable

我想知道为什么我改变了行

import Tweet, pickle

timeline = []

save_tweets = 'tweets.dat'

def main():    
    while True:
        tweet_menu()

        choice = get_choice()
        if choice==1:
            make_tweet()
        elif choice==2:
            view_tweets()
        elif choice==3:
            search_tweets()
        elif choice==4:
            print('Thank you for using Tweet Manager.')
           break

def tweet_menu():
    print(' ')
    print('Tweet Menu')
    print('_____________')
    print('1. Make a tweet')
    print('2. View recent tweets')
    print('3. Search tweets')
    print('4. Quit')

def get_choice():
    while True:
        try:
            choice = int(input('Which would you like to do? '))
            if choice < 1 or choice > 4:
                print('That is not a valid choice.')
                continue
        except:
            print('Please enter a number between 1 and 4.')
        else:
            break

    return choice

def make_tweet():
        author = input('What is your name? ')
        while True:
            text = input('What would you like to tweet? ')
            if len(text) > 140:
                print('Error: Tweets cannot be longer than 140 characters.')
                continue
            else:
                break
        age = 0               

        #Create a tweet object.
        tweet = Tweet.Tweet(author,text,age)

        #Add the tweet to the list.
        timeline.append(tweet)

        #Encrypt the tweets to a data file.
        try:
            save_tweets = open('tweets.dat', 'wb')
            pickle.dump(timeline, save_tweets)
            save_tweets.close()
        except:
            print('Your tweets could not be saved.')

        print('Your tweet has been saved.')


def view_tweets():

    #Unpickle the tweets so the user can access them.
    try:
        load_file = open(save_tweets, 'rb')
        timeline = pickle.load(load_file)
        load_file.close()

     except:
         print('The tweets could not be loaded.')

    print('Recent Tweets')
    print('______________')
    if len(timeline) == 0:
         print('There are no recent tweets.')
    for tweet in timeline[-5]:
        print(timeline.get_author, '-', timeline.get_age)
        print(timeline.get_text)


def search_tweets():
    match = 0
    timeline.reverse()

    if timeline ==[]:
        print('There are no tweets to search at this time.')

    search = input('What would you like to search for? ')
    for tweet in timeline:
        if (search in timeline.get_text):
            match = 1
        if match == 1:
            print('Search Results')
            print('_______________')
            print(tweet.Tweet.get_author, '-', tweet.Tweet.get_age)
            print(tweet.Tweet.get_text)
        else:
            print('Search Results')
            print('_______________')
            print('No tweets contained ', tweet.Tweet.get_author, 
            tweet.Tweet.get_text)

main()

"sub = sub.SelectMany(x => x.Next(i)).ToList();" 

我收到错误

  

第48行:System.IndexOutOfRangeException:当我向方法SolveNQueens提供4的输入时,索引超出了数组的范围。

我认为这可能与懒惰评估有关。

下面列出了完整的代码示例,它是一种有效的解决方案 对于女王问题。

"sub = sub.SelectMany(x => x.Next(i));"  

1 个答案:

答案 0 :(得分:3)

失败的原因是因为for loop中使用的迭代器变量在captured by a closure时被评估的方式。当您删除循环内的ToList()时,sub IEnumerable仅在返回语句sub中实现return sub.Select(x => x.ToPosition()).ToList();时进行评估。此时,for循环变量i的值为n(例如标准棋盘上为8),它位于数组范围之外。

但是,当您立即实现List时,不会遇到副作用,因为i的值在下一次迭代之前使用(ToList具体化)。

使用:

for (int i = 0; i < n; i++)
{
    // Materialized here so `i` evaluated immediately
    sub = sub.SelectMany(x => x.Next(i)).ToList(); 
}

断裂:

for (int i = 0; i < n; i++)
{
    sub = sub.SelectMany(x => x.Next(i));
}
return sub.Select(x => x.ToPosition()).ToList(); // `i` evaluated here

fix for循环变量评估问题,您可以显式捕获迭代器变量的当前值:

for (int i = 0; i < n; i++)
{
    var loop = i;
    sub = sub.SelectMany(x => x.Next(loop)); // No To List - lazy evaluation
}

Re:在FP范例代码中避免循环

OP的SolveNQueens方法使用循环逐步更改sub,而不是递归,但for也可以替换为foreach和范围:

foreach(var i in Enumerable.Range(0, n))
{
    sub = sub.SelectMany(x => x.Next(i));
}

然后哪个Resharper重写为左折:

sub = Enumerable.Range(0, n)
    .Aggregate(sub, (current, i) => current.SelectMany(x => x.Next(i)));

无论哪种方式,都避免了在for循环内对延迟评估迭代器变量的缺陷。