Выключить одним при скольжении?

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

Теперь я часто использую sliding в некоторых алгоритмах в Scala, но я чувствую, что это обычно приводит к чему-то очень похожим на ошибки "за собой", поскольку sliding m элементов в наборе размера n имеет размер n - m + 1 элемент. Или, что более тривиально, list sliding 2 - это один элемент, меньший, чем list.

Чувство, которое я получаю, состоит в том, что в этом шаблоне отсутствует абстракция, что-то, что будет частью sliding, часть чего-то большего - например foldLeft - to reduceLeft. Однако я не могу придумать, что это может быть. Может ли кто-нибудь помочь мне найти просветление здесь?

ОБНОВИТЬ

Поскольку люди не ясно, что я говорю, рассмотрим этот случай. Я хочу использовать строку. В принципе, каждая буква, которой не предшествует буква, должна быть в верхнем регистре, а все остальные буквы должны быть строчными. Используя sliding, я должен использовать специальный случай либо первую, либо последнюю букву. Например:

def capitalize(s: String) = s(0).toUpper +: s.toSeq.sliding(2).map {
  case Seq(c1, c2) if c2.isLetter => if (c1.isLetter) c2.toLower else c2.toUpper
  case Seq(_, x) => x
}.mkString

Ответ 1

Im принимая Owens ответ как вдохновение для этого.

Если вы хотите применить простой список diff() к списку, это можно рассматривать как эквивалентное следующему умножению матрицы.

a = (0 1 4 3).T

M = ( 1 -1  0  0)
    ( 0  1 -1  0)
    ( 0  0  1 -1)

diff(a) = M * a = (1 3 1).T

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

Таким образом, плюс плюс операция добавления списка (с последующим flatten или просто операция collect), а мультипликативный эквивалент - Some(_) или None, слайд с размером окна в два раза становится:

M = (Some(_) Some(_) None None)
    (None Some(_) Some(_) None)
    (None None Some(_) Some(_))

slide(a) = M "*" a = ((0 1) (1 4) (4 3)).T

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

операции diff или slide порядка m для ввода длины n потребуют использования матриц размера n-m + 1 × n.


Изменить. Решением может быть преобразование List[A] в List[Some[A]] а затем добавление или добавление (slideLeft или slideRight) с помощью None. Таким образом, вы можете справиться со всей магией внутри метода map.

list.slideLeft(2) {
  case Seq(Some(c1), Some(c2)) if c2.isLetter => if (c1.isLetter) c2.toLower else c2.toUpper
  case Seq(_, Some(x)) => x
}

Ответ 2

Я сталкиваюсь с этой проблемой все время в python/R/Matlab, где вы меняете() вектор, а затем не можете выровнять его с оригинальным! Это очень расстраивает.

Я думаю, что действительно не хватает того, что вектор содержит только зависимые переменные и предполагает, что вы, программист, отслеживаете независимые переменные, т.е. Размерность, которую охватывает диапазон коллекций.

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

Это лучшее, о чем я думал до сих пор.

РЕДАКТИРОВАТЬ

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

РЕДАКТИРОВАТЬ

Другим следствием было бы то, что преобразования, подобные sliding фактически представляют собой два преобразования: одно для зависимых переменных и одно для их оси.

Ответ 3

В вашем примере, я думаю, что код становится более сложным, потому что вы в основном хотите сделать map но работать со sliding что вводит краевые условия таким образом, что это не работает хорошо. Я думаю, что складка с аккумулятором, который запоминает соответствующее состояние, может быть проще концептуально:

def capitalize2(s: String) = (("", true) /: s){ case ((res, notLetter), c) => 
  (res + (if (notLetter) c.toUpper else c.toLower), !c.isLetter)
}._1

Я думаю, что это можно обобщить так, чтобы notLetter не мог запомнить n элементов, где n - размер скользящего окна.

Ответ 4

Преобразование, о котором вы просите, по своей сути уменьшает размер данных. Извините, нет другого способа взглянуть на это. tail также дает вам ошибки "за собой".

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

В этом случае вы можете использовать эти методы в List:

initializedSliding(init: List[A]) = (init ::: this).sliding(1 + init.length)
finalizedSliding(tail: List[A]) = (this ::: tail).sliding(1 + tail.length)

который будет поддерживать вашу длину списка. (Вы можете представить, как обобщать на не-списки, я уверен.)

Это аналог сгибания влево/вправо, когда вы передаете отсутствующие данные, чтобы выполнить парную (или более) операцию для каждого элемента списка.

Ответ 5

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

Оператор проблемы для вашего примера может быть грубо сформулирован как "Верхний регистр каждого символа, если он (a) является первым символом, или (b) следует буквенному символу". Как указывает Оуэн, первый символ - особый случай, и любая абстракция должна уважать это. Здесь есть возможность,

def slidingPairMap[A, B](s: List[A], f1: A => B, f2: (A, A) => B): List[B] = s match {
  case Nil => Nil
  case x :: _ => f1(x) +: s.sliding(2).toList.map { case List(x, y) => f2(x, y) } 
}

(не лучшая реализация, но вы получите эту идею). Это обобщает скользящие тройки, с ошибками "за-на-два" и т.д. Тип slidingPairMap дает понять, что выполняется специальный корпус.

Эквивалентная подпись может быть

def slidingPairMap[A, B](s: List[A], f: Either[A, (A, A)] => B): List[B]

Тогда f может использовать сопоставление с образцом, чтобы выяснить, работает ли он с первым элементом или с последующим.


Или, как говорит Оуэн в комментариях, почему бы не сделать модифицированный метод sliding который дает информацию о том, является ли элемент первым или нет,

def slidingPairs[A](s: List[A]): List[Either[A, (A, A)]]

Я предполагаю, что эта последняя идея изоморфна тому, что предлагает Дебильски в комментариях: проложите начало списка с помощью None, оберните все существующие элементы Some, а затем вызовите sliding.

Ответ 6

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

Заимствование из этих подходов для раздвижения привело бы к абстракции, которая не выявила бы одной проблемы:

(1::2::3::Nil).sliding(2)

даст

(1,2), (2,3), (3,1)

для круговых граничных условий и

(1,2), (2,3), (3,2)

для круговых граничных условий с обращением.

Ответ 7

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

def slidingFoldLeft[A, B] (seq: Seq[A], window: Int)(acc: B)(
    f: (B, Seq[A]) => B): B = {
  if (window > 0) {
    val iter = seq.sliding(window)
    iter.foldLeft(acc){
      // Operate normally
      case (acc, next) if iter.hasNext => f(acc, next)
      // It at the last <window> elements of the seq, handle current case and 
      // call recursively with smaller window
      case (acc, next) =>
        slidingFoldLeft(next.tail, window - 1)(f(acc, next))(f)
    }
  } else acc
}

def capitalizeAndQuestionIncredulously(s: String) =
  slidingFoldLeft(s.toSeq, 2)("" + s(0).toUpper) {
    // Normal iteration
    case (acc, Seq(c1, c2)) if c1.isLetter && c2.isLetter => acc + c2.toLower
    case (acc, Seq(_, c2))  if c2.isLetter                => acc + c2.toUpper
    case (acc, Seq(_, c2))                                => acc + c2
    // Last element of string
    case (acc, Seq(c)) => acc + "?!"
  }

def capitalizeAndInterruptAndQuestionIncredulously(s: String) =
  slidingFoldLeft(s.toSeq, 3)("" + s(0).toUpper) {
    // Normal iteration
    case (acc, Seq(c1, c2, _)) if c1.isLetter && c2.isLetter => acc + c2.toLower
    case (acc, Seq(_, c2, _))  if c2.isLetter                => acc + c2.toUpper
    case (acc, Seq(_, c2, _))                                => acc + c2
    // Last two elements of string
    case (acc, Seq(c1, c2)) => acc + " (commercial break) " + c2
    // Last element of string
    case (acc, Seq(c)) => acc + "?!"
  }

println(capitalizeAndQuestionIncredulously("hello my name is mAtthew"))
println(capitalizeAndInterruptAndQuestionIncredulously("hello my name is mAtthew"))

И выход:

Hello My Name Is Matthew?!
Hello My Name Is Matthe (commercial break) w?!

Ответ 8

Я не буду добавлять None после сопоставления с Some(_) элементами; обратите внимание, что очевидный способ сделать это (соответствие для двух Some в случае по умолчанию, как это было сделано в отредактированной Debilski) не так, как мы должны быть в состоянии изменить даже первую букву. Таким образом, абстракция уважает тот факт, что просто иногда нет предшественника. Использование getOrElse(false) гарантирует, что недостающий предшественник рассматривается как проваливший тест.

((None +: "foo1bar".toSeq.map(Some(_))) sliding 2).map {
   case Seq(c1Opt, Some(c2)) if c2.isLetter => if (c1Opt.map(_.isLetter).getOrElse(false)) c2.toLower else c2.toUpper
   case Seq(_, Some(x)) => x
}.mkString
res13: String = "Foo1Bar"

Благодарности: идея сопоставления элементов с Some(_) приходила ко мне через сообщение Дебильского.

Ответ 9

Я не уверен, что это решает вашу конкретную проблему, но мы могли бы легко представить пару методов, например slidingFromLeft(z: A, size: Int) и slidingToRight(z: A, size: Int) (где A - тип элемента коллекции), который при вызове, например,

List(1, 2, 3, 4, 5)

с аргументами, например (0, 3), должны производить соответственно

List(0, 0, 1), List(0, 1, 2), List(1, 2, 3), List(2, 3, 4), List(3, 4, 5)

а также

List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 0), List(5, 0, 0)

Ответ 10

Это та проблема, которая хорошо подходит для ориентированного на массив функционального языка, такого как J. В основном мы генерируем логическое значение с одним, соответствующим первой букве каждого слова. Для этого мы начинаем с булевской маркировки пробелов в строке. Например (строки с отступом три пробела - входы, результаты - с левым краем, " NB. " - начало комментария):

   str=. 'now  is  the    time'    NB. Example w/extra spaces for interest
   ]whspc=. ' '=str                NB. Mark where spaces are=1
0 0 0 1 1 0 0 1 1 0 0 0 1 1 1 1 0 0 0 0

Убедитесь, что (*.-.) ("А не") возвращает один только для "1 0":

   ]tt=. #:i.4                     NB. Truth table
0 0
0 1
1 0
1 1
   (*.-.)/"1 tt                    NB. Apply to 1-D sub-arrays (rows)
0 0 1 0                            NB. As hoped.

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

   2(*.-.)/\whspc                  NB. Apply to 2-ples
0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0

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

   #whspc
20
   #1,2(*.-.)/\whspc
20
   1,2(*.-.)/\whspc
1 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0

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

   'lc uc'=. 'abcdefghijklmnopqrstuvwxyz';'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
   (uc,' '){~lc i. str
NOW  IS  THE    TIME

Убедитесь, что вставка с помощью boolean дает правильный результат:

       (1,2(*.-.)/\whspc) } str,:(uc,' '){~lc i. str
Now  Is  The    Time

Настало время объединить все это в одно утверждение:

   (1,2(*.-.)/\' '=str) } str,:(uc,' '){~lc i. str
Now  Is  The    Time