TweetFollow Us on Twitter

Dec 95 Challenge
Volume Number:11
Issue Number:12
Column Tag:Programmer’s Challenge

Programmer’s Challenge

By Bob Boonstra, Westford, Massachusetts

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Find Again And Again

This month the Challenge is to write a text search engine that is optimized to operate repeatedly on the same text. You will be given a block of text, some storage for data structures, and an opportunity to analyze the text before being asked to perform any searches against that text. Then you will repeatedly be asked to find a specific occurrence of a given word in that block of text. The prototypes for the code you should write are:

void InitFind(
 char *textToSearch, /* find words in this block of text  */
 long textLength,/* number of chars in textToSearch   */
 void *privateStorage,    /* storage for your use              */
 long storageSize/* number of bytes in privateStorage */
);
long FindWordOccurrence( 
    /* return offset of wordToFind in textToSearch   */
 char *wordToFind, /* find this word in textToSearch    */
 long wordLength,/* number of chars in wordToFind     */
 long occurrenceToFind, /* find this instance of wordToFind  */
 char *textToSearch, /* same parameter passed to InitFind */
 long textLength,/* same parameter passed to InitFind */
 void *privateStorage,  /* same parameter passed to InitFind */
 long storageSize/* same parameter passed to InitFind */
);

The InitFind routine will be called once for a given block of textLength characters at textToSearch to allow you to analyze the text, create data structures, and store them in privateStorage. When InitFind is called, storageSize bytes of memory at privateStorage will have been preallocated and initialized to zero.

FindWordOccurrence is to search for words, where a word is defined as a continuous sequence of alphanumeric characters delimited by a non-alphanumeric character (e.g., space, tab, punctuation, hyphen, CR, NL, or other special character). Your code should look for complete words - it would be incorrect, for example, to return a value pointing to the word “these” if the wordToFind was “the”. The wordToFind will be a legal word (i.e., no embedded delimiters). FindWordOccurrence should return the offset in textToSearch of the occurrenceToFind-th instance of wordToFind. It should return -1 if wordToFind does not occur in textToSearch, or if there are fewer than occurrenceToFind instances of wordToFind.

Both the InitFind and the FindWordOccurrence routines will be timed in determining the winner. In designing your code, you should assume that FindWordOccurrence will be called approximately 1000 times for each call to InitFind (with the same textToSearch, but possibly differing values of wordToFind and occurrenceToFind).

There is no predefined limit on textLength - you should handle text of arbitrary length. The amount of privateStorage available could be very large, but is guaranteed to be at least 64K bytes. While the test cases will include at least one large textToSearch with a small storageSize, most test cases will provide at least 32 bytes for each occurrence of a word in textToSearch, so you might want to optimize for that condition.

Other fine print: you may not change the input pointed to by textToSearch or wordToFind, and you should not use any static storage other than that provided in privateStorage.

This will be a native PowerPC Challenge, scored using the latest CodeWarrior compiler. Good luck, and happy searching.

Programmer’s Challenge Mailing List

We are pleased to announce the creation of the Programmer’s Challenge Mailing List. The list will be used to distribute the latest Challenge, provide answers to questions about the current Challenge, and discuss suggestions for future Challenges. The Challenge problem will be posted to the list each month, sometime between the 20th and the 25th of the month. This should alleviate problems caused by variations in the publication and mailing date of the magazine, and provide a predictable amount of time to work on each Challenge.

To subscribe to the list, send a message to autoshare@mactech.com with the SUBJECT line “sub challenge YourName”, substituting your real name for YourName. To unsubscribe from the list, send a message to autoshare@mactech.com with the SUBJECT line “unsub challenge”.

Note: the list server, autoshare, is set to accept commands in the SUBJECT line, not the body of the message. If you have any problems, please contact online@mactech.com.

Two Month’s Ago Winner

The Master Mindreader Challenge inspired ten readers to enter, and all ten solutions gave correct results. Congratulations to Xan Gregg (Durham, N.C.) for producing the fastest entry and winning the Challenge.

The problem required you to write code that would correctly guess a sequence of colors using a callback routine provided in the problem statement that returned two values for each guess: the number of elements of the guess where the correct color is located in the correct place in the sequence, and the number of elements where the correct color is in an incorrect place in the sequence. The number of guesses was not an explicit factor in determining the winner, but the time used by the callback routine was included in determining the winner. Participants correctly noted that this made the relative execution time of the guessing routine and the callback routine a factor in designing a fast solution. A couple of entries went so far as to offer their own, more efficient, callbacks. Nice try, but I didn’t use them - the callback in the problem was designed to provide a known time penalty for making a guess, and that was the callback I used in evaluating solutions.

The callback I supplied had one unanticipated side effect - it permitted callers to supply an out-of-range value for positions in the sequence that they didn’t care about for that guess, and six of the entries took advantage of this loophole. This wasn’t what I had intended, and I gave some thought to giving priority to solutions that did not use the loophole. In the end, however, I decided not to treat these entries any differently, because the solution statement permitted and provided a defined callback behavior for out-of-range guesses. As it turned out, the winning entry and three of the fastest four entries did not use out-of-range guesses.

Xan’s winning code first makes a sequence of guesses to determine how many positions are set to each of the possible colors. He then starts with an initial guess corresponding to these colors and begins swapping positions to determine how the number of correctly placed colors is affected. Separate logic handles the cases where the number of correctly placed colors increased or decreased by 0, 1, or 2, all the while keeping track of which color possibilities have been eliminated for each position. These and other details of Xan’s algorithm are documented in the comments to his code.

The table of results below indicates, in addition to execution time, the cumulative number of guesses used by each entry for all test cases. In general, it shows the expected rough correlation between execution time and the number of guesses, with a significant exception for the second-place entry from Ernst Munter, which took significantly fewer guesses. Ernst precalculated tables to define the guessing strategy for problems of length 5 or less and devised a technique for partitioning larger problems to use these tables. Normally I try to discourage the use of extensive precalculated data, but I decided to allow this entry because the amount of data was not unreasonable, because the tables guided the algorithm but did not precalculate a solution, and because I thought the approach was innovative and interesting. Although including the second-place entry in the article is not possible because of length restrictions, I have included the preamble from Ernst’s solution describing his approach.

Here are the times and code sizes for each of the entries. Numbers in parentheses after a person’s name indicate that person’s cumulative point total for all previous Challenges, not including this one.

Name time guesses code data out-of-range

values used?

Xan Gregg (61) 102 4123 1360 16 no

Ernst Munter (90) 109 2880 6264 5480 limited

Gustav Larsson (60) 116 3700 712 40 no

Greg Linden 127 5002 576 16 no

M. Panchenko (4) 146 5391 344 16 yes

Eric Lengyel (20) 176 6456 312 16 yes

Peter Hance 206 6557 336 16 yes

J. Vineyard (42) 228 9933 328 16 no

Ken Slezak (10) 251 6544 808 16 yes

Stefan Sinclair 259 11058 200 16 yes

Top 20 Contestants of All Time

Here are the Top 20 Contestants for the Programmer’s Challenges to date. The numbers below include points awarded for this month’s entrants. (Note: ties are listed alphabetically by last name - there are more than 20 people listed this month because of ties.)

Rank Name Points

1. [Name deleted] 176

2. Munter, Ernst 100

3. Gregg, Xan 81

4. Karsh, Bill 78

5. Larsson, Gustav 67

6. Stenger, Allen 65

7. Riha, Stepan 51

8. Goebel, James 49

9. Nepsund, Ronald 47

10. Cutts, Kevin 46

11. Mallett, Jeff 44

12. Kasparian, Raffi 42

13. Vineyard, Jeremy 42

14. Darrah, Dave 31

15. Landry, Larry 29

16. Elwertowski, Tom 24

17. Lee, Johnny 22

18. Noll, Robert 22

19. Anderson, Troy 20

20. Beith, Gary 20

21. Burgoyne, Nick 20

22. Galway, Will 20

23. Israelson, Steve 20

24. Landweber, Greg 20

25. Lengyel, Eric 20

26. Pinkerton, Tom 20

There are three ways to earn points: (1) scoring in the top 5 of any Challenge, (2) being the first person to find a bug in a published winning solution or, (3) being the first person to suggest a Challenge that I use. The points you can win are:

1st place 20 points

2nd place 10 points

3rd place 7 points

4th place 4 points

5th place 2 points

finding bug 2 points

suggesting Challenge 2 points

Here is Xan’s winning solution:

MindReader

By Xan Gregg,Durham, N.C.

/*  
  I try to minimize the number of guesses without adding too much complexity to the
  code.  First I figure out how many of each color are present in the answer by
  essentially repeatedly guessing all of each color.
  
  Then I figure out the correct positions one at a time starting at slot 0.  I exchange it
  with each other slot (one at a time) until the correct color is found.  When there is a
  change in the numCorrect response from checkGuess I can tell which of the two
  slots caused the change by looking at my remembered information or, if necessary,
  by performing a second guess with one of the colors in both slots.
  
  The “remembered information” includes keeping track of colors that were
  determined (via the numCorrectchange) to be wrong before and/or a swap is made. 
  This doesn’t help out too often, but it doesn’t take much time to record compared to
  calling checkGuess.
  
  While the outer loop determines the color of each slot “left-to-right” (0 to n-1), I
  found that indexing the inner loop right-to-left instead of left-to-right increased the
  speed by 30% - 40%.  I wish I understood why!
  
  Oddly, the checkGuess function spends most of its time figuring out the numWrong
  value, which we generally ignore.
*/

typedef void (*CheckGuessProcPtr)(
        unsigned char  *theGuess,
        unsigned short *numInCorrectPos,
        unsigned short *numInWrongPos);

#define kMaxLength 16

#define Bit(color) (1L << (long) (color))


MindReader

void MindReader(unsigned char guess[],
    CheckGuessProcPtr checkGuess,
        unsigned short answerLength,
        unsigned short numColors)
{
  long    prevColorsFound;
  long    colorsFound;
  long    curColor;
  long    i, j;
  long    curCorrect;
  long    numOfColor[kMaxLength + 1];  /* 1-based */
  Boolean isCorrect[kMaxLength];
  long    possibilities[kMaxLength];   /* bit fields */
  long    colorBit1;
  long    colorBit2;
  char    color1;
  char    color2;
  long    delta;
  unsigned short  newCorrect;
  unsigned short  newWrong;
  
  /* first find the correct set of colors */
  colorsFound = 0;
  curColor = 1;
  while (colorsFound < answerLength)
   {
    for (i = colorsFound; i < answerLength; i++)
      guess[i] = curColor;
    (*checkGuess)(guess, &newCorrect, &newWrong);
    prevColorsFound = colorsFound;
    colorsFound = newCorrect + newWrong;
    numOfColor[curColor] = colorsFound - prevColorsFound;
    curColor++;
   }
  
  /* now work on the order */
  for (i = 0; i < answerLength; i++)
   {
    isCorrect[i] = false;
    possibilities[i] = -1;  /* all colors */
   }
  curCorrect = newCorrect;
  /* step through every slot, starting at 0 */
  for (i = 0; curCorrect < answerLength; i++)
   {
    if (isCorrect[i])
      continue;
    color1 = guess[i];
    colorBit1 = Bit(color1);
    /* try swapping slot i with every other open */
    /* slot, starting with the last one */
    j = answerLength;
    nextSubSlot:
    j--;
    if (guess[i] == guess[j])
      goto nextSubSlot;
    if (isCorrect[j])
      goto nextSubSlot;
    color2 = guess[j];
    colorBit2 = Bit(color2);
    if ((possibilities[i] & colorBit2) == 0)
      goto nextSubSlot;  /* no hope here */
    /* swap slots i & j and check result */
    guess[i] = color2;
    guess[j] = color1;
    (*checkGuess)(guess, &newCorrect, &newWrong);
    delta = newCorrect - curCorrect;
    if (delta >= 0)
      if (delta == 0)
       {  /* either both are incorrect OR */
                           /* one is correct and answer[i]==answer[j] */
        guess[i] = color1;
        guess[j] = color2;
        if (numOfColor[color1] == 1)
         {  /* color1 can’t be in both places */
          possibilities[i] &= ~colorBit1;
          possibilities[j] &= ~colorBit1;
         }
        if (numOfColor[color2] == 1)
         {  /* color2 can’t be in both places */
          possibilities[i] &= ~colorBit2;
          possibilities[j] &= ~colorBit2;
         }
       }
      else if (delta == 1)
       {  /* both were wrong, now one is correct */
                        /* find out which is correct */
        curCorrect = newCorrect;
        if ((possibilities[j] & colorBit1) == 0)
         {  /* i must be color2 */
          possibilities[j] &= ~colorBit2;
          numOfColor[color2] -= 1;
          goto nextSlot;
         }
        else if ((possibilities[i] & colorBit2) == 0)
         {  /* j must be color1 */
          isCorrect[j] = true;
          possibilities[i] &= ~colorBit1;
          numOfColor[color1] -= 1;
          color1 = color2;
          colorBit1 = colorBit2;
         }
        else
         {  /* we’ll have to make another guess to */
                        /* see which is correct */
          guess[i] = color1;
          (*checkGuess)(guess, &newCorrect, &newWrong);
          if (newCorrect == curCorrect)
           {  /* j must be color1 */
            possibilities[i] &=
                  (~(colorBit1 | colorBit2));
            isCorrect[j] = true;
            guess[i] = color2;
            numOfColor[color1] -= 1;
            color1 = color2;
            colorBit1 = colorBit2;
           }
          else
           {  /* i must be color2 */
            possibilities[j] &=
                  (~(colorBit1 | colorBit2));
            guess[i] = color2;
            numOfColor[color2] -= 1;
            goto nextSlot;
           }
         }
       }
      else  /* delta == 2 */
       {  /* both were wrong, now both correct */
        isCorrect[j] = true;
        numOfColor[color1] -= 1;
        numOfColor[color2] -= 1;
        curCorrect = newCorrect;
        goto nextSlot;
       }
    else  /* delta < 0 */
      if (delta == -1)
       {  /* one was correct before swap, now neither is */
        guess[i] = color1;
        guess[j] = color2;
        if ((possibilities[i] & colorBit1) == 0)
         {  /* color2 in slot j was correct */
          isCorrect[j] = true;
          numOfColor[color2] -= 1;
          possibilities[i] &= ~colorBit2;
         }
        else if ((possibilities[j] & colorBit2) == 0)
         {  /* color1 in slot i was correct */
          possibilities[j] &= ~colorBit1;
          numOfColor[color1] -= 1;
          goto nextSlot;
         }
        else
         {  /* we’ll have to make another guess to */
                        /* see which was correct */
          guess[j] = color1;
          (*checkGuess)(guess, &newCorrect, &newWrong);
          if (newCorrect == curCorrect)
           {  /* color1 in slot i was correct */
            possibilities[j] &=
                  (~(colorBit1 | colorBit2));
            guess[j] = color2;
            numOfColor[color1] -= 1;
            goto nextSlot;
           }
          else
           {  /* color2 in slot j was correct */
            possibilities[i] &=
                  (~(colorBit1 | colorBit2));
            guess[j] = color2;
            isCorrect[j] = true;
            numOfColor[color2] -= 1;
           }
         }
       }
      else  /* delta == -2 */
       {  /* both were already correct */
        guess[i] = color1;
        guess[j] = color2;
        isCorrect[j] = true;
        numOfColor[color1] -= 1;
        numOfColor[color2] -= 1;
        goto nextSlot;
       }
    goto nextSubSlot;
    nextSlot: ;
   }
  done: ;
}

Alternative Approach (Description Only)

Copyright 1995, Ernst Munter, Kanata, ON, Canada.

/*
  Problem:
    Find the value of a multidigit code, by a question and answer method.  Each
    question is a guess of the code, the answer is the number of digits that are correct,
    reported as either in correct or wrong positions.

    The challenge is to minimize total time, that is in the first order, keep the number of
    guesses small, since the time to check the guess is included in total time.  But
    spending too much time minimizing the number of guesses is counterproductive.

  Assumptions:
    1. It is OK to guess a color that is not within the range 1 to numColors.  It will not
        be “correct” or “wrong”, but it will also not corrupt the CheckGuess function.

    2. The “opponent” will call with randomly generated correctAnswer codes, and not
        try to defeat the MindReader by learning the solution strategy.

    3. The objective is not to be a true Mindreader, as this could be done by reading into
         the (*checkGuess) code, the address of which is handy.  One would then
         disassemble PowerPC instructions to discover the hidden address of
         correctAnswer.

  Solution:
    It is relatively simple to manually construct solution trees for small N
    (N=answerLength), and make them into a lookup table.

    I have made a table for N=4, and hardcoded the trees for N=2 and N=3.

    The table for N=5 was too large to be done easily by hand, and I wrote a Tree
    Builder program to construct its 246 nodes.  I then hand tuned the 2 smallest parts
    of it.

    I felt, a 246 node tree is about at the limit of what might be tolerable in a static
    array.  The tree for N=6 would have 1400 or so nodes.  There are diminishing
    returns.  Adding the N=5 tree improved the higher splits (2 or 3 splits instead of 3 or
    4), but gained only a few percentage points on the callBack frequency overall;

    To keep the trees manageable, the permutation patterns and the color schemes are
    normalized.

    Now the details:

    Even if numColors > N, there can be at most N distinct colors in the answer, for
    example 5, if answerLength=5.

    And we can arrange a color mapping so that all colors are refered to by index 1, 2,
    3, etc, with the most frequently occurring color labeled #1.

    For N=5, this reduces the possible answers to 7 color schemes, 11111, 11112,
    11122, 11123, 11223, 11234, 12345.

    To solve for N<=5, the function “ProcessSlice()” only needs the color mapping, and
    a list of the colors, suitably sorted.

    For example, the real answer “73646” can be solved by walking the solution tree in
    4 steps, given the color list 6,3,4,7 and the pattern to be found is 42131.  The
    pattern at the root of the tree T11234, is 11234.

    To obtain the pattern information, I “scan” the answer with successive guesses
    (somewhat optimized for answer lengths of 2 to 4, to eliminate some obviously
    unneeded calls to checkGuess).  The basic idea is:

    correctAnswer
    7 3 6 4 6

    Six or seven calls to checkGuess, to build the color and color-frequency lists:

    guess       correct wrong   yetToFind colorList
    1 1 1 1 1   0       0       5         -
    2 2 2 2 2   0       0       5         -
    3 3 3 3 3   1       0       4         3
    4 4 4 4 4   1       0       3         3,4
    5 5 5 5 5   0       0       3         3,4
    6 6 6 6 6   2       0       1         6,3,4
    7 7 7 7 7   1       0       0         6,3,4,7

    The last call back is avoided if the color==numColors occurs in the code.

    Then, using the tree, the correct answer is found with four more calls to checkGuess:
                                 goal   42131
    6 6 3 4 7   1       x   tree code   11234
    6 3 6 7 4   2       x               12143
    4 3 6 6 7   2       x               32114
    4 6 6 7 3   1       x               31142

    7 3 6 4 6                           42131 (no other choice)

    This results in a total of 10 or 11 calls or less to the checkGuess function.

    On average, 10 calls are needed to solve 5-wide answers, when numColors is
    randomly set to a value from 1 to 16.

    For N>5, the size of tree grows very rapidly.  So I decided to split the answer into
    multiple slices, and treat each as separate problems of width 3, 4, or 5:

    6 = 3 + 3
    7 = 4 + 3
    8 = 4 + 4
    9 = 5 + 4
    10 = 5 + 5
    11 = 5 + 6 = 5 + (3 + 3)
    12 = 5 + 7 = 5 + (4 + 3)
    13 = 5 + 8 = 5 + (4 + 4)
    14 = 5 + 9 = 5 + (5 + 4)
    15 = 5 + 10 = 5 + (5 + 5)
    16 = 8 + 8 = (4 + 4) + (4 + 4)

    To create a split, we call checkGuess with guesses of a solid color for the left side,
    and 0s for the right. (e.g. first guess 1 1 1 1 0 0 0 0, to split 8).  As a result,
    correctPos gives the number of 1s in the left slice, and wrongPos, the number of 1s
    in the right slice.  If we get correctPos+wrongPos=4 as an answer, we must call
    again because there might be more than four 1s in the answer;  the guess 0 0 0 0 1 1
    1 1 will do it.

    Performance:

    Overall, I find an almost linear relationship between the total number of call backs
    (CB) and the value of answerLength (AL), approximately CB = AL * 1.26 + 2.84
    when numColors varies randomly from 1 to 16.
*/

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

MacFamilyTree 8.2.7 - Create and explore...
MacFamilyTree gives genealogy a facelift: modern, interactive, convenient and fast. Explore your family tree and your family history in a way generations of chroniclers before you would have loved.... Read more
WhatsApp 0.2.8000 - Desktop client for W...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
TotalFinder 1.10.7 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Features... Read more
Box Sync 4.0.7886 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Espresso 5.1 - Powerful HTML, XML, CSS,...
Note from the developer: For the new Espresso, we changed our versioning and licensing approach with more consistent pricing and a simpler development timeline: "X+1". Each new update would increase... Read more
VueScan 9.6.04 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Slack 3.0.5 - Collaborative communicatio...
Slack is a collaborative communication app that simplifies real-time messaging, archiving, and search for modern working teams. Version 3.0.5: Bug Fixes: An important security update. Security... Read more
VirtualBox 5.2.6 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
Vivaldi 1.13.1008.40 - An advanced brows...
Vivaldi is a browser for our friends. In 1994, two programmers started working on a web browser. Our idea was to make a really fast browser, capable of running on limited hardware, keeping in mind... Read more
WhatRoute 2.1.1 - Geographically trace o...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the router... Read more

Latest Forum Discussions

See All

JYDGE (Games)
JYDGE 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: Build your JYDGE. Enter Edenbyrg. Get out alive. JYDGE is a lawful but awful roguehate top-down shooter where you get to build your... | Read more »
Tako Bubble guide - Tips and Tricks to S...
Tako Bubble is a pretty simple and fun puzzler, but the game can get downright devious with its puzzle design. If you insist on not paying for the game and want to manage your lives appropriately, check out these tips so you can avoid getting... | Read more »
Everything about Hero Academy 2 - The co...
It's fair to say we've spent a good deal of time on Hero Academy 2. So much so, that we think we're probably in a really good place to give you some advice about how to get the most out of the game. And in this guide, that's exactly what you're... | Read more »
Everything about Hero Academy 2: Part 3...
In the third part of our Hero Academy 2 guide we're going to take a look at the different modes you can play in the game. We'll explain what you need to do in each of them, and tell you why it's important that you do. [Read more] | Read more »
Everything about Hero Academy 2: Part 2...
In this second part of our guide to Hero Academy 2, we're going to have a look at the different card types that you're going to be using in the game. We'll split them up into different sections too, to make sure you're getting the most information... | Read more »
Everything about Hero Academy 2: Part 1...
So you've started playing Hero Academy 2, and you're feeling a little bit lost. Don't worry, we've got your back. So we've come up with a series of guides that are going to help you get to grips with everything that's going on in the game. [Read... | Read more »
What mobile gaming can learn from the Ni...
While Nintendo might not have had things all its own way since it began developing for mobile, one thing it has got right is the release of the Switch. After the disappointment of the WiiU, which I still can't really explain, the Switch felt a... | Read more »
Programmer of Sonic The Hedgehog launche...
Japanese programmer Yuji Naka is best known for leading the team that created the original Sonic The Hedgehog. He’s moved on from the speedy blue hero since then, launching his own company based in Tokyo – Prope Games. Legend of Coin is the... | Read more »
Why doesn't mobile gaming have its...
The Overwatch League is a pretty big deal. It's an attempt to really push eSports into the mainstream, by turning them into, well, regular sports. But slightly less sweaty. It's a lavish affair with teams from all around the world, and more... | Read more »
Give Webzen’s new billiard game PoolTime...
Best known for producing hugely popular MMO titles, South Korean publisher Webzen is now taking aim at a different genre altogether. PoolTime is a realistic eight ball pool simulator, allowing you to compete in real-time matches against players... | Read more »

Price Scanner via MacPrices.net

9.7-inch 2017 WiFi iPads on sale starting at...
B&H Photo has 9.7″ 2017 WiFi #Apple #iPads on sale for $30 off MSRP for a limited time. Shipping is free, and pay sales tax in NY & NJ only: – 32GB iPad WiFi: $299, $30 off – 128GB iPad WiFi... Read more
Wednesday deal: 13″ MacBook Pros for $100-$15...
B&H Photo has 13″ #Apple #MacBook Pros on sale for up to $100-$150 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 13-inch 2.3GHz/128GB Space Gray... Read more
Apple now offering Certified Refurbished 2017...
Apple has Certified Refurbished 9.7″ WiFi iPads available for $50-$80 off the cost of new models. An Apple one-year warranty is included with each iPad, and shipping is free: – 9″ 32GB WiFi iPad: $... Read more
10″ iPad Pros on sale for $50-$75 off MSRP, n...
B&H Photo has 10″ and #Apple #iPad Pros on sale for up to $75 off MSRP. Shipping is free, and B&H charges sales tax in NY & NJ only. Note that some sale prices are restricted to certain... Read more
Apple refurbished Mac minis available startin...
Apple has restocked Certified Refurbished Mac minis starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: – 1.4GHz Mac mini: $419 $80 off MSRP – 2.6GHz Mac... Read more
Amazon offers Silver 13″ Apple MacBook Pros f...
Amazon has new Silver 2017 13″ #Apple #MacBook Pros on sale today for up to $150 off MSRP, each including free shipping: – 13″ 2.3GHz/128GB Silver MacBook Pro (MPXR2LL/A): $1199.99 $100 off MSRP – 13... Read more
Sale: 12″ 1.3GHz MacBooks on sale for $1499,...
B&H Photo has Space Gray and Rose Gold 12″ 1.3GHz #Apple MacBooks on sale for $100 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 12″ 1.3GHz Space... Read more
Apple offers Certified Refurbished 2017 iMacs...
Apple has a full line of Certified Refurbished iMacs available for up to $350 off original MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: – 27... Read more
13″ MacBook Airs on sale for $120-$100 off MS...
B&H Photo has 2017 13″ 128GB MacBook Airs on sale for $120 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 13″ 1.8GHz/128GB MacBook Air (MQD32LL/A): $... Read more
15″ Touch Bar MacBook Pros on sale for up to...
Adorama has Space Gray 15″ MacBook Pros on sale for $200 off MSRP. Shipping is free, and Adorama charges sales tax in NJ and NY only: – 15″ 2.8GHz MacBook Pro Space Gray (MPTR2LL/A): $2199, $200 off... Read more

Jobs Board

*Apple* Solutions Consultant - Apple (United...
# Apple Solutions Consultant Job Number: 113384559 Brandon, Florida, United States Posted: 10-Jan-2018 Weekly Hours: 40.00 **Job Summary** Are you passionate about Read more
Art Director, *Apple* Music + Beats1 Market...
# Art Director, Apple Music + Beats1 Marketing Design Job Number: 113258081 Santa Clara Valley, California, United States Posted: 05-Jan-2018 Weekly Hours: 40.00 Read more
*Apple* Pay & Wallet Engineering Manager...
# Apple Pay & Wallet Engineering Manager, Apple Watch Job Number: 83769531 Santa Clara Valley, California, United States Posted: 06-Nov-2017 Weekly Hours: 40.00 Read more
UI Tools and Automation Engineer, *Apple* M...
# UI Tools and Automation Engineer, Apple Media Products Job Number: 113136387 Santa Clara Valley, California, United States Posted: 11-Jan-2018 Weekly Hours: 40.00 Read more
Senior Product Architect, *Apple* Pay - App...
# Senior Product Architect, Apple Pay Job Number: 58046427 Santa Clara Valley, California, United States Posted: 04-Jan-2018 Weekly Hours: **Job Summary** Apple , Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.