«Деинтерлейсинг» списка в Scala - PullRequest
9 голосов
/ 03 ноября 2011

У меня есть список байтов, которые представляют необработанные сэмплы, считанные с аудиоинтерфейса.В зависимости от варианта использования и H / W каждая выборка может иметь длину от 1 до 4 байтов, а общее количество каналов в «потоке» может быть более или менее произвольным.Количество каналов и битов на выборку известно во время выполнения.

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

List(A1, A2, B1, B2, C1, C2, D1, D2, A3, A4, B3, B4, C3, C4, D3, D4)

, поэтому A1 - это первый байт первого сэмпла канала A ., A2 - второй байт того же сэмпла и т. Д.

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

List(List(A1, A2, A3, A4), List(B1, B2, B3, B4), List(C1, C2, C3, C4), List(D1, D2, D3, D4))

Как бы я поступил так в идиоматическом Scala?Я только начал изучать Scala несколько часов назад, и единственное неимперативное решение, которое я нашел, явно неоптимально:

def uninterleave(samples: Array[Byte], numChannels: Int, bytesPerSample: Int) = {
val dropAmount = numChannels * bytesPerSample
  def extractChannel(n: Int) = {
    def extrInner(in: Seq[Byte], acc: Seq[Byte]): Seq[Byte] = {
      if(in == List()) acc
      else extrInner(in.drop(dropAmount), in.take(bytesPerSample) ++ acc)
    }
    extrInner(samples.drop(n * bytesPerSample), Nil)
  }

  for(i <- 0 until numChannels) yield extractChannel(i)
}

Ответы [ 2 ]

12 голосов
/ 03 ноября 2011

Я бы сделал

samples.grouped(bytesPerSample).grouped(numChannels).toList
  .transpose.map(_.flatten)

Я бы не ручался за его производительность. Я бы предпочел избегать списков, к сожалению grouped их создает.

Может

samples.grouped(bytesPerSample).map(_.toArray)
  .grouped(numChannels).map(_.toArray)
  .toArray
  .transpose
  .map(flatten)

Тем не менее, много списков.

6 голосов
/ 03 ноября 2011

didierd ' ответ почти идеален, но, увы, я думаю, что один может немного его улучшить. Он занимается созданием всех списков, и транспонирование также является довольно сложной операцией. Если вы можете обрабатывать все данные одновременно, это вполне может быть достаточно.

Однако я использую Stream и использую небольшой трюк, чтобы избежать переноса.

Прежде всего, группировка такая же, только я превращаю вещи в потоки:

def getChannels[T](input: Iterator[T], elementsPerSample: Int, numOfChannels: Int) =
  input.toStream.grouped(elementsPerSample).toStream.grouped(numOfChannels).toStream

Далее я дам вам функцию для извлечения одного канала из этого:

def streamN[T](s: Stream[Stream[Stream[T]]])(channel: Int) = s flatMap (_(channel))

С их помощью мы можем декодировать потоки следующим образом:

// Sample input
val input = List('A1, 'A2, 'B1, 'B2, 'C1, 'C2, 'D1, 'D2, 'A3, 'A4, 'B3, 'B4, 'C3, 'C4, 'D3, 'D4)

// Save streams to val, to avoid recomputing the groups
val streams = getChannels(input.iterator, elementsPerSample = 2, numOfChannels = 4)

// Decode each one
def demuxer = streamN(streams) _
val aa = demuxer(0)
val bb = demuxer(1)
val cc = demuxer(2)
val dd = demuxer(3)

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

def source(elementsPerSample: Int, numOfChannels: Int) = Iterator.from(0).map { x =>
  "" + ('A' + x / elementsPerSample % numOfChannels).toChar +
  (x % elementsPerSample 
   + (x / (numOfChannels * elementsPerSample)) * elementsPerSample 
   + 1)
}.map { x => println("Saw "+x); x }

Затем вы можете попробовать что-то вроде этого:

val streams = getChannels(source(2, 4), elementsPerSample = 2, numOfChannels = 4)
def demuxer = streamN(streams) _
val cc = demuxer(2)
println(cc take 20 toList)
val bb = demuxer(1)
println(bb take 30 toList)
...