Determine if a string has all unique characters: Difference between revisions

Content added Content deleted
(→‎{{header|JavaScript}}: Added a variation in terms of sorting and grouping.)
Line 493: Line 493:


=={{header|JavaScript}}==
=={{header|JavaScript}}==

<lang javascript>(() => {
'use strict';

// duplicatedCharIndices :: String -> Maybe (Char, [Int])
const duplicatedCharIndices = s => {
const
duplications = filter(g => 1 < g.length)(
groupBy(on(eq)(snd))(
sortBy(comparing(snd))(
zip(enumFrom(0))(chars(s))
)
)
);
return 0 < duplications.length ? (() => {
const
firstCase = sortBy(comparing(compose(fst, fst)))(
duplications
)[0];
return Just(Tuple(firstCase[0][1])(
firstCase.map(fst)
));
})() : Nothing()
};

// ------------------------TEST------------------------
const main = () =>
console.log(
fTable('First duplicated character, if any:')(
s => `'${s}'`
)(maybe('None')(tpl => {
const [c, ixs] = Array.from(tpl);
return `'${c}' (0x${showHex(ord(c))}) at ${ixs.join(', ')}`
}))(duplicatedCharIndices)([
"", ".", "abcABC", "XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"
])
);


// -----------------GENERIC FUNCTIONS------------------

// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});

// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});

// Tuple (,) :: a -> b -> (a, b)
const Tuple = a => b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});

// chars :: String -> [Char]
const chars = s => s.split('');

// comparing :: (a -> b) -> (a -> a -> Ordering)
const comparing = f =>
x => y => {
const
a = f(x),
b = f(y);
return a < b ? -1 : (a > b ? 1 : 0);
};

// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
x => fs.reduceRight((a, f) => f(a), x);

// enumFrom :: Enum a => a -> [a]
function* enumFrom(x) {
let v = x;
while (true) {
yield v;
v = 1 + v;
}
}

// eq (==) :: Eq a => a -> a -> Bool
const eq = a => b => a === b;

// filter :: (a -> Bool) -> [a] -> [a]
const filter = f => xs => xs.filter(f);

// fst :: (a, b) -> a
const fst = tpl => tpl[0];

// fTable :: String -> (a -> String) -> (b -> String)
// -> (a -> b) -> [a] -> String
const fTable = s => xShow => fxShow => f => xs => {
// Heading -> x display function ->
// fx display function ->
// f -> values -> tabular string
const
ys = xs.map(xShow),
w = Math.max(...ys.map(length));
return s + '\n' + zipWith(
a => b => a.padStart(w, ' ') + ' -> ' + b
)(ys)(
xs.map(x => fxShow(f(x)))
).join('\n');
};

// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
const groupBy = fEq =>
// Typical usage: groupBy(on(eq)(f), xs)
xs => 0 < xs.length ? (() => {
const
tpl = xs.slice(1).reduce(
(gw, x) => {
const
gps = gw[0],
wkg = gw[1];
return fEq(wkg[0])(x) ? (
Tuple(gps)(wkg.concat([x]))
) : Tuple(gps.concat([wkg]))([x]);
},
Tuple([])([xs[0]])
);
return tpl[0].concat([tpl[1]])
})() : [];

// length :: [a] -> Int
const length = xs =>
// Returns Infinity over objects without finite length.
// This enables zip and zipWith to choose the shorter
// argument when one is non-finite, like cycle, repeat etc
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;

// maybe :: b -> (a -> b) -> Maybe a -> b
const maybe = v =>
// Default value (v) if m is Nothing, or f(m.Just)
f => m => m.Nothing ? v : f(m.Just);

// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
const on = f =>
g => a => b => f(g(a))(g(b));

// ord :: Char -> Int
const ord = c => c.codePointAt(0);

// showHex :: Int -> String
const showHex = n =>
n.toString(16);

// snd :: (a, b) -> b
const snd = tpl => tpl[1];

// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
const sortBy = f => xs =>
xs.slice()
.sort(uncurry(f));

// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n => xs =>
'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));

// uncurry :: (a -> b -> c) -> ((a, b) -> c)
const uncurry = f =>
(x, y) => f(x)(y)

// zip :: [a] -> [b] -> [(a, b)]
const zip = xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
vs = take(lng)(ys);
return take(lng)(xs)
.map((x, i) => Tuple(x)(vs[i]));
};

// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
vs = take(lng)(ys);
return take(lng)(xs)
.map((x, i) => f(x)(vs[i]));
};

// MAIN ---
return main();
})();</lang>
{{Out}}
<pre>First duplicated character, if any:
'' -> None
'.' -> None
'abcABC' -> None
'XYZ ZYX' -> 'X' (0x58) at 0, 6
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' -> '0' (0x30) at 9, 24</pre>


Or, an alternative to sorting and grouping:
<lang javascript>(() => {
<lang javascript>(() => {
'use strict';
'use strict';