2

It seems every time I go to write a recursive function I end up making it return void and using a ref parameter.

I'd much rather be able to write a function that just returns a result list.

Apologies if the answer is very simple - for some reason it elludes me.

Here's the code I have now:

public static void GetResrouces(string startURL, ref List<XDocument> result)
{
    var doc = XDocument.Parse(GetXml(startURL)); // GetXml ommitted - returns xml string
    var xs = new XmlSerializer(typeof(resourceList));
    var rdr = doc.CreateReader();
    if (xs.CanDeserialize(rdr))
    {
        var rl = (resourceList)xs.Deserialize(doc.CreateReader());

        foreach (var item in rl.resourceURL)
        {
            GetResrouces(startURL + item.location, ref result);
        }
    }
    else
    {
        result.Add(doc);
    }
}

public partial class resourceList
{

    private resourceListResourceURL[] resourceURLField;

    private string locationField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("resourceURL")]
    public resourceListResourceURL[] resourceURL
    {
        get
        {
            return this.resourceURLField;
        }
        set
        {
            this.resourceURLField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute(DataType = "anyURI")]
    public string location
    {
        get
        {
            return this.locationField;
        }
        set
        {
            this.locationField = value;
        }
    }
}

I'd like to know if it can be rewritten to the prototype:

public static List<XDocument> GetResources(string startURL)
4

4 回答 4

3

I guess something like:

public static List<XDocument> GetResources(string startURL)
{
    var result = new List<XDocument>();
    var doc = XDocument.Parse(GetXml(startURL));
    var xs = new XmlSerializer(typeof(resourceList));
    var rdr = doc.CreateReader();
    if (xs.CanDeserialize(rdr))
    {
        var rl = (resourceList)xs.Deserialize(doc.CreateReader());

        foreach (var item in rl.resourceURL)
        {
            result.AddRange(GetResources(startURL + item.location));
        }
    }
    else
    {
        result.Add(doc);
    }
    return result;
}
于 2011-08-15T22:05:32.153 回答
2

For one thing, there's absolutely no point in that being a ref parameter in the first place. It's quite possible that you don't understand ref parameters - see my article on this topic.

As this is naturally recursive, I'd probably write it like this:

public static List<XDocument> GetResources(string startURL)
{
    List<XDocument> ret = new List<XDocument>();
    GetResourcesRecursive(startURL, ret);
    return ret;
}

private static void GetResourcesRecursive(string startURL,
                                          List<XDocument> result)
{
    var doc = XDocument.Parse(GetXml(startURL));
    var xs = new XmlSerializer(typeof(resourceList));
    var rdr = doc.CreateReader();
    if (xs.CanDeserialize(rdr))
    {
        var rl = (resourceList)xs.Deserialize(doc.CreateReader());

        foreach (var item in rl.resourceURL)
        {
            GetResourcesRecursive(startURL + item.location, ref result);
        }
    }
    else
    {
        result.Add(doc);
    }
}

You can keep it in a recursive fashion and create a new list at every level, but it feels a little ugly to me. The above gives you the public API you want, but without allocating collections left, right and centre.

Now you could write it in a non-recursive fashion, basically by creating a queue of URLs to work through:

public static List<XDocument> GetResources(string startURL)
{
    List<XDocument> ret = new List<XDocument>();
    Queue<string> urls = new Queue<string>();
    urls.Enqueue(startUrl);
    while (urls.Count > 0)
    {
        string url = urls.Dequeue();
        var doc = XDocument.Parse(GetXml(url));
        var xs = new XmlSerializer(typeof(resourceList));
        var rdr = doc.CreateReader();
        if (xs.CanDeserialize(rdr))
        {
            var rl = (resourceList) xs.Deserialize(doc.CreateReader());

           foreach (var item in rl.resourceURL)
           {
               queue.Enqueue(url + item.location);
           }
        }
        else
        {
            ret.Add(doc);
        }  
    }
    return ret;
}

It's too late in the day for me to work out whether this gives the results in the same order - I suspect it doesn't - but hopefully that's not important.

(You don't really have a type called resourceList do you? ResourceList, please!)

于 2011-08-15T22:05:46.330 回答
2

The code looks fine as is (minus the unnecessary ref on the parameter.) One option is to wrap the recursive method in a non-recursive companion:

public static List<XDocument> GetResources(string startURL)
{
    List<XDocument> retDocs = new List<XDocument>();
    GetResources(startURL, retDocs);

    return retDocs;
}
于 2011-08-15T22:05:57.020 回答
1

Well, I have a pattern I have used on occasion, and I wanted to show it as an option. However, my brain was a bit ticked off when I tried to tackle it as written, so instead we came to an agreement (my brain and I) that we would just figure out a simple version to show you.

It may not even be quite applicable to your specific question, but it's one way I have used in the past when I wanted things done in a non-mutable fashion, which seemed to be what you were looking for.

    public string IntCSVReverse(List<int> IntList)
    {
        return IntCSVReverse_recurse(IntList, 0);
    }

    private string IntCSVReverse_recurse(List<int> IntList, int Index)
    {
        if (Index == (IntList.Count - 1))
            return IntList[Index].ToString();
        else
            return
                IntCSVReverse_recurse(IntList, Index + 1)
                + "," + IntList[Index].ToString();
    }

So, there is the pattern, for what it's worth. It's not XML, it doesn't branch, but it's a succinct example of when a non-mutating recursion is easy to implement and is more understandable (to me) than trying to implement the same thing by, say, mutating a StringBuilder.

Really, your particular example seems to flow better (to me) as the two-step solution with a single List return value created at the beginning. :)

于 2011-08-16T00:12:21.063 回答