Crop Collection

hey,
i have this collection of items:
d = [ 0.25, 0.5, 0.5, 0.25, 0.5, 0.25, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.5, 0.25, 0.25, 0.25, 0.5, 0.5, 0.25, 0.5, 0.25 ]

now i would like to drop all items necessary to receive a sum of 4.
how can i do this?
thanks :slight_smile:

The algorithm is pretty straightforward:

  1. Initialize a counter and a sum (both 0).
  2. Do this in a loop, while the counter is still within the array size, and the sum is less than the desired value:
    1. Add the current value to the sum (+).
    2. Add the current value to the output list (.add). One refinement here is to adjust the value, in case the sum has gone over the total.
    3. counter = counter + 1
d = [ 0.25, 0.5, 0.5, 0.25, 0.5, 0.25, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.5, 0.25, 0.25, 0.25, 0.5, 0.5, 0.25, 0.5, 0.25 ];

(
~sumUpTo = { |array, total|
	var sum = 0, i = 0, result = Array.new;
	while { i < array.size and: { sum < total } } {
		sum = sum + array[i];
		if(sum > total) {
			result = result.add(array[i] - (sum - total));
		} {
			result = result.add(array[i]);
		};
		i = i + 1;
	};
	result
};
)

~sumUpTo.(d, 4);

But there’s a Pattern that does this for you:

Pconst(4, Pseq(d)).asStream.all;

hjh

hey, thanks for your help.
its working fine.
i also came across the pattern solution, but i have something else in mind:

the array is a collection of durations as a result of an l-system.

i would like to create a dictionary which contains different abstractions of this duration l-system pattern, ordered by their rythmic density, which i then can acces later via Pdef. so slicing the l-system pattern to a specific length of 4 or 8 was only the first step.

an example:

0: [8]
1: [4,4]
2: [4,2,2]
3: [3,1,2,2] <-- possible pattern from the l-system
4: [2,1,1,2,2]
5: [2,1,1,2,1,1]
6: [2,1,1,1,1,1,1]
7: [1,1,1,1,1,1,1,1]

to create the abstractions i would like to go with the following ideas from renick bell:

create the patterns of lesser density:
when reducing density, an item from the pattern is chosen at random and combined with a neighbour value to yield a similiar pattern of reduced density. this process is repeated until the list contains only one single item. in the example above 0:[8].

create the patterns of higher density:
when increasing density an item is randomly chosen and replaced with two items: an item of lesser value from the list of potential IOIs and the difference between the original IOI value and the lesser value. this is repeated until all the items in the pattern are the smallest of the potential IOIs. in the example above 7: [1,1,1,1,1,1,1,1].

*** UPDATE ***
i have deleted the other posts and will post my current upcommings with specific questions below to make it more clear. Im sorry for the confusion.

Hey, after two weeks of nightshifts Im 95% there:

  • Both Functions ~low_dens and ~high_dens are working fine.
  • The abstractions are getting added to the dictionary.

The only problems still existing are:

  • while { result.sum != 8 } in ~low_dens causes stackoverflow for bigger starting arrays. The reason for implementing this condition is, that the sum of the new arrays should always stay at a fixed value of for example 8. Depending on what item is chosen for index_min this is not always the case and the function should be forced to choose again. Any other idea on this issue?

  • at the moment I have two functions for getting the abstractions ~get_high_Abs and ~get_low_Abs. I would like to combine these two to ~get_Abs How can i do this and still make sure all the abstractions are added to the dictionary and nothing gets overwritten.

  • How can I order all Arrays in the dictionary after size like this: 0: [8], 1: [4, 4], 2: [4,2,2], etc.

        //functions for building Abstractions
          //creating abstractions with lesser density:
          (
          ~low_dens = {
          	arg array;
          	var ioi, pot_ioi, index_min, neighbour, result = Array.new;
          	ioi = array.as(Set).as(Array).sort;
          	pot_ioi = ioi.minItem;
          	while { result.sum != 8 } {
          		index_min = array.findAll([ioi.minItem]).choose;
          		neighbour = [index_min-1,index_min+1].minItem.abs;
          		result = array[index_min] + array[neighbour];
          		result = array.replace(array[index_min], result);
          		result.removeAt(neighbour);
          	};
          	result;
          };
    
          //creating abstractions with higher density:
          ~high_dens = {
          	arg array;
          	var ioi, pot_ioi, index_max, result = Array.new;
          	ioi = array.as(Set).as(Array).sort;
          	pot_ioi = ioi.minItem;
          	index_max = array.findAll([ioi.maxItem]).choose;
          	result = result.add(array).flat;
          	result = result.insert(index_max+1, [result[index_max] - pot_ioi, pot_ioi]).flat;
          	result.removeAt(index_max);
          	result;
          };
          )
    
    
      (
      ~lsys = #[3,1,2,2];
      ~absDict = Dictionary.new;
      ~absDict.put(0, ~lsys);
      )
    
      (
      ~get_high_Abs = Routine({
      	var i=0;
      	while { ~lsys.maxItem > ~lsys.minItem } {
      		~lsys = ~high_dens.(~lsys);
      		~lsys.yield;
      		~absDict.put((i+1), ~lsys);
      		i = i + 1;
      	};
      	~absDict.yield;
      }).do({ arg item; item.postln });
      )
    
      (
      ~get_low_Abs = Routine({
      	var i=0;
      	while { ~lsys.size != 1 } {
      		~lsys = ~low_dens.(~lsys);
      		~lsys.yield;
      		~absDict.put((i+1), ~lsys);
      		i = i + 1;
      	};
      	~absDict.yield;
      }).do({ arg item; item.postln });
      )

Im stuck here at the moment. Does anybody have any idea? thanks :slight_smile:

Not completely clear where you get stuck so please bear with me if perhaps I’m stating the obvious:

  • if you start with a different array, the chance that result.sum == 8 is very small, so you end up with an infinite loop trying forever to get result.sum == 8

  • if you combine neighbors by adding them together, the total sum should never change, so why you need this while loop is not clear to me.

[a,b,c,d] or [a,b+c,d] : no matter where you put the comma’s or the plus sign the sum is always going to be a+b+c+d.

Can you explain how " Depending on what item is chosen for index_min this is not always the case"?

It seems to me you just have a buglet in your code where you don’t properly handle what should happen if you chose the very first or very last item in the list (which causes you to try to access elements outside the list).

The following does what I think you wanted to do for reduction of an arbitrary list:

(
var reductions;
~lsys = #[3,1,2,2,4,6,2,3,1,4];
~absDict = Dictionary.new;
~absDict.put(0, ~lsys);

~low_dens = {
	arg array;
	var ioi, result = [], min_index, neighbor_index;
	ioi = array.copy();
	
	ioi.debug("start ioi");
	
	while {ioi.size() > 1} {
		// in every step, we will reduce ioi until it has size 1
		var intermediate_result = [];
		min_index = ioi.minIndex;
		
		// if first element is minimum element, the neighor can only be to the right
		if (min_index == 0) {
			neighbor_index = 1;
		} {
			// else if last element is minimum element, the neighbor can only be to the left
			if (min_index == (ioi.size - 1)) {
				neighbor_index = (ioi.size - 2);
			} {
				// else we're in the middle, so use the neighbor which is smallest (arbitrary choice)
				var dir = 1;
				if (ioi[min_index - 1] < ioi[min_index + 1]) {
					dir = -1;
				};
				neighbor_index = min_index + dir;
			};
		};
		// swap neighbor and min_index so that neighbor is always bigger than min_index 
		// - this makes copying easier in the next step
		if (neighbor_index < min_index) {
			var tmp = min_index;
			min_index = neighbor_index;
			neighbor_index = tmp;
		};
		
		// make a new list consisting of everything before min_index, sum of min el and its neighbor, everything after neighbor
		intermediate_result = ioi.copyRange(0, min_index-1) ++ (ioi[min_index] + ioi[neighbor_index]) ++ ioi.copyRange(neighbor_index+1, (ioi.size-1));
		
		if (intermediate_result.size >= ioi.size) {
			"ERROR!".postln;
		};
		
		// add it to the list of results
		result = result.add(intermediate_result);
		
		// and update our starting point with the reduced list
		ioi = intermediate_result.copy();
	};
	
	// return the complete list of reductions
	result;
};

reductions = ~low_dens.(~lsys);
reductions.do({
	|el, elindex|
	~absDict.put((elindex+1), el);
});

~absDict.debug("abs dict");
)

hey thanks for your help :slight_smile:

The resulting array of the L-System gets always cropped to a sum of 4 or 8 or whatever length you choose with ~sumUpto (keep in mind the usage for note durations in a pattern later on, so you want to have it locked to a specific amount of bars/beats).
So the starting array ~lsys has always a predefined sum.
I think the best is 4 or 8, everything else misses its musical purpose, as shorter rhythm figures of 3-5 items tend to be more recognizable. The whole thing is about having a specific rhythmic figure (the starting array ~lsys) which essence is kept in the abstractions of lesser or higher density and you can access them all in the dictionary via a pattern in real time later on.

if you combine neighbors by adding them together, the total sum should never change, so why you need this while loop is not clear to me.

In the example we start with:
~lsys = #[3,1,2,2]; and want to receive [4,2,2] in the first step, [4,4] in the second step and [8] in the last step.

  1. the sum of the starting array is 8 because ~sumUpto has taken care of it.
  2. the possible iois for the first abstrations are [1,2,3] so ioi.min is [1]. (when reducing the density you want to get rid of the smallest items with priority.).
    For the first abstraction [4,2,2] its easy because you only have 1 smallest item [1] in the starting array. When creating the second abstration [4,4] the possible iois are [2,4] and ioi.min is [2] but now you can choose index 1 or index 2 which have both the ioi.min value [2].
    When index 2 is chosen you yield the result [4,4] but when index 1 is chosen you get [6,6] which sum is bigger then 8. thats the reason for implementing while != 8:

WRONG:
index_min = array.findAll([ioi.minItem]).choose.postln; → 1
neighbour = [index_min-1,index_min+1].minItem.abs.postln; → 0
I think the problem is also [ ].minItem.abs, it should compare the values at the given indices and give the index of the smallest value. what can I change change here?
result = array[index_min] + array[neighbour].postln; – > 4
result = array.replace(array[index_min], result).postln; → [4, 6, 6]
result.removeAt(neighbour).postln; → [6,6]

RIGHT:
index_min = array.findAll([ioi.minItem]).choose.postln; → 2
neighbour = [index_min-1,index_min+1].minItem.abs.postln; → 1
result = array[index_min] + array[neighbour].postln; – > 2
result = array.replace(array[index_min], result).postln; → [ 4, 4, 4 ]
result.removeAt(neighbour).postln; → [4,4]

but when the starting array is bigger for example:

~lsys=[ 0.25, 0.5, 0.5, 0.25, 0.5, 0.25, 0.25, 0.5, 0.25, 0.5, 0.25, 0.5, 0.5, 0.25, 0.25, 0.25, 0.5, 0.5, 0.25, 0.5, 0.25, 0.25 ]

which sum is also 8, there are to many options. the possible iois are [0.25, 0.5] so ioi.min is [0.25] but there are a lot of possible indice with a value of 0.25 to choose from. do you understand the problem? or is it something else?

I also want to combine ~get_high_Abs and ~get_low_Abs to ~get_Abs and add the abstrations in both ways to the dictionary and order them by size and dont know how.
thanks a lot :slight_smile:

I think the problem is also [ ].minItem.abs, it should compare the values at the given indices and give the index of the smallest value. what can I change change here?

Yes, that’s a core problem in your code causing the 6’s to appear. Unfortunately, just replacing min_index-1 with ioi[min_index-1] and min_index+1 with ioi[min_index+1] will lead to some complications if index_min == 0 or index_min == (ioi.size - 1) (it would cause you to try to access elements outside ioi).

The code I posted in my previous answer should do reductions for lists of thousands of values if desired without running into big problems (i tried with 10000 elements and it took 5 seconds to calculate the complete reduction).

It will always preserve the original sum (no matter what its value was) without any while loops.

It implements the following behavior:

0 -> [3,1,2,2] // now combine lowest element 1 with lowest neighbor 2
1 -> [3,3,2]   // now combine lowest element 2 with lowest neighbor 3
2 -> [3,5]     // now combine lowsest element 3 with lowest neighbor 5
4 -> [8]       // finished!

Combining a ~high_dens and ~low_dens implementation shouldn’t be too hard I think? Just call one after the other and put all results into the same dictionary (using a suitable key).