Find an array that fits a set of sums

Husk, 20 bytes

ḟȯ⁰¦ṁ∫ṫ!¡Sof~Λ€∫×:¹g

Try it online!

Returns one solution, or an empty list if it doesn't exist. The last test case (n=15) finishes in 3.8 seconds on TIO.

Explanation

The program has two parts. In the first part (¡ and to the right of it), we construct an infinite list whose kth element is a list containing all length-k lists whose slice sums are in S. We do this inductively, starting from the 1-element slices of S, and at each step prepending each element of S to each list, and keeping those whose prefix sums are in S. In the second part (! and to the left of it), we take the nth element of the list, which contains length-n lists. Of these, we select the first one whose slice sums actually contain every element of S.

In the code, let's first replace o and ȯ (which compose two and three functions into one) by parentheses for clarity.

¡S(f~Λ€∫)×:¹g  First part. Input is a list, say S=[1,2,3]
            g  Group equal adjacent elements: [[1],[2],[3]]
¡              Iterate function:
                Argument is a list of lists, say [[1,1],[1,2],[2,1]]
         ×      Mix (combine two lists in all possible ways)
          :     by prepending
           ¹    with the list S: [[1,1,1],[1,1,2],[2,1,1],[1,2,1],[2,1,2],[3,1,1],[2,2,1],[3,1,2],[3,2,1]]
   f            Filter by condition:
        ∫        Cumulative sums: [[1,2,3],[1,2,4],[2,3,4],[1,3,4],[2,3,5],[3,4,5],[2,4,5],[3,4,6],[3,5,6]]
     ~Λ          All of the numbers
 S     €         are elements of S: [[1,1,1]]
                 Only this list remains, since the other cumulative sums contain numbers not from S.
               Result of iteration: [[[1],[2],[3]],[[1,1],[1,2],[2,1]],[[1,1,1]],[],[],[]...

ḟ(⁰¦ṁ∫ṫ)!      Second part. Implicit input, say n=2.
        !      Take nth element of above list: [[1,1],[1,2],[2,1]]
ḟ              Find first element that satisfies this:
                Argument is a list, say [1,2]
      ṫ         Tails: [[1,2],[2]]
    ṁ           Map and concatenate
     ∫          cumulative sums: [1,3,2]
 ȯ ¦            Does it contain all elements of
  ⁰             S? Yes.
               Result is [1,2], print implicitly.

There are some parts that need more explanation. In this program, the superscripts ⁰¹ both refer to the first argument S. However, if α is a function, then α¹ means "apply α to S", while ⁰α means "plug S to the second argument of α". The function ¦ checks whether its first argument contains all elements of the second (counting multiplicities), so S should be its second argument.

In the first part, the function that ¡ uses can be interpreted as S(f~Λ€∫)(×:)¹. The combinator S behaves like Sαβγ -> (αγ)(βγ), which means that we can simplify it to (f~Λ€∫¹)(×:¹). The second part, ×:¹, is "mix with S by prepending", and its result is passed to the first part. The first part, f~Λ€∫¹, works like this. The function f filters a list by a condition, which in this case is ~Λ€∫¹. It receives a list of lists L, so we have ~Λ€∫¹L. The combinator ~ behaves like ~αβγδε -> α(βδ)(γε): the first argument is passed to β, the second to γ, and the results are combined with α. This means that we have Λ(€¹)(∫L). The last part ∫L is just the cumulative sums of L, €¹ is a function that checks membership in S, and Λ takes a condition (here €¹) and a list (here ∫L), and checks that all elements satisfy it. Put simply, we filter the results of the mixing by whether their cumulative sums are all in S.


Ruby, 135 bytes

->a,n{r=w=1;r+=1until w=(s=a[0,r]).product(*[s]*~-n).find{|x|x.sum==a.max&&a==[]|(1..n).flat_map{|r|x.each_cons(r).map(&:sum)}.sort};w}

Try it online!

Use a breadth-first search. n=10 works on TIO, n=15 takes longer than a minute, but works on my machine.

Ruby, 147 bytes

->a,n{r=w=1;r+=1until w=([a[-1]-a[-2]]).product(*[s=a[0,r]]*~-n).find{|x|x.sum==a.max&&a==[]|(1..n).flat_map{|r|x.each_cons(r).map(&:sum)}.sort};w}

Try it online!

Optimized version, works on TIO for n=15 (~20 sec)

Actually, this is the beginning of a non-brute-force approach. I hope somebody will work on it and find a complete solution.

First ideas:

  • The sum of the output array is the last element (max) of the input array.
  • The sum of the output array minus the first (or the last) element, is the second last element of the input array.
  • If an array is a solution, then the reverse array is a solution too, so we can assume the first element is the difference between the last 2 elements of the input array.
  • The second element can be the difference between second and third or second and fourth last element of the input array.

Which brings us to the next optimization:

Ruby, 175 bytes

->a,n{r=w=1;r+=1until w=([a[-1]-a[-2]]).product([a[-2]-a[-3],a[-2]-a[-4]],*[s=a[0,r]]*(n-2)).find{|x|x.sum==a.max&&a==[]|(1..n).flat_map{|r|x.each_cons(r).map(&:sum)}.sort};w}

Try it online!

~8.5 seconds on TIO. Not bad...

...and so on (to be implemented)


Haskell, 117 111 bytes

6 bytes saved thanks to @nimi!

f r i n s|n<1=[r|r==[]]|1<2=[y:z|y<-s,t<-[y:map(y+)i],all(`elem`s)t,z<-f[a|a<-r,all(a/=)t]t(n-1)s]
n&s=f s[]n s

Try it online!

This computes all solutions. The helper function f builds them incrementally, the r argument contains the values of \$ S \$ that were not yet seen, the i argument contains the sums that include the newest element, n is the remaining number of elements that are needed, and s is always the given set.

When n is zero (golfed to n<1) the list must be ready, so we check if all values have been seen. If not, we return an empty list to indicate no solutions, else we return a singleton list containing an empty list, to which the chosen elements will be prepended. This case could also have been handled with the additional equations

f [] _ 0 _=[[]]
f _ _ 0 _=[]

If n is not zero, we return

[y:z|y<-s,t<-[y:map(y+)i],all(`elem`s)t,z<-f[a|a<-r,all(a/=)t]t(n-1)s]
 ^1^ ^2^^ ^......3......^ ^.....4.....^ ^.............5.............^

This is the list of (1) lists where the first element (2) comes from s and the rest (5) comes from the recursive call, under the condition (4) that all new sums are in s. The new sums are computed in (3) - note that t is drawn from a singleton list, an ugly golfing hack for what in idiomatic Haskell would be let t=y:map(y+)i. The recursive call (5) gets as new r set the old one without those elements that appear among the new sums t.

The main function & calls the helper function saying that we still have to see all values (r=s) and that there are no sums yet (i=[]).

For seven more bytes, we can restrict the computation to only give the first result (if any), which is much faster and handles all test cases in less than 2 seconds.

Try it online! (this is the first result only variation of the old version)