2016-10-20 3 views
0

Есть ли метод StartWith для массивов в .NET? Или что-то подобное в LINQ?Метод StartWith для массивов

var arr1 = { "A", "B, "C" } 
var arr2 = { "A", "B, "C", "D" } 

var arr3 = { "A", "B, "CD" } 
var arr4 = { "E", "A, "B", "C" } 

arr2.StartWith(arr1) // true 
arr1.StartWith(arr2) // false 

arr3.StartWith(arr1) // false 
arr4.StartWith(arr1) // false 

Или я должен сделать это просто:

bool StartWith(string[] arr1, string[] arr2) 
{ 
    if (arr1.Count() < arr2.Count) return false; 

    for (var i = 0; i < arr2.Count(), i++) 
    { 
     if (arr2[i] != arr1[i]) return false; 
    } 

    return true; 
} 

Я ищу наиболее эффективным способом, чтобы сделать это.

+0

ли это "просто". Нет никакого встроенного способа сделать это. Btw .: вопросы о рабочем коде должны идти в codereview.stackexchange.com – HimBromBeere

+1

избегать использования '.Count()' use '.Length' –

+0

Не предполагайте, что все последовательности являются массивами. Это текущий год. Используйте 'IEnumerable '. Вызовите GetEnumerator() по обоим параметрам и зациклируйте на них. –

ответ

6
bool answer = arr2.Take(arr1.Length).SequenceEqual(arr1); 
+0

, я думаю, вам также нужно убедиться, что 'arr2.Length> = arr1.Length', поскольку более короткий массив не может начинаться с более длинного. – juharr

+0

Нет необходимости: Если 'arr2' короче, чем' arr1', то 'arr2.Take (arr1.Length)' вернет весь 'arr2', а' .SequenceEqual (arr1) 'будет правильно возвращать false. – Blorgbeard

5

Вы можете сделать:

var result = arr2.Take(arr1.Length).SequenceEqual(arr1); 

Чтобы оптимизировать его дальше, вы можете добавить проверку arr2.Length >= arr1.Length в начале, как:

var result = arr2.Length >= arr1.Length && arr2.Take(arr1.Length).SequenceEqual(arr1); 

конечный результат будет таким же.

+1

Отсутствует проверка, чтобы убедиться, что 'arr1' не длиннее, чем' arr2' – juharr

+0

@juharr, спасибо, это должно оптимизировать его. – Habib

3

Попробуйте Enumerable.SequenceEqual (a1, a2), но подрезать ваш первый массив, то есть,

var arr1 = { "A", "B, "C" } 
var arr2 = { "A", "B, "C", "D" } 

if (Enumerable.SequenceEqual(arr1, arr2.Take(arr1.Length)) 
+0

Или используйте его как метод расширения, как предлагается в других ответах. – HimBromBeere

6

Ваш "striaghtformward" путь как большинство методов LINQ будет делать это в любом случае. Есть несколько настроек, которые вы могли бы сделать. Например, сделайте это методом расширения и используйте сопоставитель для сравнения двух типов, чтобы можно было использовать пользовательские сопоставления.

public static class ExtensionMethods 
{ 
    static bool StartWith<T>(this T[] arr1, T[] arr2) 
    { 
     return StartWith(arr1, arr2, EqualityComparer<T>.Default); 
    } 

    static bool StartWith<T>(this T[] arr1, T[] arr2, IEqualityComparer<T> comparer) 
    { 
     if (arr1.Length < arr2.Length) return false; 

     for (var i = 0; i < arr2.Length, i++) 
     { 
      if (!comparer.Equals(arr2[i], arr1[i])) return false; 
     } 

     return true; 
    } 
} 

UPDATE: Для развлечения я решил взять время и написать немного больше «продвинутую» версию, которая будет работать с любыми IEnumerable<T> и не только массивами.

public static class ExtensionMethods 
{ 
    static bool StartsWith<T>(this IEnumerable<T> @this, IEnumerable<T> @startsWith) 
    { 
     return StartsWith(@this, startsWith, EqualityComparer<T>.Default); 
    } 

    static bool StartsWith<T>(this IEnumerable<T> @this, IEnumerable<T> startsWith, IEqualityComparer<T> comparer) 
    { 
     if (@this == null) throw new ArgumentNullException("this"); 
     if (startsWith == null) throw new ArgumentNullException("startsWith"); 
     if (comparer == null) throw new ArgumentNullException("comparer"); 

     //Check to see if both types implement ICollection<T> to get a free Count check. 
     var thisCollection = @this as ICollection<T>; 
     var startsWithCollection = startsWith as ICollection<T>; 
     if (thisCollection != null && startsWithCollection != null && (thisCollection.Count < startsWithCollection.Count)) 
      return false; 

     using (var thisEnumerator = @this.GetEnumerator()) 
     using (var startsWithEnumerator = startsWith.GetEnumerator()) 
     { 
      //Keep looping till the startsWithEnumerator runs out of items. 
      while (startsWithEnumerator.MoveNext()) 
      { 
       //Check to see if the thisEnumerator ran out of items. 
       if (!thisEnumerator.MoveNext()) 
        return false; 

       if (!comparer.Equals(thisEnumerator.Current, startsWithEnumerator.Current)) 
        return false; 

      } 
     } 
     return true; 
    } 
} 
0

Вы не хотите, чтобы требовать все, чтобы быть массивом, и вы не хотите, чтобы позвонить Count() на IEnumerable<T>, что может быть большой запрос, когда вы только действительно хотите, чтобы обнюхать первые четыре предметы или что-то еще.

public static class Extensions 
{ 
    public static void Test() 
    { 
     var a = new[] { "a", "b" }; 
     var b = new[] { "a", "b", "c" }; 
     var c = new[] { "a", "b", "c", "d" }; 
     var d = new[] { "x", "y" }; 

     Console.WriteLine("b.StartsWith(a): {0}", b.StartsWith(a)); 
     Console.WriteLine("b.StartsWith(c): {0}", b.StartsWith(c)); 
     Console.WriteLine("b.StartsWith(d, x => x.Length): {0}", 
          b.StartsWith(d, x => x.Length)); 
    } 

    public static bool StartsWith<T>(
     this IEnumerable<T> sequence, 
     IEnumerable<T> prefixCandidate, 
     Func<T, T, bool> compare = null) 
    { 
     using (var eseq = sequence.GetEnumerator()) 
     using (var eprefix = prefixCandidate.GetEnumerator()) 
     { 

      if (compare == null) 
      { 
       compare = (x, y) => Object.Equals(x, y); 
      } 

      eseq.MoveNext(); 
      eprefix.MoveNext(); 

      do 
      { 
       if (!compare(eseq.Current, eprefix.Current)) 
        return false; 

       if (!eprefix.MoveNext()) 
        return true; 
      } 
      while (eseq.MoveNext()); 

      return false; 
     } 
    } 

    public static bool StartsWith<T, TProperty>(
     this IEnumerable<T> sequence, 
     IEnumerable<T> prefixCandidate, 
     Func<T, TProperty> selector) 
    { 
     using (var eseq = sequence.GetEnumerator()) 
     using (var eprefix = prefixCandidate.GetEnumerator()) 
     { 
      eseq.MoveNext(); 
      eprefix.MoveNext(); 

      do 
      { 
       if (!Object.Equals(
         selector(eseq.Current), 
         selector(eprefix.Current))) 
       { 
        return false; 
       } 

       if (!eprefix.MoveNext()) 
        return true; 
      } 
      while (eseq.MoveNext()); 

      return false; 
     } 
    } 
} 
0

Вот несколько способов сделать это. Я не оптимизировал и полностью не проверял все, везде есть место для улучшения. Но это должно дать вам некоторую идею.

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

Методы и показатели результатов:

StartsWith1 00:00:01.9014586 
StartsWith2 00:00:02.1227468 
StartsWith3 00:00:03.2222109 
StartsWith4 00:00:05.5544177 

Метод испытания:

var watch = new Stopwatch(); 
watch.Start(); 
for (int i = 0; i < 10000000; i++) 
{ 
    bool test = action(arr2, arr1); 
} 
watch.Stop(); 
return watch.Elapsed; 

Методы:

public static class IEnumerableExtender 
{ 
    public static bool StartsWith1<T>(this IEnumerable<T> source, IEnumerable<T> compare) 
    { 
     if (source.Count() < compare.Count()) 
     { 
      return false; 
     } 

     using (var se = source.GetEnumerator()) 
     { 
      using (var ce = compare.GetEnumerator()) 
      { 
       while (ce.MoveNext() && se.MoveNext()) 
       { 
        if (!ce.Current.Equals(se.Current)) 
        { 
         return false; 
        } 
       } 
      } 
     } 

     return true; 
    } 

    public static bool StartsWith2<T>(this IEnumerable<T> source, IEnumerable<T> compare) => 
     compare.Take(source.Count()).SequenceEqual(source); 

    public static bool StartsWith3<T>(this IEnumerable<T> source, IEnumerable<T> compare) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException(nameof(source)); 
     } 

     if (compare == null) 
     { 
      throw new ArgumentNullException(nameof(compare)); 
     } 

     if (source.Count() < compare.Count()) 
     { 
      return false; 
     } 

     return compare.SequenceEqual(source.Take(compare.Count())); 
    } 

    public static bool StartsWith4<T>(this IEnumerable<T> arr1, IEnumerable<T> arr2) 
    { 
     return StartsWith4(arr1, arr2, EqualityComparer<T>.Default); 
    } 
    public static bool StartsWith4<T>(this IEnumerable<T> arr1, IEnumerable<T> arr2, IEqualityComparer<T> comparer) 
    { 
     if (arr1.Count() < arr2.Count()) return false; 

     for (var i = 0; i < arr2.Count(); i++) 
     { 
      if (!comparer.Equals(arr2.ElementAt(i), arr1.ElementAt(i))) return false; 
     } 

     return true; 
    } 
} 
+0

Вы можете улучшить StartsWith1 с помощью нескольких настроек. Посмотрите [обновленную часть моего ответа] (http://stackoverflow.com/a/40163518/80274), это почти то же самое, что и StartsWith1, за исключением того, что я добавляю приведение в 'ICollection ', чтобы выполнить проверку длины и если я не могу, я просто наблюдаю за перечислителем 'arr1', чтобы закончились элементы до' arr2'. это удаляет дополнительную перечисление списка, если список не реализовал 'ICollection ' внутренне сохраняя вас потенциально довольно много циклов для больших списков или списков, поддерживаемых методами 'yeild return'. –

+0

Каждый раз, когда вы вызываете Count() в IEnumerable, вы перечисляете все элементы, если это не внутренний массив, а, например, некоторый запрос linq. ElementAt просто. В StartsWith4 вы сделали в основном цикл foreach, но с временной сложностью O (n^2), это ужасно. Пожалуйста, не обрабатывайте IEnumerable как массив, перечислите его только один раз. –

Смежные вопросы