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 k
th 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 n
th 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)