[ 1, 2, 1, 1, 2, 1, 1 ] how to find adjacent elements in an array

Hello everyone,
I’d like to know if anyone can figure out how to find adjacent elements within an array.

ex: [2, 3, 5, 5]
at index 2 I have an equality of 2 times the value 5

ex: [1, 2, 1, 1, 1, 2, 1, 1],
at index 2 I have an equality of 3 times the value 1 and at index 7 I have an equality of 2 times the value 1

I need to find the index, the how many times (the adjacent values are equal) and what is the adjacent value.

Thank you so much in advance, I hope someone knows the way

What is your desired output structure? Something like this?

// input array
[2, 3, 5, 5]

// desired output array
[ [ 2, 2, 5] ] // array containing arrays structured as [index of first repeat occurrence, number of adjacent repeats, value that is repeated]


// input
[1, 2, 1, 1, 1, 2, 1, 1]

// desired output
[ [2, 3, 1], [6, 2, 1] ] // your example has a mistake I think? the second repeated chunk starts at index 6, not 7

Or do you want a Dictionary associating the repeated chunks with their named properties (index of first repeat occurrence, # of repeats, etc.)?

something like this:

~findIt = {|array, index|
	var count=0;
	while({((index+count)<array.size)and:(array[index+count+1]==array[index])},{count=count+1});
	[array[index], count+1, array[index+1]]
};

a = [1, 2, 1, 1, 1, 2, 1, 1];
b = ~findIt.value(a, 2);

a = [2, 3, 5, 5];
b = ~findIt.value(a, 2);

this only looks to the right.

sam

If I understood OP correctly, this returns the wrong value for both inputs, see my post above.
~findIt always only returns one array even though it should return multiple arrays if there are multiple chunks of repeated values (one for every chunk). Also, it returns

[value at index of first repeated chunk, number of repeats, value at index+1 of first repeated chunk]

instead of

[index of first repeat occurrence, number of adjacent repeats, value that is repeated]

which is how I interpreted OP’s requirement. I also don’t get why ~findIt needs an index argument?
Edit: I should probably wait for OP to respond instead of being pedantic about your solution :stuck_out_tongue:

This is perhaps a bit verbose and clunky, but it meets the above specification:

(
~repeats = {|array|
    // return [index, number of repeats, the value] for each repeated value
    var results = [];
    var thisIndex = 0;
    var thisCount = 1;
    var thisValue = array[0];
    array[1..].do{|val, i|
        if(val == thisValue,
            {thisCount = thisCount + 1},
            {
                if(
                    thisCount > 1,
                    {results = results.add([thisIndex, thisCount, thisValue])},
                );
                thisValue = val;
                thisIndex = i + 1;
                thisCount = 1;
            }
        )       
    };
    if(thisCount > 1, {results = results.add([thisIndex, thisCount, thisValue])});
    results;
}
)

a = [1, 2, 1, 1, 1, 2, 1, 1];
b = ~repeats.value(a);

a = [2, 3, 5, 5];
b = ~repeats.value(a);
(
~findIt = { |array, repeatThreshold=2|
	var chunks = [];
	var chunkIndex = 0;
	var chunkCount = 1;

	if (array.every(_.isNumber).not) {
		Error("Input contains non-numeric elements").throw;
	};

	while {chunkIndex < array.size} {
		while {array[chunkIndex] == array[chunkIndex+chunkCount]} {
			chunkCount = chunkCount + 1;
		};

		if (chunkCount >= repeatThreshold) {
			chunks = chunks.add([chunkIndex, chunkCount, array[chunkIndex]])
		};

		chunkIndex = chunkIndex + chunkCount;
		chunkCount = 1;
	};
	chunks
};
)

// testing:
(
var testArrays = [
	[],
	[1],
	[2, 3, 5, 5],
	[1, 2, 1, 1, 1, 2, 1, 1],
	[2, 3, 5, 5, 7, 7, 8, 0, 7, 7, 7, 2, 1, 1, 1, 3],
	{ 6.rand } ! 30
];

testArrays.do { |testArr|
	"input: %, output: %\n".postf(testArr, ~findIt.(testArr))
};
)

Edit: added error handling for non-numeric inputs.

1 Like

Hello,

The following code could be shortened and be constructed in more elegant ways, but it seems to work:

(
~findRepetitionsAt = { |collection|
	var
	collSize = collection.size-1,
	at = 0;
	
	collSize.do{ 
		(collection.[at] == collection.[at+1]).if {
			(collection.[at] == collection.[at+2]).if {
				(
					"index" + at ++ "," + (at+1) + "and" + (at+2) + 
					"have" + collection.[at] ++ ".\n <-" + collection.[at]++
					":  3 times."
				).postln;
				at = at + 2
			}{
				(
					"index" + at + "and" + (at+1) +
					"have" + collection.[at] ++ ".\n <-" + collection.[at]++
					":  twice."
				).postln;
				at = at + 1
			}
		}{
			at = at + 1;
		}
	}
}
)

x = [2, 3, 5, 5];
y = [1, 2, 1, 1, 1, 2, 1, 1]; 

~findRepetitionsAt.(x)
~findRepetitionsAt.(y)

‘while’ seems to be better than ‘.do’.

(
~findRepetitionsAt = { |array|
	var
	collSize = array.size-1,
	at = 0;
	
	while {
		at <= collSize
	}{
		(array.[at] == array.[at+1]).if {
			(array.[at] == array.[at+2] ).if {
				(
					"index" + at ++ "," + (at+1) + "and" + (at+2) + 
					"have" + array.[at] ++ ".\n <-" + array.[at]++
					":  3 times."
				).postln;
				at = at + 2;				
			}{
				(
					"index" + at + "and" + (at+1) +
					"have" + array.[at] ++ ".\n <-" + array.[at]++
					":  twice."
				).postln;
				at = at + 1
			}
		}{
			at = at + 1;
		}
	}
}
)

x = [2, 3, 5, 5];
y = [1, 2, 1, 1, 1, 2, 1, 1]; 
z = [1, 2, 1, 1, 1, 2, 1, 1, 1]; 

~findRepetitionsAt.(x)
~findRepetitionsAt.(y)
~findRepetitionsAt.(z)

Your solution doesn’t work for more than 3 repeating values.

x = [1, 1, 1, 1];
~findRepetitionsAt.(x);

/*
index 0, 1 and 2 have 1.
 <- 1:  3 times.
index 2 and 3 have 1.
 <- 1:  twice.
*/
1 Like

I would have proposed this:


x = [1, 2, 1, 1, 1, 2, 1, 1];

// if you just want the groups
y = x.separate { |a, b| a.first != b.first };
// if you want the sizes
z = x.separate { |a, b| a.first != b.first }.collect { |a| a.size };
// if you want the indices
i = [0] ++ z.integrate.drop(-1);
// sizes and indices as subarrays
[z, i].flop
// select all larger than 1
[z, i].flop.select { |a| a.first > 1 }
// indices and sizes as dictionary
[i, z].flop.flat.asDict
// select all larger than 1
[i, z].flop.flat.asDict.select { |a| a > 1 }
// values at the indices left as an exercise
5 Likes

Nice, I knew there was a neat functional solution out there somewhere! To match my spec above:

~findIt2 = { |arr|
	var sizes = arr.separate { |a, b| a.first != b.first }.collect { |a| a.size };
	var indices = [0] ++ sizes.integrate.drop(-1);
	var values = arr[indices];
	[indices, sizes, values].flop.select { |a| a[1] > 1 }
};
2 Likes

Thank you very much,
to all of you for the generosity of answers!
I think the latter is the answer that best suits what I had asked.