def infer(self, x: np.ndarray, y: np.ndarray | None = None, **kwargs) -> np.ndarray:
        """
        Infer the attacked feature.

        :param x: Input to attack. Includes all features except the attacked feature.
        :param y: The labels expected by the membership attack.
        :param values: Possible values for attacked feature. For a single column feature this should be a simple list
                       containing all possible values, in increasing order (the smallest value in the 0 index and so
                       on). For a multi-column feature (for example 1-hot encoded and then scaled), this should be a
                       list of lists, where each internal list represents a column (in increasing order) and the values
                       represent the possible values for that column (in increasing order).
        :type values: list
        :return: The inferred feature values.
        """
        if self.estimator.input_shape is not None:
            if isinstance(self.attack_feature, int) and self.estimator.input_shape[0] != x.shape[1] + 1:
                raise ValueError("Number of features in x + 1 does not match input_shape of the estimator")

        if "values" not in kwargs:
            raise ValueError("Missing parameter `values`.")
        values: list | None = kwargs.get("values")
        if not values:
            raise ValueError("`values` cannot be None or empty")

        if y is not None:
            if y.shape[0] != x.shape[0]:
                raise ValueError("Number of rows in x and y do not match")

        # single index
        if isinstance(self.attack_feature, int):
            first = True
            for value in values:
                v_full = np.full((x.shape[0], 1), value).astype(x.dtype)
                x_value = np.concatenate((x[:, : self.attack_feature], v_full), axis=1)
                x_value = np.concatenate((x_value, x[:, self.attack_feature :]), axis=1)
                predicted = self.membership_attack.infer(x_value, y, probabilities=True)
                if first:
                    probabilities = predicted
                    first = False
                else:
                    probabilities = np.hstack((probabilities, predicted))

            # needs to be of type float, so that we can later replace back the actual values
            value_indexes = np.argmax(probabilities, axis=1).astype(x.dtype)
            pred_values = np.zeros_like(value_indexes)
            for index, value in enumerate(values):
                pred_values[value_indexes == index] = value
        else:  # 1-hot encoded feature. Can also be scaled.
            first = True
            # assumes that the second value is the "positive" value and that there can only be one positive column
            for index, value in enumerate(values):
                curr_value = np.zeros((x.shape[0], len(values)))
                curr_value[:, index] = value[1]
                for not_index, not_value in enumerate(values):
                    if not_index != index:
                        curr_value[:, not_index] = not_value[0]
                x_value = np.concatenate((x[:, : self.attack_feature.start], curr_value), axis=1)
                x_value = np.concatenate((x_value, x[:, self.attack_feature.start :]), axis=1)

                predicted = self.membership_attack.infer(x_value, y, probabilities=True)
                if first:
                    probabilities = predicted
                else:
                    probabilities = np.hstack((probabilities, predicted))
                first = False
            value_indexes = np.argmax(probabilities, axis=1).astype(x.dtype)
            pred_values = np.zeros_like(probabilities)
            for index, value in enumerate(values):
                curr_value = np.zeros(len(values))
                curr_value[index] = value[1]
                for not_index, not_value in enumerate(values):
                    if not_index != index:
                        curr_value[not_index] = not_value[0]
                pred_values[value_indexes == index] = curr_value
        return pred_values

        Infer the attacked feature.

        :param x: Input to attack. Includes all features except the attacked feature.
        :param y: The labels expected by the membership attack.
        :param values: Possible values for attacked feature. For a single column feature this should be a simple list
                       containing all possible values, in increasing order (the smallest value in the 0 index and so
                       on). For a multi-column feature (for example 1-hot encoded and then scaled), this should be a
                       list of lists, where each internal list represents a column (in increasing order) and the values
                       represent the possible values for that column (in increasing order).
        :type values: list
        :return: The inferred feature values.
        
art/attacks/inference/attribute_inference/meminf_based.py