tags:

views:

103

answers:

5

I've got a real problem (it's not homework, you can check my profile). I need to parse data whose formatting is not under my control.

The data look like this:

6,852:6,100,752

So there's first a number made of up to 9 digits, followed by a colon.

Then I know for sure that, after the colon:

  • there's at least one valid combination of numbers that add up to the number before the column
  • I know exactly how many numbers add up to the number before the colon (two in this case, but it can go as high as ten numbers)

In this case, 6852 is 6100 + 752.

My problem: I need to find these numbers (in this example, 6100 + 752).

It is unfortunate that in the data I'm forced to parse, the separator between the numbers (the comma) is also the separator used inside the number themselves (6100 is written as 6,100).

Once again: that unfortunate formatting is not under my control and, once again, this is not homework.

I need to solve this for up to 10 numbers that need to add up.

Here's an example with three numbers adding up to 6855:

6,855:360,6,175,320

I fear that there are cases where there would be two possible different solutions. HOWEVER if I get a solution that works "in most cases" it would be enough.

How do you typically solve such a problem in a C-style bracket language?

+1  A: 

I think your main problem is deciding how to actually parse the numbers. The rest is just rote work with strings->numbers and iteration over combinations.

For instance, in the examples you gave, you could heuristically decide that a single-digit number followed by a three-digit number is, in fact, a four-digit number. Does a heuristic such as this hold true over a larger dataset? If not, you're also likely to have to iterate over the possible input parsing combinations, which means the naive solution is going to have a big polynomic complexity (O(nx), where x is >4).

Actually checking for which numbers add up is easy to do using a recursive search.

List<int> GetSummands(int total, int numberOfElements, IEnumerable<int> values)
{
    if (numberOfElements == 0)
    {
        if (total == 0)
            return new List<int>(); // Empty list.
        else
            return null; // Indicate no solution.
    }
    else if (total < 0)
    {
        return null; // Indicate no solution.
    }
    else
    {
        for (int i = 0; i < values.Count; ++i)
        {
            List<int> summands = GetSummands(
                total - values[i], numberOfElements - 1, values.Skip(i + 1));
            if (summands != null)
            {
                // Found solution.
                summands.Add(values[i]);
                return summands;
            }
        }
    }
}
jdmichal
+1  A: 

I think you should try all possible ways to parse the string and calculate the sum and return a list of those results that give the correct sum. This should be only one result in most cases unless you are very unlucky.

One thing to note that reduces the number of possibilities is that there is only an ambiguity if you have aa,bbb and bbb is exactly 3 digits. If you have aa,bb there is only one way to parse it.

Mark Byers
+2  A: 

Well, I would start with the brute force approach and then apply some heuristics to prune the search space. Just split the list on the right by commas and iterate over all possible ways to group them into n terms (where n is the number of terms in the solution). You can use the following two rules to skip over invalid possibilities.

(1) You know that any group of 1 or 2 digits must begin a term.

(2) You know that no candidate term in your comma delimited list can be greater than the total on the left. (This also tells you the maximum number of digit groups that any candidate term can have.)

Peter Ruderman
I don't see any reason for (2) to be true. That simply means you can ignore that element when checking for summations. But that does not mean that it's invalid input. For instance, `5:2,3,1,234` can still be valid input, and has a valid solution, despite both `234` and `1,234` being bigger than `5`.
jdmichal
You misunderstood. Part of the problem description is that you can't have invalid input. I meant exactly what you said--you can ignore that value (and skip the corresponding portion of the solution space).
Peter Ruderman
Yup, as stated in the question I know that in every single case there's at least one correct solution :)
NoozNooz42
+1  A: 

Reading in C++:

std::pair<int,std::vector<int> > read_numbers(std::istream& is)
{
  std::pair<int,std::vector<int> > result;
  if(!is >> result.first) throw "foo!"
  for(;;) {
    int i;
    if(!is >> i) 
      if(is.eof()) return result;
      else throw "bar!";
    result.second.push_back(i);
    char ch;
    if(is >> ch) 
      if(ch != ',') throw "foobar!";
    is >> std::ws;
  }
}

void f()
{
  std::istringstream iss("6,852:6,100,752");
  std::pair<int,std::vector<int> > foo = read_numbers(iss);
  std::vector<int> result = get_winning_combination( foo.first
                                                   , foo.second.begin()
                                                   , foo.second.end() );
  for( std::vector<int>::const_iterator i=result.begin(); i!=result.end(), ++i)
    std::cout << *i << " ";
}

The actual cracking of the numbers is left as an exercise to the reader. :)

sbi
+1  A: 

Recursive implementation (pseudo code):

int total;  // The total read before the colon

// Takes the list of tokens as integers after the colon
// tokens is the set of tokens left to analyse,
// partialList is the partial list of numbers built so far
// sum is the sum of numbers in partialList
// Aggregate takes 2 ints XXX and YYY and returns XXX,YYY (= XXX*1000+YYY)
function getNumbers(tokens, sum, partialList) =
    if isEmpty(tokens)
         if sum = total return partialList
         else return null  // Got to the end with the wrong sum
    var result1 = getNumbers(tokens[1:end], sum+token[0], Add(partialList, tokens[0]))
    var result2 = getNumbers(tokens[2:end], sum+Aggregate(token[0], token[1]), Append(partialList, Aggregate(tokens[0], tokens[1])))
    if result1 <> null return result1
    if result2 <> null return result2
    return null  // No solution overall

You can do a lot better from different points of view, like tail recursion, pruning (you can have XXX,YYY only if YYY has 3 digits)... but this may work well enough for your app. Divide-and-conquer would make for a nice improvement.

Mau