Тестирование диагонально смежных элементов во вложенных списках - PullRequest
3 голосов
/ 22 марта 2020

Это продолжение недавнего вопроса , который не был четко задан. Постер Aditi Jain разъяснения лишить законной силы ответ в некоторой степени уже опубликован там, следовательно, это новое сообщение.

Цель состоит в том, чтобы проверить нет ли во вложенных списках соседних по диагонали пар элементов, отрицательных по отношению друг к другу. Постер является новым для Haskell программирования.

Сигнатура функции:

checkNegation :: [[Int]] -> Bool

Примеры:

checkNegation [[1,2], [-2,3]] вернет False:

[ [ 1 ,  2],      -- 2, -2 are diagonally adjacent
  [-2 ,  3] ]

checkNegation [[1,2], [3,-1]] return False:

[ [ 1 ,  2],      -- 1, -1 are diagonally adjacent
  [ 3 , -1] ]

checkNegation [[1,2], [-1,3]] вернется True:

[ [ 1 ,  2],      -- no diagonally adjacent negatives
  [-1 ,  3] ]

checkNegation [[0,2,1], [3,1,-2], [3,-1,3]] вернется False:

[ [ 0 ,  2,  1],  -- 2, -2 are diagonally adjacent
  [ 3 ,  1, -2],
  [ 3 , -1,  3] ]

В исходном посте не было попыток кодирования.

(я не отмечаю это как CW, чтобы не мешать ответчикам получать очки репутации за свои усилия)

Ответы [ 4 ]

3 голосов
/ 22 марта 2020

Немного проще делать вещи, если мы берем матрицу строка за строкой. Например, для следующего:

  [a,b,c],
  [d,e,f],

Мы только хотим сравнить пары:

[(a,e),(b,f),(b,d),(c,e)]

Итак, первый шаг - написать функцию, которая создает этот список из двух соседних строк. .

diags xs ys = zip xs (drop 1 ys) ++ zip (drop 1 xs) ys

Мы используем drop 1 вместо tail, потому что в пустом списке это не ошибка, и то, как я собираюсь использовать эту функцию позже, будет использовать пустые списки.

Если мы используем это в сгибе, то это выглядит следующим образом:

anyDiags :: (a -> a -> Bool) -> [[a]] -> Bool
anyDiags p = fst . foldr f (False, [])
  where
    f xs (a, ys) = (a || or (zipWith p xs (drop 1 ys)) || or (zipWith p (drop 1 xs) ys), xs)

Мы также сделали это generic c для любого отношения.

Далее мы хотим выяснить, как проверить, являются ли два числа отрицаниями друг друга.

negEachOther x y = negate x == y

И тогда наша функция проверки отрицания выглядит следующим образом:

checkNegation = anyDiags negEachOther

Есть некоторые забавные вещи, которые мы можем сделать с помощью функции anyDiags здесь. На самом деле в нем спрятана писательская монада. С этим мы можем переписать фолд, чтобы использовать этот факт:

anyDiags :: (a -> a -> Bool) -> [[a]] -> Bool
anyDiags p = getAny . fst . foldrM f []
  where
    f xs ys = (Any (or (zipWith p xs (drop 1 ys)) || or (zipWith p (drop 1 xs) ys)), xs)

Хотя я не уверен, что это немного яснее.

В качестве альтернативы, мы могли бы сделать все это, используя zip xs (tail xs) трюк:

anyDiags :: (a -> a -> Bool) -> [[a]] -> Bool
anyDiags p xs = or (zipWith f xs (tail xs))
  where
    f xs ys = or (zipWith p xs (drop 1 ys)) || or (zipWith p (drop 1 xs) ys)
2 голосов
/ 22 марта 2020

Мы можем использовать утилиту diagonals из пакета Data.Universe.Helpers. Так, что

λ> diagonals [[0,2,1], [3,1,-2], [3,-1,3]]
[[0],[3,2],[3,1,1],[-1,-2],[3]]

, что составляет лишь половину того, что нам нужно. Итак, давайте перевернем наш 2D-список и применим diagonals еще раз. Для переворота списка потребуется операция reverse . transpose, так что

λ> (reverse . transpose) [[0,2,1], [3,1,-2], [3,-1,3]]
[[1,-2,3],[2,1,-1],[0,3,3]]

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

λ> (diagonals . reverse . transpose) [[0,2,1], [3,1,-2], [3,-1,3]]
[[1],[2,-2],[0,1,3],[3,-1],[3]]

Для всех диагоналей нам нужно объединить их. Так что в целом мы можем поступить так:

allDiags = (++) <$> diagonals . reverse . transpose <*> diagonals

Остальное применяет необходимый логический тест.

import Data.List (transpose)
import Data.Universe.Helpers (diagonals)

checkNegation :: Num a => Eq a => [[a]] -> Bool
checkNegation = and . map (and . (zipWith (\x y -> 0 /= (x + y)) <*> tail)) . allDiags
                where
                allDiags = (++) <$> diagonals . reverse . transpose <*> diagonals

λ> checkNegation [[0,2,1], [3,1,-2], [3,-1,3]]
False
λ> checkNegation [[1,2], [-1,3]]
True
1 голос
/ 22 марта 2020

Если у вас есть подобная матрица и вы хотите сравнить соседние диагональные элементы:

m = [[ 1, 2, 3, 4]
    ,[ 5, 6, 7, 8]
    ,[ 9,10,11,12]]

, то вы хотите сделать два сравнения. Во-первых, вы хотите сравнить, элемент за элементом, субматрицу, которую вы получаете, опуская первую строку и первый столбец (слева) с субматрицей, которую вы получаете, опуская последнюю строку и последний столбец (справа):

[[ 6, 7, 8]    [[ 1, 2, 3]
,[10,11,12]    ,[ 5, 6, 7]]

Во-вторых, вы хотите сравнить, элемент за элементом, субматрицу, которую вы получаете, опуская первую строку и последний столбец (слева) с подматрицей, которую вы получаете, опуская последнюю строку и первый столбец ( справа):

[[ 5, 6, 7]    [[ 2, 3, 4]
,[ 9,10,11]]   ,[ 6, 7, 8]]

Мы можем построить эти подматрицы, используя init, tail и map s из них:

m1 = tail (map tail m)   -- drop first row and first column
m2 = init (map init m)   -- drop last row and last column
m3 = tail (map init m)   -- drop first row and last column
m4 = init (map tail m)   -- drop last row and first column

, что дает:

λ> m1
[[6,7,8],[10,11,12]]
λ> m2
[[1,2,3],[5,6,7]]
λ> m3
[[5,6,7],[9,10,11]]
λ> m4
[[2,3,4],[6,7,8]]

Как мы можем сравнить две субматрицы? Ну, мы можем написать двумерную версию zipWith, чтобы применить двоичную функцию (скажем, сравнение) элемент за элементом к двум матрицам, точно так же, как zipWith применяет двоичную функцию элемент за элементом к двум спискам:

zipZipWith :: (a -> b -> c) -> [[a]] -> [[b]] -> [[c]]
zipZipWith f m1 m2 = zipWith zipRow m1 m2
  where zipRow r1 r2 = zipWith f r1 r2

Это работает, объединяя матрицы, строка за строкой, используя вспомогательную функцию zipRow. Для каждой пары строк zipRow объединяет строки, элемент за элементом, с функцией f. Это определение можно упростить до чуть менее ясного:

zipZipWith f m1 m2 = zipWith (zipWith f) m1 m2

В любом случае, чтобы проверить, являются ли соответствующие пары элементов в двух матрицах отрицательными по отношению друг к другу, мы можем использовать zipZipWith isNeg, где:

isNeg :: (Num a, Eq a) => a -> a -> Bool
isNeg x y = x == -y

Затем, чтобы проверить, являются ли любые из этих пар отрицательными, мы можем использовать concat, чтобы изменить матрицу логических значений в длинный список, и or, чтобы проверить на наличие True значения:

anyNegPairs :: (Num a, Eq a) => [[a]] -> [[a]] -> Bool
anyNegPairs ma mb = or . concat $ zipZipWith isNeg ma mb

Наконец, полная функция для сравнения будет выглядеть следующим образом:

noDiagNeg :: (Num a, Eq a) => [[a]] -> Bool
noDiagNeg m = not (anyNegPairs m1 m2 || anyNegPairs m3 m4)

Поскольку zipZipWith, как и zipWith, игнорирует «дополнительные» элементы, когда сравнивая аргументы разных размеров, на самом деле нет необходимости обрезать последний столбец / строку, поэтому определения подматрицы можно упростить, удалив все init s:

m1 = tail (map tail m)
m2 = m
m3 = tail m
m4 = map tail m

Мы могли бы написать m1 с точки зрения m4 для сохранения двойного вычисления map tail m:

m1 = tail m4

, но компилятор достаточно умен, чтобы понять это самостоятельно.

Итак, разумное окончательное решение будет:

noDiagNeg :: (Num a, Eq a) => [[a]] -> Bool
noDiagNeg m = not (anyNegPairs m1 m2 || anyNegPairs m3 m4)
  where
    m1 = tail (map tail m)
    m2 = m
    m3 = tail m
    m4 = map tail m

    anyNegPairs ma mb = or . concat $ zipZipWith isNeg ma mb
    isNeg x y = x == -y

zipZipWith :: (a -> b -> c) -> [[a]] -> [[b]] -> [[c]]
zipZipWith f m1 m2 = zipWith (zipWith f) m1 m2

и, похоже, в тестовых случаях он работает как нужно:

λ> noDiagNeg [[1,2],[-2,3]]
False
λ> noDiagNeg [[1,2],[3,-1]]
False
λ> noDiagNeg [[1,2],[-1,3]]
True
λ> noDiagNeg [[0,2,1],[3,1,-2],[3,-1,3]]
False

Это очень похоже на решение @ oisdk, хотя эту версию, возможно, будет легче понять, если вы еще не слишком знакомы со складками.

Сбой на (определенных) матрицах без элементов:

λ> noDiagNeg []
*** Exception: Prelude.tail: empty list
λ> noDiagNeg [[],[]]
*** Exception: Prelude.tail: empty list

, поэтому вы можете использовать метод @ oisdk для замены tail на drop 1, если это проблема. (На самом деле, я мог бы определить tail' = drop 1 как помощника и заменить все tail вызовы на tail' вызовы, так как это выглядело бы немного лучше.)

1 голос
/ 22 марта 2020

Сначала мы объединяем строки: сначала со вторым, затем со вторым, затем с третьим, затем с четвертым и т. Д.

Затем для каждой пары строк мы рассматриваем все клиновидные тройки ячейки, например:

--*---
-*-*--

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

Затем мы просто проверяем, являются ли какие-либо нижние ячейки отрицательными вершины.

За исключением того, что это имеет (буквально) крайний случай: начало и конец строк. Если мы сделаем эту тройную вещь в форме клина, мы пропустим первый и последний элементы верхнего ряда. Чтобы обойти это, мы сначала оборачиваем всю матрицу в Just, а затем расширяем каждую строку на Nothing s слева и справа:

[a,b,c]     ==>     [Nothing, Just a, Just b, Just c, Nothing]
[d,e,f]     ==>     [Nothing, Just d, Just e, Just f, Nothing]

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

checkNegation :: [[Int]] -> Bool
checkNegation matrix = any rowPairHasNegation rowPairs
    where
        extendedMatrix = map extendRow matrix
        extendRow row = [Nothing] ++ map Just row ++ [Nothing]

        rowPairs = extendedMatrix `zip` drop 1 extendedMatrix

        rowPairHasNegation (row, nextRow) =
            any cellTripleHasNegation $
                drop 1 row `zip` nextRow `zip` drop 2 nextRow

        cellTripleHasNegation ((x1y0, x0y1), x2y1) =
            isNegation x1y0 x0y1 || isNegation x1y0 x2y1

        isNegation (Just a) (Just b) = a == -b
        isNegation _ _ = False

Насколько я понимаю, это приведет к итерации по всей матрице ровно трижды - один раз как верхняя строка и дважды как нижняя строка, что означает O (n * m)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...