Подтвердить что ты не робот

Найти массив внутри другого большего массива

Недавно мне было предложено написать 3 тестовые программы для работы. Они будут написаны с использованием только основного Java API и любой тестовой среды по моему выбору. Модульные тесты должны быть реализованы там, где это необходимо.

Хотя я и не получил никакой обратной связи, я полагаю, что им не нравились мои решения (в противном случае я бы слышал от них), поэтому я решил показать здесь свои программы и спросить, можно ли считать эту реализацию хорошей, а если нет, то почему?

Чтобы избежать путаницы, я попрошу только первый на данный момент.

Внедрить функцию, которая находит массив в другом более крупном массиве. Это должен принимать два массива в качестве параметров и он вернет индекс первый массив, где второй массив сначала происходит в полном объеме. Например, findArray ([2,3,7,1,20], [7,1]) следует return 2.

Я не пытался найти какое-либо существующее решение, но вместо этого хотел сделать это сам.

Возможные причины: 1. Должен быть статичным. 2. Следует использовать комментарии строк вместо блочных. 3. Сначала не проверял нулевые значения (я знаю, просто заметил слишком поздно). 4.?

UPDATE:
Было представлено несколько причин, и мне очень сложно выбрать один ответ, так как многие ответы имеют хорошее решение. Как упоминал @adietrich, я склонен полагать, что они хотели, чтобы я продемонстрировал знание основного API (они даже попросили написать функцию, а не писать алгоритм).

Я считаю, что лучший способ обеспечить работу - предоставить как можно больше решений, в том числе: 1. Внедрение с использованием метода Collections.indexOfSubList(), чтобы показать, что я знаю API основных коллекций. 2. Реализуйте с использованием подхода грубой силы, но обеспечите более элегантное решение. 3. Реализуйте использование алгоритма поиска, например Boyer-Moore. 4. Реализуйте использование комбинации System.arraycopy() и Arrays.equal(). Однако это не лучшее решение с точки зрения производительности, это покажет мои знания стандартных процедур массива.

Спасибо всем за ваши ответы!
КОНЕЦ ОБНОВЛЕНИЯ.

Вот что я написал:

Фактическая программа:

package com.example.common.utils;

/**
 * This class contains functions for array manipulations.
 * 
 * @author Roman
 *
 */
public class ArrayUtils {

    /**
     * Finds a sub array in a large array
     * 
     * @param largeArray
     * @param subArray
     * @return index of sub array
     */
    public int findArray(int[] largeArray, int[] subArray) {

        /* If any of the arrays is empty then not found */
        if (largeArray.length == 0 || subArray.length == 0) {
            return -1;
        }

        /* If subarray is larger than large array then not found */
        if (subArray.length > largeArray.length) {
            return -1;
        }

        for (int i = 0; i < largeArray.length; i++) {
            /* Check if the next element of large array is the same as the first element of subarray */
            if (largeArray[i] == subArray[0]) {

                boolean subArrayFound = true;
                for (int j = 0; j < subArray.length; j++) {
                    /* If outside of large array or elements not equal then leave the loop */
                    if (largeArray.length <= i+j || subArray[j] != largeArray[i+j]) {
                        subArrayFound = false;
                        break;
                    }
                }

                /* Sub array found - return its index */
                if (subArrayFound) {
                    return i;
                }

            }
        }

        /* Return default value */
        return -1;
    }

}

Тестовый код:

package com.example.common.utils;

import com.example.common.utils.ArrayUtils;

import junit.framework.TestCase;

public class ArrayUtilsTest extends TestCase {

    private ArrayUtils arrayUtils = new ArrayUtils();

    public void testFindArrayDoesntExist() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {8,9,10};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistSimple() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {3,4,5};

        int expected = 2;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistFirstPosition() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {1,2,3};

        int expected = 0;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistLastPosition() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {5,6,7};

        int expected = 4;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayDoesntExistPartiallyEqual() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {6,7,8};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistPartiallyEqual() {

        int[] largeArray = {1,2,3,1,2,3,4,5,6,7};
        int[] subArray = {1,2,3,4};

        int expected = 3;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArraySubArrayEmpty() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArraySubArrayLargerThanArray() {

        int[] largeArray = {1,2,3,4,5,6,7};
        int[] subArray = {4,5,6,7,8,9,10,11};

        int expected = -1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

    public void testFindArrayExistsVeryComplex() {

        int[] largeArray = {1234, 56, -345, 789, 23456, 6745};
        int[] subArray = {56, -345, 789};

        int expected = 1;
        int actual = arrayUtils.findArray(largeArray, subArray);

        assertEquals(expected, actual);
    }

}
4b9b3361

Ответ 1

Требование "использовать только базовые Java API" также может означать, что они хотели посмотреть, будете ли вы изобретать колесо. Таким образом, помимо вашей собственной реализации, вы можете дать однострочное решение, чтобы быть в безопасности:

public static int findArray(Integer[] array, Integer[] subArray)
{
    return Collections.indexOfSubList(Arrays.asList(array), Arrays.asList(subArray));
}

Может быть, а может и не быть хорошей идеей указать, что приведенный пример содержит недопустимые литералы массивов.

Ответ 2

Хорошо, с головы:

  • Да, должен быть статичным.

  • Компания, жалующаяся на это, не стоит работать.

  • Да, но что бы вы сделали? Вернуть? Или выбросить исключение? Это сделает исключение так, как оно есть.

  • Я думаю, что основная проблема заключается в том, что ваш код не очень элегантный. Слишком много проверок во внутреннем цикле. Слишком много избыточных проверок.

Просто грубо, с головы:

public int findArray(int[] largeArray, int[] subArray) {

    int subArrayLength = subArray.length;

    if (subArrayLength == 0) {
        return -1;
    }

    int limit = largeArray.length - subArrayLength;

    int i=0;

    for (int i = 0; i <= limit; i++) {
        boolean subArrayFound = true;

        for (int j = 0; j < subArrayLength; j++) {
            if (subArray[j] != largeArray[i+j]) {
                subArrayFound = false;
                break;
            }

        /* Sub array found - return its index */
        if (subArrayFound) {
            return i;
        }
    }

    /* Return default value */
    return -1;
}

Вы можете сохранить эту проверку для первого элемента, чтобы у вас не было накладных расходов на настройку логического цикла и цикла for для каждого отдельного элемента массива. Тогда вы будете смотреть на

public int findArray(int[] largeArray, int[] subArray) {

    int subArrayLength = subArray.length;

    if (subArrayLength == 0) {
        return -1;
    }

    int limit = largeArray.length - subArrayLength;

    int i=0;

    for (int i = 0; i <= limit; i++) {
        if (subArray[0] == largeArray[i]) {
            boolean subArrayFound = true;

            for (int j = 1; j < subArrayLength; j++) {
                if (subArray[j] != largeArray[i+j]) {
                    subArrayFound = false;
                    break;
                }

            /* Sub array found - return its index */
            if (subArrayFound) {
                return i;
            }
        }
    }

    /* Return default value */
    return -1;
}

Ответ 3

Для нахождения массива целых чисел в большем массиве целых чисел вы можете использовать такие же алгоритмы, как поиск подстроки в большей строке. Для этого существует много известных алгоритмов (см. Wikipedia). Особенно эффективный поиск строк Boyer-Moore для больших массивов. Алгоритм, который вы пытаетесь реализовать, не очень эффективен (Wikipedia называет это "наивной" реализацией).

По всем вопросам:

  • Да, такой метод должен быть статическим
  • Не волнует, что вопрос вкуса
  • Нулевая проверка может быть включена, или вы должны указать в JavaDoc, что пустые значения не разрешены, или JavaDoc должен указать, что когда любой параметр равен null, будет выбрано исключение NullPointerException.

Ответ 4

Clean and improved code 

public static int findArrayIndex(int[] subArray, int[] parentArray) {
    if(subArray.length==0){
        return -1;
    }
    int sL = subArray.length;
    int l = parentArray.length - subArray.length;
    int k = 0;
    for (int i = 0; i < l; i++) {
        if (parentArray[i] == subArray[k]) {
            for (int j = 0; j < subArray.length; j++) {
                if (parentArray[i + j] == subArray[j]) {
                    sL--;
                    if (sL == 0) {
                        return i;
                    }

                }

            }
        }

    }
    return -1;
}

Ответ 5

Ниже приведен подход с использованием алгоритма сопоставления с образцом KMP. Это решение принимает O(n+m). Где n = length of large array и m = length of sub array. Для получения дополнительной информации проверьте:

https://en.wikipedia.org/wiki/KMP_algorithm

Грубая сила берет O(n*m). Я только что проверил, что метод Collections.indexOfSubList также является O(n*m).

public static int subStringIndex(int[] largeArray, int[] subArray) {
    if (largeArray.length == 0 || subArray.length == 0){
      throw new IllegalArgumentException();
}
    if (subArray.length > largeArray.length){
      throw new IllegalArgumentException();
}

    int[] prefixArr = getPrefixArr(subArray);
    int indexToReturn = -1;

    for (int m = 0, s = 0; m < largeArray.length; m++) {
      if (subArray[s] == largeArray[m]) {
        s++;
      } else {
        if (s != 0) {
          s = prefixArr[s - 1];
          m--;
        }
      }
      if (s == subArray.length) {
        indexToReturn = m - subArray.length + 1;
        break;
      }
    }

    return indexToReturn;
  }

  private static int[] getPrefixArr(int[] subArray) {
    int[] prefixArr = new int[subArray.length];
    prefixArr[0] = 0;

    for (int i = 1, j = 0; i < prefixArr.length; i++) {
      while (subArray[i] != subArray[j]) {
        if (j == 0) {
          break;
        }
        j = prefixArr[j - 1];
      }

      if (subArray[i] == subArray[j]) {
        prefixArr[i] = j + 1;
        j++;
      } else {
        prefixArr[i] = j;
      }

    }
    return prefixArr;
  }

Ответ 6

Немного оптимизированный код, который был опубликован раньше:

public int findArray(byte[] largeArray, byte[] subArray) {
    if (subArray.length == 0) {
        return -1;
    }
    int limit = largeArray.length - subArray.length;
    next:
    for (int i = 0; i <= limit; i++) {
        for (int j = 0; j < subArray.length; j++) {
            if (subArray[j] != largeArray[i+j]) {
                continue next;
            }
        }
        /* Sub array found - return its index */
        return i;
    }
    /* Return default value */
    return -1;
}

Ответ 7

Я бы предложил следующие улучшения:

  • сделать статическую функцию, чтобы избежать создания экземпляра
  • условие внешнего цикла может быть i <= largeArray.length-subArray.length, чтобы избежать проверки внутри цикла
  • удалить тест (largeArray[i] == subArray[0]), который является избыточным

Ответ 8

int findSubArr(int[] arr,int[] subarr)
{
    int lim=arr.length-subarr.length;

    for(int i=0;i<=lim;i++)
    {
        int[] tmpArr=Arrays.copyOfRange(arr,i,i+subarr.length);
        if(Arrays.equals(tmpArr,subarr))
            return i;   //returns starting index of sub array
    }
    return -1;//return -1 on finding no sub-array   
}

UPDATE:

Повторное использование одного и того же экземпляра массива int:

int findSubArr(int[] arr,int[] subarr)
{
    int lim=arr.length-subarr.length;
    int[] tmpArr=new int[subarr.length];
    for(int i=0;i<=lim;i++)
    {
        System.arraycopy(arr,i,tmpArr,0,subarr.length);
        if(Arrays.equals(tmpArr,subarr))
          return i; //returns starting index of sub array
    }
    return -1;//return -1 on finding no sub-array   

}

Ответ 9

Здесь #indexOf из String:

/**
 * Code shared by String and StringBuffer to do searches. The
 * source is the character array being searched, and the target
 * is the string being searched for.
 *
 * @param   source       the characters being searched.
 * @param   sourceOffset offset of the source string.
 * @param   sourceCount  count of the source string.
 * @param   target       the characters being searched for.
 * @param   targetOffset offset of the target string.
 * @param   targetCount  count of the target string.
 * @param   fromIndex    the index to begin searching from.
 */
static int indexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    if (fromIndex >= sourceCount) {
        return (targetCount == 0 ? sourceCount : -1);
    }
    if (fromIndex < 0) {
        fromIndex = 0;
    }
    if (targetCount == 0) {
        return fromIndex;
    }

    char first = target[targetOffset];
    int max = sourceOffset + (sourceCount - targetCount);

    for (int i = sourceOffset + fromIndex; i <= max; i++) {
        /* Look for first character. */
        if (source[i] != first) {
            while (++i <= max && source[i] != first);
        }

        /* Found first character, now look at the rest of v2 */
        if (i <= max) {
            int j = i + 1;
            int end = j + targetCount - 1;
            for (int k = targetOffset + 1; j < end && source[j]
                    == target[k]; j++, k++);

            if (j == end) {
                /* Found whole string. */
                return i - sourceOffset;
            }
        }
    }
    return -1;
}

Ответ 10

Сначала по вашим возможным причинам:

  • Да. И класс final с конструктором private.
  • Не следует использовать такие комментарии вообще. Код должен быть понятным.
  • Вы в основном неявно проверяете null, обращаясь к полю length, который выдает NullPointerException. Только в случае largeArray.length == 0 и a subArray == null это проскальзывает.

Другие потенциальные причины:

  • Класс не содержит функции для манипуляций с массивами, в отличие от документации.
  • Документация для метода очень разрежена. Он должен указать, когда и какие исключения выбрасываются (например, NullPointerException), и какое возвращаемое значение ожидает, если второй массив не найден или пуст.
  • Код более сложный, чем необходимо.
    • Почему равенство первых элементов настолько важно, что оно получает свою собственную проверку?
    • В первом цикле предполагается, что будет найден второй массив, который является непреднамеренным.
    • Необязательная переменная и прыжок (boolean и break), что еще больше снижает удобочитаемость.
    • largeArray.length <= i+j нелегко понять. Следует проверить перед циклом, улучшая производительность на этом пути.
    • Я бы заменил операнды subArray[j] != largeArray[i+j]. Мне кажется более естественным.
    • Всем слишком долго.
  • В тестовом коде отсутствуют дополнительные случаи ребер (null массивы, первый массив пуст, оба массива пустые, первый массив содержится во втором массиве, второй массив содержит несколько раз и т.д.).
  • Почему последний тестовый пример с именем testFindArrayExistsVeryComplex?

Отсутствие упражнения - это спецификация типа компонента параметров массива, соответственно сигнатура метода. Это имеет огромное значение, является ли тип компонента примитивным типом или ссылочным типом. Решение adietrich предполагает ссылочный тип (поэтому его можно было бы обобщить как дальнейшее улучшение), my принимает примитивный тип (int).

Итак, вот мой снимок, сосредоточенный на коде/игнорировании документации и тестов:

public final class ArrayUtils {
    // main method

    public static int indexOf(int[] haystack, int[] needle) {
        return indexOf(haystack, needle, 0);
    }

    // helper methods

    private static int indexOf(int[] haystack, int[] needle, int fromIndex) {
        for (int i = fromIndex; i < haystack.length - needle.length; i++) {
            if (containsAt(haystack, needle, i)) {
                return i;
            }
        }
        return -1;
    }

    private static boolean containsAt(int[] haystack, int[] needle, int offset) {
        for (int i = 0; i < needle.length; i++) {
            if (haystack[i + offset] != needle[i]) {
                return false;
            }
        }
        return true;
    }

    // prevent initialization

    private ArrayUtils() {}
}

Ответ 11

    byte[] arr1 = {1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 1, 3, 4, 56, 6, 7};
    byte[] arr2 = {9, 1, 3};

    boolean i = IsContainsSubArray(arr1, arr2);

 public static boolean IsContainsSubArray(byte[] Large_Array, byte[] Sub_Array){
    try {
        int Large_Array_size, Sub_Array_size, k = 0;

        Large_Array_size = Large_Array.length;
        Sub_Array_size = Sub_Array.length;

        if (Sub_Array_size > Large_Array_size) {
            return false;
        }
        for (int i = 0; i < Large_Array_size; i++) {
            if (Large_Array[i] == Sub_Array[k]) {
                k++;
            } else {
                k = 0;
            }
            if (k == Sub_Array_size) {
                return true;
            }
        }
    } catch (Exception e) {
    }
    return false;
}

Ответ 12

Код из Гуавы:

import javax.annotation.Nullable;

/**
 * Ensures that an object reference passed as a parameter to the calling method is not null.
 *
 * @param reference an object reference
 * @param errorMessage the exception message to use if the check fails; will be converted to a
 *     string using {@link String#valueOf(Object)}
 * @return the non-null reference that was validated
 * @throws NullPointerException if {@code reference} is null
 */
public static <T> T checkNotNull(T reference, @Nullable Object errorMessage) {
    if (reference == null) {
        throw new NullPointerException(String.valueOf(errorMessage));
    }
    return reference;
}


/**
 * Returns the start position of the first occurrence of the specified {@code
 * target} within {@code array}, or {@code -1} if there is no such occurrence.
 *
 * <p>More formally, returns the lowest index {@code i} such that {@code
 * java.util.Arrays.copyOfRange(array, i, i + target.length)} contains exactly
 * the same elements as {@code target}.
 *
 * @param array the array to search for the sequence {@code target}
 * @param target the array to search for as a sub-sequence of {@code array}
 */
public static int indexOf(int[] array, int[] target) {
    checkNotNull(array, "array");
    checkNotNull(target, "target");
    if (target.length == 0) {
        return 0;
    }

    outer:
    for (int i = 0; i < array.length - target.length + 1; i++) {
        for (int j = 0; j < target.length; j++) {
            if (array[i + j] != target[j]) {
                continue outer;
            }
        }
        return i;
    }
    return -1;
}

Ответ 13

Я хотел бы сделать это тремя способами:

  1. Использование без импорта, то есть с использованием простых операторов Java.

  2. Использование основных API-интерфейсов JAVA - в некоторой степени или в значительной степени.

  3. Использование алгоритмов поиска по строковому шаблону, таких как KMP и т.д. (Вероятно, наиболее оптимизированный.)

1,2 и 3 все показано выше в ответах. Вот подход 2 с моей стороны:

public static void findArray(int[] array, int[] subArray) {

        if (subArray.length > array.length) {
            return;
        }

        if (array == null || subArray == null) {
            return;
        }

        if (array.length == 0 || subArray.length == 0) {
            return;
        }

        //Solution 1
        List<Integer> master = Arrays.stream(array).boxed().collect(Collectors.toList());
        List<Integer> pattern = IntStream.of(subArray).boxed().collect(Collectors.toList());

        System.out.println(Collections.indexOfSubList(master, pattern));

        //Solution2
        for (int i = 0; i <= array.length - subArray.length; i++) {
            String s = Arrays.toString(Arrays.copyOfRange(array, i, i + subArray.length));

            if (s.equals(Arrays.toString(subArray))) {
                System.out.println("Found at:" + i);
                return;
            }
        }
        System.out.println("Not found.");
    }