login
Header Space

 
 

Ice Cream Problem

January 24, 2006 - 7:12pm
Submitted by Greg Buchholz on January 24, 2006 - 7:12pm.

Brian Harvey throws down the gauntlet: implement the Ice Cream problem in a small number of lines of Java. Well, since I don't know Java, I'll take up the challenge in C++...

template<template<class> class Out, class Obj, template<class> class G, template<class> class F> 
Out<Obj> comb(typename G<F<Obj> >::const_iterator b, typename G<F<Obj> >::const_iterator e)
{
    typename G<F<Obj> >::const_iterator tail = b;

    if(++tail == e) { return *b; }
    else
    { // recursivly concatenate the items in the head of the list
      // with the tails...
      G<F<Obj> > temp;
      transform((*b).begin(),(*b).end(),back_inserter(temp),bind1st(ptr_fun(aux<Out,Obj>),comb<Out,Obj,G,F>(tail,e)));
      //now flatten temp...
      Out<Obj> out;
      for(typename G<F<Obj> >::const_iterator i = temp.begin(); i != temp.end(); ++i)
         copy((*i).begin(),(*i).end(),back_inserter(out));
        
      return out;
    }
}

template<template<class> class Container, class Obj>
Container<Obj> aux(Container<Obj> ps, Obj n)
{
    Container<Obj> out;
    transform(ps.begin(),ps.end(),back_inserter(out),bind1st(plus<string>(),n));
    return out;
}

...OK. That's pretty verbose. But it is interesting to note that there are only ~5 lines of code doing the work there. The rest is dedicated to declaring stuff like types. The other thing to notice is that the C++ solution above is pretty generic. You can feed it a list of list of strings, or a vector of deque's of strings, or even a list of stacks of valarray<double>'s (don't ask me what you'd use that for). You can save a few LOC and a little bit of uglyness by specializing it for lists of lists of strings. Of course, that still pales in comparison to the two line Haskell solution...


combs [a] = a
combs (x:xs) = concat $ map (\n -> map (n++) (combs xs)) x

...and for interested parties, here is a driver program, so you can get up to speed quickly (and catch any of my mistakes)...

#include <list>
#include <string> 
#include <iostream>
#include <algorithm>
#include <iterator>

using namespace std;

template<template<class> class Out, class Obj, 
         template<class> class G, template<class> class F> 
Out<Obj> comb(typename G<F<Obj> >::const_iterator b, 
              typename G<F<Obj> >::const_iterator e);

template<template<class> class Container, class Obj>
Container<Obj> aux(Container<Obj> ps, Obj n);

int main()
{
    char *c[] = {"small ","medium ","large ",
                 "vanilla ","ultra chocolate ","lychee ","rum raisin ","ginger ",
                 "cone","cup"}; 
    int len[] = { 3, 5, 2 };                  
    list<list<string> > choices;

    for(int i=0, off=0; i<3; off += len[i++])
    {  
        list<string> temp(c+off,c+off+len[i]);
        choices.push_back(temp);
    }
    
    list<string> o = comb<list,string,list,list>(choices.begin(),choices.end());

    copy(o.begin(),o.end(),ostream_iterator<string>(cout, ".\n"));
    
    return 0;
}

Here's a version in C. It's d

January 27, 2006 - 8:36pm
mac the naîf (not verified)

Here's a version in C. It's different in that it produces the results in a different order, it doesn't use recursion, and it doesn't construct temporary lists. Instead, it uses an encoding for a choice.
Oh, it uses C style array initialization.

---


static char
	*sub0[] = {"small", "medium", "large", 0},
	*sub1[] = {"vanilla", "ultra chocolate", "lychee", "rum raisin", "ginger", 0},
	*sub2[] = {"cone", "cup", 0},
	**menu[] = {sub0, sub1, sub2, 0};

static int
lgth(char *sub[])
{	int n = 0;
	while (*sub++)
		n++;
	return n;
}

static int
poss(char **menu[])
{	int n = 1;
	while (*menu)
		n *= lgth(*menu++);
	return n;
}

static int
choices(char **menu[])
{	int n = poss(menu);
	while (0<=--n)
	{	char ***m = menu;
		int i = n;
		while (*m)
		{	char **s = *m++;
			int l = lgth(s);
			printf(" %s",s[i%l]);
			i /= l;
		}
		printf("\n");
	}
	return n;
}

int	main(int argc, char *argv[])	{ choices(menu); return 0; }

I should add that this is rea

January 27, 2006 - 9:30pm
mac the naîf (not verified)

I should add that this is really an APL function coded in C

Nice. Out of curiosity, what

January 30, 2006 - 1:11pm

Nice. Out of curiosity, what would the APL (or A+, J, or K) version look like?

I don't have a working APL ri

February 2, 2006 - 6:39pm
mac the naîf (not verified)

I don't have a working APL right now, so I can't really try this -
The idea is to compute the number of possible choices, then iterate.
The iteration count represents a choice which decodes to a set of indices, one for the selection in each submenu. APL has a decode operator for this sort of thing.

In Ocaml...

June 13, 2006 - 11:11am
ChriS_00 (not verified)

Here it is in OCaml (http://caml.inria.fr/):

let rec comb = function
  | [] -> [[]]
  | x :: tl -> List.concat(List.map (fun n -> List.map (fun l -> n :: l) (comb tl)) x)

The compiler returns the type

val comb : 'a list list -> 'a list list

which is a clear indication that it has the same polymorphism as your C++ version ('a stands for an arbitrary type). You can use it this way:

comb [["small"; "medium"; "large"];
      ["vanilla"; "ultra chocolate"; "rum raisin"; "ginger"];
      ["cone"; "cup"]]

(BTW, I do not think the base case in your Haskell version is correct -- and the case [] is missing.)

Not quite

December 28, 2007 - 12:39am

The compiler returns the type

val comb : 'a list list -> 'a list list

which is a clear indication that it has the same polymorphism as your C++ version ('a stands for an arbitrary type).

The C++ is more polymorphic. The Ocaml only works with lists of lists. The C++ closer to something like Haskell's Monad m => m (m a) -> m (m a), but it'll even work with arrays and things that aren't monads.

Shorter Haskell

December 28, 2007 - 8:30am
Anonymous (not verified)

combs = map unwords . sequence

It also saves putting spaces in all the strings.
The sequence function is the magic, in the list monad it generates all the combinations.

bash wins again

December 28, 2007 - 9:52am
Andy G (not verified)
#!/bin/sh

for a in small medium large; do
  for b in vanilla 'ultra chocolate' lychee 'rum raisin' ginger; do
    for c in cone cup; do
      echo $a $b $c
    done
  done  
done

A compact Java version

December 28, 2007 - 11:35am
PyRubyite (not verified)

Here's a java implementation. It's not all that huge ...

import java.util.Arrays;
import java.util.List;

public class IceCream {

    private void build(String sentence, List... components) {
        if (components.length == 1) for(String component : components[0]) 
            System.out.println(sentence + " " + component);
        else {
            List[] subComponents = new List[components.length - 1];
            System.arraycopy(components, 1, subComponents, 0,components.length - 1 );
            for(String component : components[0]) build(sentence + " " + component, subComponents);
        }
    }

    public static void main(String[] args) {
        new IceCream().build("", Arrays.asList("small","medium","large"),
                                 Arrays.asList("vanilla","ultra chocolate","lychee","rum raisin","ginger"),
                                    Arrays.asList("cone","cup"));
    }
}

Python FTW

December 28, 2007 - 5:04pm
drea (not verified)


size = ["Small ", "Medium ", "Large "]
flavour = ["Vanilla", "Ultra Chocolate", "Rum Raisin", "Ginger"]
container = [" Cone", " Cup"]

combs = [i + j + k for i in size for j in flavour for k in container]

for comb in combs:
print comb

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
speck-geostationary