Сравните два списка или массива произвольной длины в C #; порядок важен

2

Скажем, у меня есть два списка или массивы строк. Например:

список 1: "a", "c", "b", "d" , "f" , "e"

список 2: "a", "d" , "e" , "f" , "h"

Список 1 и список 2 имеют произвольную длину. Список 1 может содержать элементы, не входящие в список 2, и наоборот.

Я хотел бы знать, когда элементы в списке 1 находятся в списке 2, и более конкретно я хочу знать, когда элемент в списке 1 найден в списке 2, но найден в другом порядке, чем он найден в списке 2, относительно элементов в списке 2. (Надеюсь, приведенный ниже пример поясняет это утверждение).

Например, "a" находится в обоих списках и является первым элементом в обоих списках. Итак, до сих пор все в порядке. "c" и "b" находятся только в первом списке, поэтому их можно игнорировать. "h" находится только во втором списке, и поэтому его можно игнорировать.

"d" находится как в первом, так и во втором списке. Он найден после "a" (первый элемент) в исходном списке. Несмотря на то, что позиция в первом списке отличается от второго списка, это нормально, потому что его относительный порядок одинаковый в двух списках (второе совпадение между списками).

В приведенном выше примере "f" и "e" находятся в "неправильном" порядке в списке 1, потому что "e" приходит до "f" во втором списке. Поэтому я хотел бы сообщить, что "e" и "f" находятся в неправильном порядке в первом списке. Как мне это сделать?

Решение должно быть в С#. Спасибо!

  • 0
    Я думаю, что было бы полезно, если бы вы могли предоставить пример кода того, как вы хотите манипулировать результатами такого сравнения. Прямо сейчас проблема может быть слишком открытой, чтобы дать содержательный ответ.
  • 0
    У вас есть какая-либо информация о «правильном» порядке товаров или один из списков «правильный»? Потому что для вашего примера вы говорите, что «f» и «e» находятся в неправильном порядке в списке 1, но вы также можете сказать, что они находятся в неправильном порядке в списке 2.
Показать ещё 3 комментария
Теги:
arrays
list
order
compare

5 ответов

2
Лучший ответ

string[] list1 = {"a", "c", "b", "d", "f", "e"};
string[] list2 = {"a", "d", "e", "f", "h"};
int i = 0;
var list1a = list1.Intersect(list2).Select(l=> new { item = l, order= i++});
int j = 0;
var list2a = list2.Intersect(list1).Select(l=> new { item = l, order= j++});

var r = from l1 in list1a join l2 in list2a on l1.order equals l2.order
        where l1.item != l2.item
        select new {result=string.Format("position {1} is item [{0}] on list1  but its [{2}] in list2", l1.item, l1.order, l2.item )};

r.Dump();

Результат

позиция 2 - это элемент [f] в списке1, но его [e] в списке2

позиция 3 - это элемент [e] в списке1, но его [f] в списке2

  • 0
    Насколько я понимаю, он сравнивает только исходные последовательности одинаковой длины и соответствующие элементы (с равными индексами). В моем случае две мои последовательности имеют разную длину и могут рассматриваться как совпадение (или, во всяком случае, ОК), даже если элементы с разными индексами отличаются, при условии, что последовательности одинаковы.
  • 0
    я заметил, что вы не были заинтересованы в сравнении последовательностей ... приведенный выше запрос даст желаемые результаты ... попробуйте запустить в LinqPad ...
Показать ещё 1 комментарий
1

Левенштейн расстояние? Я думаю, что первое решение, которое вы уже приняли, имеет недостаток. Он скажет вам, что все не в порядке, даже если одна маленькая вещь вставлена:

      string[] list1 = { "a", "c", "b", "d", "j", "e", "f" };
      string[] list2 = { "a", "d", "e", "f", "h", "j" };

который говорит, что j, e, f не соответствуют порядку, поскольку j был вставлен.

Это указывает на проблему, с которой вы сталкиваетесь. Существует множество решений, даже более чем одного оптимального решения, для проблемы того, что не соответствует порядку. Является ли порядок J или e и f? Все ли они не в порядке? Есть что-то, называемое алгоритм расстояния Levenshtein, который находит минимальное количество вставок и операций удаления, необходимых для начала работы с множеством A и заканчивается набором B Есть несколько лучших решений, это просто находит один из них.

Этот следующий алгоритм корректно выводит, что в list1, j был вставлен и e, f сдвинуты, но все еще в правильном порядке.

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using Math = System.Math;

namespace LevCompareLists {

  class Program {

    static void Main(string[] args) {

      string[] list1 = { "a", "c", "b", "d", "j", "e", "f" };
      string[] list2 = { "a", "d", "e", "f", "h", "j" };

      int?[] aMap21 = Levenshtein(list2, list1);
      int?[] aMap12 = Levenshtein(list1, list2);

    }

    public static int?[] Levenshtein(string[] Src, String[] Dst) {
      // this finds a minimum difference solution of inserts and deletes that maps Src to Dst
      // it returns the map from the perspective of Dst, i.e.:
      //   each element of the return array contains the Src index of the corresponging element in B 
      //   a null value means the element in B was inserted, and never existed in A
      //
      // Given A = {"a", "c", "b", "d", "j", "e", "f"}
      //       B = {"a", "d", "e", "f", "h", "j"};
      //
      // Levenshtein(B, A):
      //  a c b d j e f     <-- A
      //  0     1   2 3     <-- aMap
      //  a     d   e f h j <-- B
      //
      // Levenshtein(A, B):
      //  a     d   e f h j  <-- B   
      //  0     3   5 6      <-- aMap
      //  a c b d j e f      <-- A
      //
      // see: http://en.wikipedia.org/wiki/Levenshtein_distance

      int cSrc = Src.Length; //length of s
      int cDst = Dst.Length; //length of t
      if (cSrc == 0 || cDst == 0) return null;

      //**** create the Levenshtein matrix 
      // it has at 1 extra element in each dimension to contain the edges
      int[,] aLev = new int[cSrc + 1, cDst + 1]; // the matrix
      int iSrc, iDst;
      // Load the horizontal and vertical edges
      for (iSrc = 0; iSrc <= cSrc; aLev[iSrc, 0] = iSrc++) ;
      for (iDst = 0; iDst <= cDst; aLev[0, iDst] = iDst++) ;
      // load the interior
      for (iSrc = 1; iSrc <= cSrc; iSrc++)
        for (iDst = 1; iDst <= cDst; iDst++)
          aLev[iSrc, iDst] = Math.Min(Math.Min(aLev[iSrc - 1, iDst] + 1, aLev[iSrc, iDst - 1] + 1),
           aLev[iSrc - 1, iDst - 1] + ((Dst[iDst - 1] == Src[iSrc - 1]) ? 0 : 2));

      DumpLevMatrix(aLev, Src, Dst);  // Debug

      //**** create the return map, using the Levenshtein matrix
      int?[] aMap = new int?[cDst];  // this is the return map
      iSrc = cSrc;  // start in lower right corner of the Levenshtein matrix
      iDst = cDst;  // start in lower right corner of the Levenshtein matrix
      // work backwards to pick best solution
      while ((iSrc >= 0) || (iDst >= 0)) {
        if ((iSrc > 0) && (iDst > 0)) {
          // enter here if iSrc and iDst are in the lev matrix and not on its edge
          int nCur = aLev[iSrc, iDst];
          int nIns = nCur - aLev[iSrc, iDst - 1];  // if move along B to find match, it was an insert
          int nDel = nCur - aLev[iSrc - 1, iDst];  // if move along A to find match, it was a deletion
          if (nIns == 1)                 // this char was NOT in A, but was inserted into B
            iDst--;                         //   Leave map of B[j] to nowher, scan to previous B (--j)
          else if (nDel == 1)            // this char was in A, but is missing in B
            iSrc--;                         //   Don't map any B, scan to previous A (--i)
          else                           // Match
            aMap[iDst-- - 1] = iSrc-- - 1;       //   After map B[j] to A[i], scan to prev A,B (--i, --j)
        } else {
          if (iDst > 0)       // remaining chars are inserts, Leave map of B[j] to nowher, scan to previous B (--j)
            iDst--;
          else if (iSrc > 0)  // Delete to the end, deletes do nothing
            iSrc--;
          else
            break;
        }
      }

      DumpMap(aMap, Dst); // Debug

      return aMap;

    }

    // just for debugging
    static void DumpLevMatrix(int[,] aLev, string[] Src, string[] Dst) {

      StringBuilder sb = new StringBuilder();
      int cSrc = Src.Length;
      int cDst = Dst.Length;
      int iSrc, iDst;
      sb.Length = 6;
      for (iDst = 0; iDst < cDst; ++iDst)
        sb.AppendFormat("{0,-3}", Dst[iDst]);
      Console.WriteLine(sb.ToString());
      for (iSrc = 0; iSrc <= cSrc; ++iSrc) {
        if (iSrc == 0)
          sb.Length = 3;
        else {
          sb.Length = 0;
          sb.AppendFormat("{0,-3}", Src[iSrc - 1]);
        }
        for (iDst = 0; iDst <= cDst; ++iDst)
          sb.AppendFormat("{0:00}", aLev[iSrc, iDst]).Append(" ");
        Console.WriteLine(sb.ToString());
      }

    }

    // just for debugging
    static void DumpMap(int?[] aMap, string[] Dst) {

      StringBuilder sb = new StringBuilder();
      for (int iMap = 0; iMap < aMap.Length; ++iMap)
        sb.AppendFormat("{0,-3}", Dst[iMap]);  // dst and map are same size
      Console.WriteLine(sb.ToString());
      sb.Length = 0;
      for (int iMap = 0; iMap < aMap.Length; ++iMap)
        if (aMap[iMap] == null)
          sb.Append("   ");
        else
          sb.AppendFormat("{0:00}", aMap[iMap]).Append(" ");
      Console.WriteLine(sb.ToString());

    }

  }
}
1

Как насчет

list1.Intersect(list2).SequenceEquals(list2.Intersect(list1))
  • 0
    Я вижу, что мое описание проблемы было не совсем ясным. Л.Бушкин прав в том, что описание моих ожидаемых результатов было бы полезно. (И теперь я понимаю, почему и вы, и ehosca предложили SequenceEquals - при условии, что я просто хочу получить истинный / ложный ответ). Но идея использования Intersect наверняка сократит часть написанного мною кода, так что большое спасибо!
1

У меня нет кода, но это должны быть два основных шага:

  • Удалите все элементы, которые находятся только в одном списке (либо сократите один из списков, либо создайте третий "чистый" список)
  • Некоторый тип diff - поиск первого несоответствующего элемента, а затем отчет о каждом следующем элементе, пока оба списка не будут синхронизированы снова.

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

  • 0
    Я не уверен, что следую первому предложению (с двумя пунктами), но я предполагаю, что мое оригинальное описание проблемы не было очень ясным. Сожалею! Сейчас я пытаюсь выполнить итерацию по обоим спискам во вложенном порядке, чтобы исключить элементы, не являющиеся общими ни для одного из списков, чтобы в итоге я получил два новых списка (или оба измененных исходных списка), которые содержат только существующее содержимое в обоих списках, в списке оригинальная последовательность. Затем я беру эти два измененных списка и использую указатель на каждую идею списка, увеличивая местоположение ссылки, пока не найду несоответствие (и затем я записываю несоответствие).
  • 0
    Я хотел бы отдать должное вам и Васу, но, похоже, я могу отметить только один ответ как правильный. Хотя оба очень одинаково полезны для меня :(
0

Как насчет этого:

string[] list1 = { "a", "c", "b", "d", "f", "e" };
string[] list2 = { "a", "d", "e", "f", "h" };

var indexedList1 = list1.Select((x, i) => new
{
    Index = i,
    Item = x
});

var indexedList2 = list2.Select((x, i) => new
    {
        Index = i,
        Item = x
    });

var intersectedWithIndexes = indexedList2
    .Join(indexedList1,
          x => x.Item,
          y => y.Item,
          (x, y) => new
        {
            ExpectedIndex = x.Index,
            ActualIndex = y.Index,
            x.Item
        })
    .Where(x => x.ActualIndex != x.ExpectedIndex)
    .ToArray();

var outOfOrder = intersectedWithIndexes
    .Select((x, i) => new
        {
            Item = x,
            index = i
        })
    .Skip(1)
    .Where(x => x.Item.ActualIndex < intersectedWithIndexes[x.index - 1].ActualIndex ||
            x.Item.ExpectedIndex < intersectedWithIndexes[x.index - 1].ExpectedIndex)
    .Select(x => new
        {
            ExpectedBefore = x.Item,
            ExpectedAfter = intersectedWithIndexes[x.index - 1]
        });

foreach (var item in outOfOrder)
{
    Console.WriteLine("'{0}' and '{1}' are out of order at index {2}",
              item.ExpectedBefore.Item,
              item.ExpectedAfter.Item,
              item.ExpectedBefore.ActualIndex);
}

выход:

'f' and 'e' are out of order at index 4
  • 0
    Да, это тоже работает. И тебе спасибо!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню