Προγραμματιστική πρόκληση Νο 2: Risk Card Sets

Στο παιγνίδι Risk κάποιες φορές τραβάς μια κάρτα. Οι πιθανές κάρτες είναι Άλογο (H), Κανόνι ( C ), Στρατιώτης (S) ή Μπαλαντέρ (J) που μπορεί να γίνει οποιαδήποτε άλλη κάρτα.
Ένα σύνολο από 3 κάρτες είναι έγκυρο, όταν οι κάρτες είναι όλες είτε ίδιες, είτε διαφορετικές. Για παράδειγμα

Έγκυρα σύνολα Μη έγκυρα σύνολα
{ Η , Η , Η } 3 ίδια { H , H , C }
{ C , S , H } 3 διαφορετικά { H , C , H }
{ S , S , J } 3 ίδια, με μπαλαντέρ σαν S
{ H , S , J } 3 διαφορετικά, με μπαλαντέρ σαν C
{ H , J , J } με 2 μπαλαντέρ !

Φτιάξτε μια συνάρτηση που να λέει αν ένα σύνολο από 3 κάρτες είναι έγκυρο ή όχι
Φτιάξτε μια συνάρτηση που να λέει αν υπάρχει ένα έγκυρο σύνολο σε μια συλλογή από κάρτες.

Πηγή: http://langrsoft.com/2017/07/17/tdd-kata-risk-card-sets/

2 «Μου αρέσει»

Απ’ ότι βλέπω, δεν υπάρχουν κάποια σύνολα τα οποία θα μπορούσαν να βγουν τυχαία. Αυτά ανήκουν στα έγκυρα ή τα μη έγκυρα;

Φοβάμαι πως δεν κατάλαβα την ερώτηση. Έχεις κάποιο παράδειγμα που σε προβληματίζει ;

Μάλλον δεν κατάλαβα το παράδειγμα

Περίληψη
def simplevalid(x):
    if 'j' in x: return True
    
    if x[0] == x[1]:
        if x[0] == x[2]: return True
        else: return False
    
    if x[2] == x[0] or x[2] == x[1]: return False
    else: return True

    
def sortedvalid(x):
    y = [i for i in x if i!='j']
    if len(y) < 3: return True
    y.sort()
    if y[0] == y[-1]: return True
    for a,b in zip(y,y[1:]):
        if a == b: return False
    
    return True


def validinset(X, valid=simplevalid):
    for x in X:
        if valid(x): return True
    
    return False

έφτιαξα και την sortedvalid που δουλεύει και σε άλλες περιπτώσεις αν έχουμε περισσότερες κάρτες

Μπορείς να βάλεις σχόλια για το τι κάνει κάθε συνάρτηση και να εξηγήσεις την προσέγγιση σου; Όχι για μένα, αλλά για την κακομοίρα την μανούλα μου.

2 «Μου αρέσει»

Παραθέτω κάποια test cases για έλεγχο.

Περίληψη
#include <cassert>
#include <vector>

bool isValid(const std::vector<char> &hand);

auto main() -> int {
    // Invalid: less cards
    assert(!isValid({}));
    assert(!isValid({'C', 'C'}));

    // Valid: different cards
    assert(isValid({'C', 'S', 'H'}));
    assert(isValid({'C', 'H', 'J'}));
    assert(isValid({'C', 'S', 'H', 'J'}));

    // Valid: same cards
    assert(isValid({'C', 'C', 'C'}));
    assert(isValid({'J', 'J', 'J'}));

    // Use of 'J'
    assert(isValid({'S', 'S', 'J'}));
    assert(isValid({'S', 'J', 'S'}));
    assert(!isValid({'S', 'S', 'C'}));
    assert(!isValid({'C', 'S', 'S'}));
    assert(!isValid({'S', 'C', 'S'}));

    assert(isValid({'S', 'J', 'J'}));
    assert(isValid({'S', 'H', 'J'}));

    // Invalid cases
    assert(!isValid({'S', 'C', 'C'}));

    // 4 cards
    assert(isValid({'H', 'H', 'C', 'H'}));
    assert(isValid({'H', 'H', 'J', 'H'}));
    assert(isValid({'J', 'H', 'J', 'H'}));
    assert(!isValid({'H', 'H', 'C', 'C'}));

    // 5 cards
    assert(isValid({'H', 'H', 'C', 'H', 'C'}));
    assert(isValid({'H', 'H', 'C', 'H', 'H'}));

    return 0;
}
1 «Μου αρέσει»

κάνουν αυτό που ζήτησε η άσκηση. Πώς το κάνουν το άφησα για άσκηση στον αναγνώστη.

αλλά βλέπω τα παραδείγματά σου και καθόλου δεν φαίνεται ότι έτσι πρέπει να δουλεύει από την αρχική σου ανάρτηση:

1 «Μου αρέσει»

Με βάση τα καινούργια παραδείγματα:

def valid(x):
    
    if len(x) < 3: return False
    if len(x) > 4: return True #προεραιτικό!
    
    if 'J' in x: return True
    
    freq = x.count('H'), x.count('C'), x.count('S')
    for n in freq:
        if n >= 3: return True
    
    if 0 in freq: return False
    
    return True 
1 «Μου αρέσει»

σκέφτηκα να παίξω λίγο ακόμα με την σύνταξη:

def valid(x):
    l = len(x)
    if l < 3: return False
    if l > 4: return True
    
    if 'J' in x: return True
    
    cards = 'HCS'
    freq = (x.count(i) for i in cards)
    allcards = True
    for n in freq:
        if n >= 3: return True
        if not n: allcards = False
    
    return allcards
2 «Μου αρέσει»

Μιας και δεν υπήρξαν ακόμα πολλές συμμετοχές δεν θα αναπτύξω το σκεπτικό, και ελεύθερα οποίος θέλει το κάνει. Δεν χρειάζεται καν να ξέρεις μια γλώσσα προγραμματισμού για να το κάνεις αυτό :slight_smile:

auto isValid(const vector<card_t> &hand) {
    if (hand.size() < 3) return false;
    if (hand.size() > 4) return true;
    if (contains(hand, Jocker))
        return true; // 3 cards. Did vector contains 'J' ?
    if ((hand.size() == 4) && (!eqDistr(hand))) return true;

    auto crd = getCardinality(hand);
    if (crd >= 3) return true; // different cards
    if (crd == 1) return true; // all cards the same
    return false;              // X-Y-Y, no jocker case
}

Μαζί με κάποιες βοηθητικές μικρές συναρτήσεις. Βγάζοντας τες έξω κάνω την λογική του αλγόριθμου πολύ ποιο εμφανής. Το ότι είχα τα asserts, που πρώτα έγραφα ένα τέτοιο και μετά προσπαθούσα να το κάνω να περάσει, βοήθησε πολύ σε αυτή την διαδικασία (την οποία λέμε refactoring). Τεσπα, οι βοηθητικές αυτές συναρτήσεις είναι :

// Check if vector contains element
template <typename T>
bool contains(const vector<T> &hand, T card) {
    return find(hand.begin(), hand.end(), card) != hand.end();
}

// Get vector cardinality
template <typename T>
auto getCardinality(vector<T> hand) {
    // set<T> set(begin(hand), end(hand));
    sort(hand.begin(), hand.end());
    hand.erase(unique(hand.begin(), hand.end()), hand.end());
    return hand.size();
}

// Is equal distribution ?
bool eqDistr(const vector<card_t> &hand) {
    assert(hand.size() == 4);
    // is it X-X-Y-Y or X-Y-Y-Y ?
    auto cnt = count_if(hand.begin(), hand.end(),
                        [&](char c) { return c == hand[0]; });
    return cnt == 2;
}

Ο πλήρης κώδικας υπάρχει εδώ.

Θα με συγχωρέσεις που δεν έχω βγάλει ακόμα την ολοκληρωμένη λύση μου σε πρόγραμμα τερματικού (με βάση την προηγούμενη άσκηση), αλλά υπάρχει μία έλλειψη χρόνου.

1 «Μου αρέσει»