Проблемы с колодой карт в языковой среде

Не так давно я был в интервью, который требовал решения двух очень интересных проблем. Мне интересно, как вы подходите к решениям.

Проблема 1:

Продукт всего, кроме текущего

Напишите функцию, которая принимает в качестве входных данных целые массивы длины len, input и index и генерирует третий массив, результат: result [i] = произведение всего на вход, кроме ввода [index [i]]

Например, если функция вызывается с len = 4, input = {2,3,4,5} и index = {1,3,2,0}, тогда результат будет установлен на {40, 24,30,60}.

ВАЖНО: ваш алгоритм должен выполняться в линейном режиме.

Проблема 2: (тема была в одном из сообщений Jeff)

Разделение карты в случайном порядке

  • Дизайн (на С++ или С#) класса Deck для представления упорядоченной колоды карт, где колода содержит 52 карты, разделенных на 13 рангов (A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K) четырех костюмов: лопастей (?), Сердец (?), Бриллиантов (?) И клубов (?).

  • Основываясь на этом классе, разработайте и внедрите эффективный алгоритм для перетасовки колоды карт. Карты должны быть равномерно перетасованы, то есть каждая карта в оригинальной колоде должна иметь такую ​​же вероятность оказаться в любом возможном положении в перетасованной колоде. Алгоритм должен быть реализован в методе shuffle() класса Deck: void shuffle()

  • Какова сложность вашего алгоритма (как функция числа n карт в колоде)?

  • Объясните, как вы будете проверять, что ваши карты равномерно перетасовываются вашим методом (проверка черного ящика).

P.S. У меня было два часа, чтобы закодировать решения

Ответ 1

Первый вопрос:

int countZeroes (int[] vec) {
int ret = 0;
foreach(int i in vec) if (i == 0) ret++;

return ret;
}

int[] mysticCalc(int[] values, int[] indexes) {
    int zeroes = countZeroes(values); 
    int[] retval = new int[values.length];
    int product = 1;

    if (zeroes >= 2) { // 2 or more zeroes, all results will be 0
        for (int i = 0; i > values.length; i++) {
            retval[i] = 0;      
        }
        return retval;
    }
    foreach (int i in values) {
        if (i != 0) product *= i; // we have at most 1 zero, dont include in product;
    }
    int indexcounter = 0;
    foreach(int idx in indexes) {
        if (zeroes == 1 && values[idx] != 0) {  // One zero on other index. Our value will be 0
            retval[indexcounter] = 0;
        }
        else if (zeroes == 1) { // One zero on this index. result is product
            retval[indexcounter] = product;
        }
        else { // No zeros. Return product/value at index
            retval[indexcounter] = product / values[idx];
        }
        indexcouter++;
    }   
    return retval;
}

В худшем случае эта программа будет проходить через 3 вектора один раз.

Ответ 2

Продукт всего, кроме текущего в Python

from numpy import array

def product(input, index):
    a = array(input)[index]

    if a[a == 0].size != 1:
        a = a.prod() / a # product except current
    else:
        # exaclty one non-zero-valued element in `a`
        nzi = a.nonzero() # indices of non-zero-valued elements
        a[a == 0] = a[nzi].prod()
        a[nzi] = 0

    return a

Пример:

for input in ([2,3,4,5], [2,0,4,5], [0,3,0,5]):
    print product(input, [1,3,2,0]) 

Вывод:

[40 24 30 60]
[40  0  0  0]
[0 0 0 0]

Ответ 3

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

Конечно, я должен предположить, что вход не имеет нулей.

Ответ 4

Продукт всего, кроме тока в C

void product_except_current(int input[], int index[], int out[], 
                            int len) {
  int prod = 1, nzeros = 0, izero = -1;

  for (int i = 0; i < len; ++i) 
    if ((out[i] = input[index[i]]) != 0)
      // compute product of non-zero elements 
      prod *= out[i]; // ignore possible overflow problem
    else {
      if (++nzeros == 2) 
         // if number of zeros greater than 1 then out[i] = 0 for all i
         break; 
      izero = i; // save index of zero-valued element
    }

  //  
  for (int i = 0; i < len; ++i)  
    out[i] = nzeros ? 0 : prod / out[i];                               

  if (nzeros == 1)
    out[izero] = prod; // the only non-zero-valued element
}

Ответ 5

Решение линейного времени в С# 3 для первой задачи: -

IEnumerable<int> ProductExcept(List<int> l, List<int> indexes) {
    if (l.Count(i => i == 0) == 1) {
        int singleZeroProd = l.Aggregate(1, (x, y) => y != 0 ? x * y : x);
        return from i in indexes select l[i] == 0 ? singleZeroProd : 0;
    } else {
        int prod = l.Aggregate(1, (x, y) => x * y);
        return from i in indexes select prod == 0 ? 0 : prod / l[i];
    }
}

Изменить: Принял во внимание один ноль! Мое последнее решение заняло у меня 2 минуты, пока я был на работе, поэтому я не чувствую себя так плохо: -)

Ответ 6

Здесь ответ на второй в С# с помощью тестового метода. Shuffle выглядит O (n) для меня.

Изменить: Посмотрев на Shuffle от Fisher-Yates, я обнаружил, что я повторно изобрел этот алгоритм, не зная об этом:-) Очевидно, однако. Я реализовал подход Дурстенфельда, который выводит нас из O (n ^ 2) → O (n), действительно умный!

public enum CardValue { A, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, J, Q, K }
public enum Suit { Spades, Hearts, Diamonds, Clubs }

public class Card {
    public Card(CardValue value, Suit suit) {
        Value = value;
        Suit = suit;
    }

    public CardValue Value { get; private set; }
    public Suit Suit { get; private set; }
}

public class Deck : IEnumerable<Card> {
    public Deck() {
        initialiseDeck();
        Shuffle();
    }

    private Card[] cards = new Card[52];

    private void initialiseDeck() {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 13; ++j) {
                cards[i * 13 + j] = new Card((CardValue)j, (Suit)i);
            }
        }
    }

    public void Shuffle() {
        Random random = new Random();

        for (int i = 0; i < 52; ++i) {
            int j = random.Next(51 - i);
            // Swap the cards.
            Card temp = cards[51 - i];
            cards[51 - i] = cards[j];
            cards[j] = temp;
        }
    }

    public IEnumerator<Card> GetEnumerator() {
        foreach (Card c in cards) yield return c;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        foreach (Card c in cards) yield return c;
    }
}

class Program {
    static void Main(string[] args) {
        foreach (Card c in new Deck()) {
            Console.WriteLine("{0} of {1}", c.Value, c.Suit);
        }

        Console.ReadKey(true);
    }
}

Ответ 7

В Haskell:

import Array

problem1 input index = [(left!i) * (right!(i+1)) | i <- index]
  where left  = scanWith scanl
        right = scanWith scanr
        scanWith scan = listArray (0, length input) (scan (*) 1 input)

Ответ 8

Вайбхав, к сожалению, мы должны предположить, что во входной таблице может быть 0.

Ответ 9

Tnilsson, отличное решение (потому что я сделал это точно так же: P).

Я не вижу другого способа сделать это в линейном времени. Кто-нибудь? Потому что менеджер по найму сказал мне, что это решение недостаточно сильное.

Мы пропустили какой-то суперкомплекс, делаем все в одной обратной линии, решение?

Ответ 10

Вторая проблема.

    public static void shuffle (int[] array) 
    {
        Random rng = new Random();   // i.e., java.util.Random.
        int n = array.length;        // The number of items left to shuffle (loop invariant).
        while (n > 1) 
        {
            int k = rng.nextInt(n);  // 0 <= k < n.
            n--;                     // n is now the last pertinent index;
            int temp = array[n];     // swap array[n] with array[k] (does nothing if k == n).
            array[n] = array[k];
            array[k] = temp;
        }
    }

Это копия/вставка из статьи wikipedia о Fisher-Yates shuffle. O (n) сложность

Ответ 11

Tnilsson, я согласен с тем, что решение YXJuLnphcnQ возможно быстрее, но идеал один и тот же. Я забыл добавить, что язык является необязательным в первой задаче, а также int второй.

Вы правы, что calcg zeroes, а продукт int в том же цикле лучше. Может быть, это и есть дело.

Ответ 12

Tnilsson, я также использую Shisher Fisher-Yates:). Мне очень интересно тесто, о части тестирования:)

Ответ 13

Разделение колоды карт в формате С++

#include <algorithm>

class Deck {
  // each card is 8-bit: 4-bit for suit, 4-bit for value
  // suits and values are extracted using bit-magic
  char cards[52];
  public:
  // ...
  void shuffle() {
    std::random_shuffle(cards, cards + 52);
  }
  // ...
};

Сложность: линейная в N. Точно выполняется 51 своп. См. http://www.sgi.com/tech/stl/random_shuffle.html

Тестирование

  // ...
  int main() {
    typedef std::map<std::pair<size_t, Deck::value_type>, size_t> Map;
    Map freqs;    
    Deck d;
    const size_t ntests = 100000;

    // compute frequencies of events: card at position
    for (size_t i = 0; i < ntests; ++i) {
      d.shuffle();
      size_t pos = 0;
      for(Deck::const_iterator j = d.begin(); j != d.end(); ++j, ++pos) 
        ++freqs[std::make_pair(pos, *j)]; 
    }

    // if Deck.shuffle() is correct then all frequencies must be similar
    for (Map::const_iterator j = freqs.begin(); j != freqs.end(); ++j)
      std::cout << "pos=" << j->first.first << " card=" << j->first.second 
                << " freq=" << j->second << std::endl;    
  }

Как обычно, одного теста недостаточно.

Ответ 15

YXJuLnphcnQ, так, как я это сделал. Это наиболее очевидно.

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